grand orm refactoring

This commit is contained in:
Yuri Kuznetsov
2020-08-12 14:38:49 +03:00
parent 96c598d454
commit e7992e356a
85 changed files with 6290 additions and 2100 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ class Auth
$this->authRequired = $authRequired;
}
public function createForEntryPoint(Authentication $authentication, bool $authRequired = true)
public static function createForEntryPoint(Authentication $authentication, bool $authRequired = true)
{
$instance = new Auth($authentication, $authRequired);
$instance->isEntryPoint = true;
+8 -9
View File
@@ -54,15 +54,14 @@ class Reminders
$dt = new \DateTime();
$dt->modify($period);
$pdo = $this->entityManager->getPDO();
$delete = $this->entityManager->getQueryBuilder()
->delete()
->from('Reminder')
->where([
'remindAt<' => $dt->format('Y-m-d'),
])
->build();
$sql = $this->entityManager->getQuery()->createDeleteQuery('Reminder', [
'whereClause' => [
'remindAt<' => $dt->format('Y-m-d')
],
]);
$sth = $pdo->prepare($sql);
$sth->execute();
$this->entityManager->getQueryExecutor()->run($delete);
}
}
@@ -134,7 +134,7 @@ class SumRelatedType extends \Espo\Core\Formula\Functions\Base implements
$entityManager->getRepository($foreignEntityType)->handleSelectParams($selectParams);
$sql = $entityManager->getQuery()->createSelectQuery($foreignEntityType, $selectParams);
$sql = $entityManager->getQueryComposer()->createSelectQuery($foreignEntityType, $selectParams);
$pdo = $entityManager->getPDO();
$sth = $pdo->prepare($sql);
@@ -0,0 +1,98 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\ORM\Repository;
use Espo\ORM\{
Entity,
Repository\EmptyHookMediator,
QueryParams\Select,
};
use Espo\Core\HookManager;
class HookMediator extends EmptyHookMediator
{
protected $hookManager;
public function __construct(HookManager $hookManager)
{
$this->hookManager = $hookManager;
}
public function afterRelate(Entity $entity, string $relationName, Entity $foreignEntity, ?array $columnData, array $options)
{
if (!empty($options['skipHooks'])) {
return;
}
$hookData = [
'relationName' => $relationName,
'relationData' => $columnData,
'foreignEntity' => $foreignEntity,
'foreignId' => $foreignEntity->id,
];
$this->hookManager->process(
$entity->getEntityType(), 'afterRelate', $entity, $options, $hookData
);
}
public function afterUnrelate(Entity $entity, string $relationName, Entity $foreignEntity, array $options)
{
if (!empty($options['skipHooks'])) {
return;
}
$hookData = [
'relationName' => $relationName,
'foreignEntity' => $foreignEntity,
'foreignId' => $foreignEntity->id,
];
$this->hookManager->process(
$entity->getEntityType(), 'afterUnrelate', $entity, $options, $hookData
);
}
public function afterMassRelate(Entity $entity, string $relationName, Select $query, array $options)
{
if (!empty($options['skipHooks'])) {
return;
}
$hookData = [
'relationName' => $relationName,
'query' => $query,
];
$this->hookManager->process(
$entity->getEntityType(), 'afterMassRelate', $entity, $options, $hookData
);
}
}
@@ -35,7 +35,10 @@ use Espo\Core\{
Repositories\Database as DatabaseRepository,
};
use Espo\ORM\RepositoryFactory as RepositoryFactoryInterface;
use Espo\ORM\{
Repository\RepositoryFactory as RepositoryFactoryInterface,
Repository\Repository,
};
class RepositoryFactory implements RepositoryFactoryInterface
{
@@ -58,7 +61,7 @@ class RepositoryFactory implements RepositoryFactoryInterface
return $this->classFinder->find('Repositories', $name);
}
public function create(string $name) : object
public function create(string $name) : Repository
{
$className = $this->getClassName($name);
@@ -38,7 +38,7 @@ class CategoryTree extends Database
parent::afterSave($entity, $options);
$pdo = $this->getEntityManager()->getPDO();
$query = $this->getEntityManager()->getQuery();
$query = $this->getEntityManager()->getQueryComposer();
$parentId = $entity->get('parentId');
$pathsTableName = $query->toDb($query->sanitize($entity->getEntityType()) . 'Path');
@@ -91,7 +91,7 @@ class CategoryTree extends Database
parent::afterRemove($entity, $options);
$pdo = $this->getEntityManager()->getPDO();
$query = $this->getEntityManager()->getQuery();
$query = $this->getEntityManager()->getQueryComposer();
$pathsTableName = $query->toDb($query->sanitize($entity->getEntityType()) . 'Path');
+13 -22
View File
@@ -30,13 +30,14 @@
namespace Espo\Core\Repositories;
use Espo\ORM\{
Repositories\RDB,
Repository\RDBRepository,
Entity,
};
use Espo\Core\ORM\{
EntityManager,
EntityFactory,
Repository\HookMediator,
};
use Espo\Core\{
@@ -46,7 +47,7 @@ use Espo\Core\{
ApplicationState,
};
class Database extends RDB
class Database extends RDBRepository
{
protected $hooksDisabled = false;
@@ -72,7 +73,13 @@ class Database extends RDB
$this->hookManager = $hookManager;
$this->applicationState = $applicationState;
parent::__construct($entityType, $entityManager, $entityFactory, $metadata);
$hookMediator = null;
if (!$this->hooksDisabled) {
$hookMediator = new HookMediator($hookManager);
}
parent::__construct($entityType, $entityManager, $entityFactory, $hookMediator);
}
protected function getMetadata()
@@ -178,8 +185,7 @@ class Database extends RDB
public function remove(Entity $entity, array $options = [])
{
$result = parent::remove($entity, $options);
return $result;
return parent::remove($entity, $options);
}
protected function afterRelate(Entity $entity, $relationName, $foreign, $data = null, array $options = [])
@@ -198,15 +204,7 @@ class Database extends RDB
}
if ($foreign instanceof Entity) {
$hookData = [
'relationName' => $relationName,
'relationData' => $data,
'foreignEntity' => $foreign,
'foreignId' => $foreign->id,
];
$this->hookManager->process(
$this->entityType, 'afterRelate', $entity, $options, $hookData
);
$this->hookMediator->afterRelate($entity, $relationName, $foreign, $data, $options);
}
}
}
@@ -227,14 +225,7 @@ class Database extends RDB
}
if ($foreign instanceof Entity) {
$hookData = [
'relationName' => $relationName,
'foreignEntity' => $foreign,
'foreignId' => $foreign->id,
];
$this->hookManager->process(
$this->entityType, 'afterUnrelate', $entity, $options, $hookData
);
$this->hookMediator->afterUnrelate($entity, $relationName, $foreign, $options);
}
}
}
+8 -8
View File
@@ -34,7 +34,7 @@ use Espo\Core\Utils\Util;
use Espo\Core\Di;
class Event extends \Espo\Core\Repositories\Database implements
class Event extends Database implements
Di\DateTimeAware,
Di\ConfigAware
{
@@ -125,16 +125,16 @@ class Event extends \Espo\Core\Repositories\Database implements
{
parent::afterRemove($entity, $options);
$pdo = $this->getEntityManager()->getPDO();
$sql = $this->getEntityManager()->getQuery()->createDeleteQuery('Reminder', [
'whereClause' => [
$delete = $this->getEntityManager()->getQueryBuilder()
->delete()
->from('Reminder')
->where([
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
],
]);
])
->build();
$pdo->query($sql);
$this->getEntityManager()->getQueryExecutor()->run($delete);
}
protected function afterSave(Entity $entity, array $options = [])
+42 -15
View File
@@ -44,7 +44,7 @@ use Espo\Core\{
ORM\EntityManager,
};
use Espo\ORM\DB\Query\BaseQuery as Query;
use Espo\ORM\QueryComposer\BaseQueryComposer as QueryComposer;
use Espo\ORM\Entity;
@@ -373,19 +373,23 @@ class SelectManager
$key = $midKeys[1];
$part[$link . 'Filter' . 'Middle.' . $key] = $idsValue;
}
} else if ($relationType == 'hasMany') {
$alias = $link . 'Filter';
$this->addLeftJoin([$link, $alias], $result);
$part[$alias . '.id'] = $idsValue;
} else if ($relationType == 'belongsTo') {
$key = $seed->getRelationParam($link, 'key');
if (!empty($key)) {
$part[$key] = $idsValue;
}
} else if ($relationType == 'hasOne') {
$this->addJoin([$link, $link . 'Filter'], $result);
$part[$link . 'Filter' . '.id'] = $idsValue;
} else {
return;
}
@@ -403,7 +407,7 @@ class SelectManager
$idsValue = $idsValue[0];
}
$query = $this->getEntityManager()->getQuery();
$query = $this->getEntityManager()->getQueryComposer();
$seed = $this->getSeed();
@@ -441,7 +445,7 @@ class SelectManager
{
$relDefs = $this->getSeed()->getRelations();
$query = $this->getEntityManager()->getQuery();
$query = $this->getEntityManager()->getQueryComposer();
$tableName = $query->toDb($this->getSeed()->getEntityType());
@@ -542,6 +546,9 @@ class SelectManager
if (empty($result)) {
$result = [];
}
if (empty($result['from'])) {
$result['from'] = $this->entityType;
}
if (empty($result['joins'])) {
$result['joins'] = [];
}
@@ -980,7 +987,7 @@ class SelectManager
}
if ($attribute && $checkWherePermission) {
$argumentList = Query::getAllAttributesFromComplexExpression($attribute);
$argumentList = QueryComposer::getAllAttributesFromComplexExpression($attribute);
foreach ($argumentList as $argument) {
$this->checkWhereArgument($argument, $type);
}
@@ -1770,21 +1777,26 @@ class SelectManager
$key = $midKeys[1];
$part[$alias . 'Middle.' . $key] = $value;
}
} else if ($relationType == 'hasMany') {
$this->addLeftJoin([$link, $alias], $result);
$part[$alias . '.id'] = $value;
} else if ($relationType == 'belongsTo') {
$key = $seed->getRelationParam($link, 'key');
if (!empty($key)) {
$part[$key] = $value;
}
} else if ($relationType == 'hasOne') {
$this->addLeftJoin([$link, $alias], $result);
$part[$alias . '.id'] = $value;
} else {
break;;
}
$this->setDistinct(true, $result);
break;
@@ -1800,30 +1812,45 @@ class SelectManager
$alias = $link . 'NotLinkedFilter' . strval(rand(10000, 99999));
if ($relationType == 'manyMany') {
$this->addLeftJoin([$link, $alias], $result);
$midKeys = $seed->getRelationParam($link, 'midKeys');
$key = $seed->getRelationParam($link, 'midKeys')[1];
$this->addLeftJoin(
[
$link, $alias, [$key => $value],
],
$result
);
$part[$alias . 'Middle.' . $key] = null;
if (!empty($midKeys)) {
$key = $midKeys[1];
$result['joinConditions'][$alias] = [$key => $value];
$part[$alias . 'Middle.' . $key] = null;
}
} else if ($relationType == 'hasMany') {
$this->addLeftJoin([$link, $alias], $result);
$result['joinConditions'][$alias] = ['id' => $value];
$this->addLeftJoin(
[
$link, $alias, ['id' => $value]
],
$result
);
$part[$alias . '.id'] = null;
} else if ($relationType == 'belongsTo') {
$key = $seed->getRelationParam($link, 'key');
if (!empty($key)) {
$part[$key . '!='] = $value;
}
} else if ($relationType == 'hasOne') {
$this->addLeftJoin([$link, $alias], $result);
$part[$alias . '.id!='] = $value;
} else {
break;
}
$this->setDistinct(true, $result);
break;
case 'arrayAnyOf':
@@ -2320,7 +2347,7 @@ class SelectManager
}
$fullTextSearchColumnSanitizedList = [];
$query = $this->getEntityManager()->getQuery();
$query = $this->getEntityManager()->getQueryComposer();
foreach ($fullTextSearchColumnList as $i => $field) {
$fullTextSearchColumnSanitizedList[$i] = $query->sanitize($query->toDb($field));
}
@@ -2744,7 +2771,7 @@ class SelectManager
protected function applyLeftJoinsFromAttribute(string $attribute, array &$result)
{
if (strpos($attribute, ':') !== false) {
$argumentList = Query::getAllAttributesFromComplexExpression($attribute);
$argumentList = QueryComposer::getAllAttributesFromComplexExpression($attribute);
foreach ($argumentList as $argument) {
$this->applyLeftJoinsFromAttribute($argument, $result);
}
+7 -8
View File
@@ -307,8 +307,6 @@ class Job
*/
public function removePendingJobDuplicates()
{
$pdo = $this->getEntityManager()->getPDO();
$duplicateJobList = $this->getEntityManager()->getRepository('Job')
->select(['scheduledJobId'])
->where([
@@ -352,14 +350,15 @@ class Job
continue;
}
$sql = $this->getEntityManager()->getQuery()->createDeleteQuery('Job', [
'whereClause' => [
$delete = $this->entityManager->getQueryBuilder()
->delete()
->from('Job')
->where([
'id' => $jobIdList,
]
]);
])
->build();
$sth = $pdo->prepare($sql);
$sth->execute();
$this->entityManager->getQueryExecutor()->run($delete);
}
}
@@ -31,7 +31,6 @@ namespace Espo\Core\Utils\Database\Schema\rebuildActions;
class Currency extends \Espo\Core\Utils\Database\Schema\BaseRebuildActions
{
public function afterRebuild()
{
$defaultCurrency = $this->getConfig()->get('defaultCurrency');
@@ -39,16 +38,18 @@ class Currency extends \Espo\Core\Utils\Database\Schema\BaseRebuildActions
$baseCurrency = $this->getConfig()->get('baseCurrency');
$currencyRates = $this->getConfig()->get('currencyRates');
if ($defaultCurrency != $baseCurrency) {
if ($defaultCurrency !== $baseCurrency) {
$currencyRates = $this->exchangeRates($baseCurrency, $defaultCurrency, $currencyRates);
}
$currencyRates[$defaultCurrency] = '1.00';
$pdo = $this->getEntityManager()->getPDO();
$delete = $this->getEntityManager()->getQueryBuilder()
->delete()
->from('Currency')
->build();
$sql = $this->getEntityManager()->getQuery()->createDeleteQuery('Currency');
$pdo->prepare($sql)->execute();
$this->getEntityManager()->getQueryExecutor()->run($delete);
foreach ($currencyRates as $currencyName => $rate) {
$this->getEntityManager()->createEntity('Currency', [
@@ -58,20 +59,12 @@ class Currency extends \Espo\Core\Utils\Database\Schema\BaseRebuildActions
}
}
/**
* Calculate exchange rates if defaultCurrency doesn't equals baseCurrency
*
* @param string $baseCurrency
* @param string $defaultCurrency
* @param array $currencyRates [description]
* @return array - List of new currency rates
*/
protected function exchangeRates($baseCurrency, $defaultCurrency, array $currencyRates)
protected function exchangeRates(string $baseCurrency, string $defaultCurrency, array $currencyRates) : array
{
$precision = 5;
$defaultCurrencyRate = round(1 / $currencyRates[$defaultCurrency], $precision);
$exchangedRates = array();
$exchangedRates = [];
$exchangedRates[$baseCurrency] = $defaultCurrencyRate;
unset($currencyRates[$baseCurrency], $currencyRates[$defaultCurrency]);
@@ -82,6 +75,4 @@ class Currency extends \Espo\Core\Utils\Database\Schema\BaseRebuildActions
return $exchangedRates;
}
}
@@ -353,7 +353,11 @@ class EntityManager
$entityDefsDataContents = $this->getFileManager()->getContents($filePath);
$entityDefsDataContents = str_replace('{entityType}', $name, $entityDefsDataContents);
$entityDefsDataContents = str_replace('{entityTypeLowerFirst}', lcfirst($name), $entityDefsDataContents);
$entityDefsDataContents = str_replace('{tableName}', $this->getEntityManager()->getQuery()->toDb($name), $entityDefsDataContents);
$entityDefsDataContents = str_replace(
'{tableName}',
$this->getEntityManager()->getQueryComposer()->toDb($name),
$entityDefsDataContents
);
foreach ($replaceData as $key => $value) {
$entityDefsDataContents = str_replace('{'.$key.'}', $value, $entityDefsDataContents);
}
-2
View File
@@ -29,8 +29,6 @@
namespace Espo\Core\Utils;
use \Espo\Core\Exceptions\Error;
class Util
{
/**
@@ -139,7 +139,7 @@ class Notifications
public function afterRemove(Entity $entity)
{
$query = $this->entityManager->getQuery();
$query = $this->entityManager->getQueryComposer();
$sql = "
DELETE FROM `notification`
WHERE
+1 -1
View File
@@ -102,7 +102,7 @@ class Stream
$this->getStreamService()->unfollowAllUsersFromEntity($entity);
}
$query = $this->entityManager->getQuery();
$query = $this->entityManager->getQueryComposer();
$sql = "
UPDATE `note`
SET `deleted` = 1, `modified_at` = '".date('Y-m-d H:i:s')."'
+23 -23
View File
@@ -118,7 +118,7 @@ class Cleanup implements Job
protected function cleanupJobs()
{
$select = $this->entityManager->createSelectBuilder()
$delete = $this->entityManager->getQueryBuilder()->delete()
->from('Job')
->where([
'DATE:modifiedAt<' => $this->getCleanupJobFromDate(),
@@ -126,9 +126,9 @@ class Cleanup implements Job
])
->build();
$this->entityManager->getQueryExecutor()->delete($select);
$this->entityManager->getQueryExecutor()->run($delete);
$select = $this->entityManager->createSelectBuilder()
$delete = $this->entityManager->getQueryBuilder()->delete()
->from('Job')
->where([
'DATE:modifiedAt<' => $this->getCleanupJobFromDate(),
@@ -137,12 +137,12 @@ class Cleanup implements Job
])
->build();
$this->entityManager->getQueryExecutor()->delete($select);
$this->entityManager->getQueryExecutor()->run($delete);
}
protected function cleanupUniqueIds()
{
$select = $this->entityManager->createSelectBuilder()
$delete = $this->entityManager->getQueryBuilder()->delete()
->from('UniqueId')
->where([
'terminateAt!=' => null,
@@ -150,7 +150,7 @@ class Cleanup implements Job
])
->build();
$this->entityManager->getQueryExecutor()->delete($select);
$this->entityManager->getQueryExecutor()->run($delete);
}
protected function cleanupScheduledJobLog()
@@ -180,7 +180,7 @@ class Cleanup implements Job
$ignoreIdList[] = $logRecord->get('id');
}
$select = $this->entityManager->createSelectBuilder()
$delete = $this->entityManager->getQueryBuilder()->delete()
->from('ScheduledJobLogRecord')
->where([
'scheduledJobId' => $scheduledJobId,
@@ -189,7 +189,7 @@ class Cleanup implements Job
])
->build();
$this->entityManager->getQueryExecutor()->delete($select);
$this->entityManager->getQueryExecutor()->run($delete);
}
}
@@ -199,14 +199,14 @@ class Cleanup implements Job
$datetime = new \DateTime();
$datetime->modify($period);
$select = $this->entityManager->createSelectBuilder()
$delete = $this->entityManager->getQueryBuilder()->delete()
->from('ActionHistoryRecord')
->where([
'DATE:createdAt<' => $datetime->format('Y-m-d'),
])
->build();
$this->entityManager->getQueryExecutor()->delete($select);
$this->entityManager->getQueryExecutor()->run($delete);
}
protected function cleanupAuthToken()
@@ -215,7 +215,7 @@ class Cleanup implements Job
$datetime = new \DateTime();
$datetime->modify($period);
$select = $this->entityManager->createSelectBuilder()
$delete = $this->entityManager->getQueryBuilder()->delete()
->from('AuthToken')
->where([
'DATE:modifiedAt<' => $datetime->format('Y-m-d'),
@@ -223,7 +223,7 @@ class Cleanup implements Job
])
->build();
$this->entityManager->getQueryExecutor()->delete($select);
$this->entityManager->getQueryExecutor()->run($delete);
}
protected function cleanupAuthLog()
@@ -232,14 +232,14 @@ class Cleanup implements Job
$datetime = new \DateTime();
$datetime->modify($period);
$select = $this->entityManager->createSelectBuilder()
$delete = $this->entityManager->getQueryBuilder()->delete()
->from('AuthLogRecord')
->where([
'DATE:createdAt<' => $datetime->format('Y-m-d'),
])
->build();
$this->entityManager->getQueryExecutor()->delete($select);
$this->entityManager->getQueryExecutor()->run($delete);
}
protected function getCleanupJobFromDate()
@@ -348,7 +348,7 @@ class Cleanup implements Job
}
}
$select = $this->entityManager->createSelectBuilder()
$delete = $this->entityManager->getQueryBuilder()->delete()
->from('Attachment')
->where([
'deleted' => true,
@@ -356,7 +356,7 @@ class Cleanup implements Job
])
->build();
$this->entityManager->getQueryExecutor()->delete($select);
$this->entityManager->getQueryExecutor()->run($delete);
}
protected function cleanupEmails()
@@ -383,7 +383,7 @@ class Cleanup implements Job
$this->entityManager->removeEntity($attachment);
}
$select = $this->entityManager->createSelectBuilder()
$delete = $this->entityManager->getQueryBuilder()->delete()
->from('Email')
->where([
'deleted' => true,
@@ -391,16 +391,16 @@ class Cleanup implements Job
])
->build();
$this->entityManager->getQueryExecutor()->delete($select);
$this->entityManager->getQueryExecutor()->run($delete);
$select = $this->entityManager->createSelectBuilder()
$delete = $this->entityManager->getQueryBuilder()->delete()
->from('EmailUser')
->where([
'emailId' => $id,
])
->build();
$this->entityManager->getQueryExecutor()->delete($select);
$this->entityManager->getQueryExecutor()->run($delete);
}
}
@@ -452,7 +452,7 @@ class Cleanup implements Job
$repository = $this->entityManager->getRepository($scope);
$repository->deleteFromDb($entity->id);
$query = $this->entityManager->getQuery();
$query = $this->entityManager->getQueryComposer();
foreach ($entity->getRelationList() as $relation) {
if ($entity->getRelationType($relation) !== Entity::MANY_MANY) {
@@ -492,12 +492,12 @@ class Cleanup implements Job
continue;
}
$select = $this->entityManager->createSelectBuilder()
$delete = $this->entityManager->getQueryBuilder()->delete()
->from($relationEntityType)
->where($where)
->build();
$this->entityManager->getQueryExecutor()->delete($select);
$this->entityManager->getQueryExecutor()->run($delete);
} catch (\Exception $e) {
$GLOBALS['log']->error("Cleanup: " . $e->getMessage());
@@ -85,7 +85,7 @@ class FindByEmailAddressType extends BaseFunction implements
if ($contact) {
if (!in_array($domain, $ignoreList)) {
$account = $em->getRepository('Account')->join(['contacts'])->where([
$account = $em->getRepository('Account')->join('contacts')->where([
'emailAddress*' => '%' . $domain,
'contacts.id' => $contact->id,
])->findOne();
@@ -77,11 +77,13 @@ class CheckEmailAccounts implements JobTargeted
public function prepare(ScheduledJob $scheduledJob, string $executeTime)
{
$collection = $this->entityManager->getRepository('EmailAccount')->join([['assignedUser', 'assignedUserAdditional']])->where([
'status' => 'Active',
'useImap' => true,
'assignedUserAdditional.isActive' => true,
])->find();
$collection = $this->entityManager->getRepository('EmailAccount')
->join('assignedUser', 'assignedUserAdditional')
->where([
'status' => 'Active',
'useImap' => true,
'assignedUserAdditional.isActive' => true,
])->find();
foreach ($collection as $entity) {
$running = $this->entityManager->getRepository('Job')->where([
@@ -167,7 +167,7 @@ class Activities implements
$selectParams = $selectManager->mergeSelectParams($selectParams, $additinalSelectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Meeting', $selectParams);
$sql = $this->getEntityManager()->getQueryComposer()->createSelectQuery('Meeting', $selectParams);
return $sql;
}
@@ -222,7 +222,7 @@ class Activities implements
$selectParams = $selectManager->mergeSelectParams($selectParams, $additinalSelectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Call', $selectParams);
$sql = $this->getEntityManager()->getQueryComposer()->createSelectQuery('Call', $selectParams);
return $sql;
}
@@ -272,7 +272,7 @@ class Activities implements
$selectParams = $selectManager->mergeSelectParams($selectParams, $additinalSelectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Email', $selectParams);
$sql = $this->getEntityManager()->getQueryComposer()->createSelectQuery('Email', $selectParams);
return $sql;
}
@@ -351,7 +351,7 @@ class Activities implements
$selectParams = $selectManager->mergeSelectParams($selectParams, $additinalSelectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Meeting', $selectParams);
$sql = $this->getEntityManager()->getQueryComposer()->createSelectQuery('Meeting', $selectParams);
if ($this->isPerson($scope)) {
$link = null;
@@ -383,7 +383,7 @@ class Activities implements
$selectParams = $selectManager->mergeSelectParams($selectParams, $additinalSelectParams);
$sql .= ' UNION ' . $this->getEntityManager()->getQuery()->createSelectQuery('Meeting', $selectParams);
$sql .= ' UNION ' . $this->getEntityManager()->getQueryComposer()->createSelectQuery('Meeting', $selectParams);
}
}
@@ -463,7 +463,7 @@ class Activities implements
$selectParams = $selectManager->mergeSelectParams($selectParams, $additinalSelectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Call', $selectParams);
$sql = $this->getEntityManager()->getQueryComposer()->createSelectQuery('Call', $selectParams);
if ($this->isPerson($scope)) {
$link = null;
@@ -495,7 +495,7 @@ class Activities implements
$selectParams = $selectManager->mergeSelectParams($selectParams, $additinalSelectParams);
$sql .= ' UNION ' . $this->getEntityManager()->getQuery()->createSelectQuery('Call', $selectParams);
$sql .= ' UNION ' . $this->getEntityManager()->getQueryComposer()->createSelectQuery('Call', $selectParams);
}
}
@@ -576,7 +576,7 @@ class Activities implements
$selectParams = $selectManager->mergeSelectParams($selectParams, $additinalSelectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Email', $selectParams);
$sql = $this->getEntityManager()->getQueryComposer()->createSelectQuery('Email', $selectParams);
if ($this->isPerson($scope) || $this->isCompany($scope)) {
$selectParams = $baseSelectParams;
@@ -600,7 +600,7 @@ class Activities implements
$selectParams = $selectManager->mergeSelectParams($selectParams, $additinalSelectParams);
$sql .= "\n UNION \n" . $this->getEntityManager()->getQuery()->createSelectQuery('Email', $selectParams);
$sql .= "\n UNION \n" . $this->getEntityManager()->getQueryComposer()->createSelectQuery('Email', $selectParams);
$selectParams = $baseSelectParams;
$selectParams['customJoin'] .= "
@@ -626,7 +626,7 @@ class Activities implements
$selectParams = $selectManager->mergeSelectParams($selectParams, $additinalSelectParams);
$sql .= "\n UNION \n" . $this->getEntityManager()->getQuery()->createSelectQuery('Email', $selectParams);
$sql .= "\n UNION \n" . $this->getEntityManager()->getQueryComposer()->createSelectQuery('Email', $selectParams);
}
return $sql;
@@ -804,7 +804,7 @@ class Activities implements
$sql = $this->getActivitiesQuery($entity, $entityType, $statusList, $isHistory, $selectParams);
$query = $this->getEntityManager()->getQuery();
$query = $this->getEntityManager()->getQueryComposer();
$seed = $this->getEntityManager()->getEntity($entityType);
@@ -816,7 +816,7 @@ class Activities implements
$sql = $query->limit($sql, $offset, $limit);
$collection = $this->getEntityManager()->getRepository($entityType)->findByQuery($sql);
$collection = $this->getEntityManager()->getRepository($entityType)->findBySql($sql);
foreach ($collection as $e) {
$service->loadAdditionalFieldsForList($e);
@@ -843,7 +843,7 @@ class Activities implements
];
}
public function getActivities($scope, $id, $params = [])
public function getActivities(string $scope, string $id, array $params = [])
{
$entity = $this->getEntityManager()->getEntity($scope, $id);
if (!$entity) {
@@ -969,7 +969,7 @@ class Activities implements
$selectManager->applyAccess($selectParams);
}
return $this->getEntityManager()->getQuery()->createSelectQuery('Meeting', $selectParams);
return $this->getEntityManager()->getQueryComposer()->createSelectQuery('Meeting', $selectParams);
}
protected function getCalendarCallQuery($userId, $from, $to, $skipAcl)
@@ -1025,7 +1025,7 @@ class Activities implements
$selectManager->applyAccess($selectParams);
}
return $this->getEntityManager()->getQuery()->createSelectQuery('Call', $selectParams);
return $this->getEntityManager()->getQueryComposer()->createSelectQuery('Call', $selectParams);
}
protected function getCalendarTaskQuery($userId, $from, $to, $skipAcl = false)
@@ -1088,7 +1088,7 @@ class Activities implements
$selectManager->applyAccess($selectParams);
}
return $this->getEntityManager()->getQuery()->createSelectQuery('Task', $selectParams);
return $this->getEntityManager()->getQueryComposer()->createSelectQuery('Task', $selectParams);
}
protected function getCalendarSelectParams($scope, $userId, $from, $to, $skipAcl = false)
@@ -1182,7 +1182,7 @@ class Activities implements
$selectManager = $this->getSelectManagerFactory()->create($scope);
if (method_exists($selectManager, 'getCalendarSelectParams')) {
$selectParams = $selectManager->getCalendarSelectParams($userId, $from, $to, $skipAcl);
return $this->getEntityManager()->getQuery()->createSelectQuery($scope, $selectParams);
return $this->getEntityManager()->getQueryComposer()->createSelectQuery($scope, $selectParams);
}
$methodName = 'getCalendar' . $scope . 'Query';
@@ -1191,7 +1191,7 @@ class Activities implements
}
$selectParams = $this->getCalendarSelectParams($scope, $userId, $from, $to, $skipAcl);
return $this->getEntityManager()->getQuery()->createSelectQuery($scope, $selectParams);
return $this->getEntityManager()->getQueryComposer()->createSelectQuery($scope, $selectParams);
}
protected function getActivitiesQuery(Entity $entity, $scope, array $statusList = [], $isHistory = false, $additinalSelectParams = null)
@@ -1211,7 +1211,7 @@ class Activities implements
$selectParams = $selectManager->mergeSelectParams($selectParams, $additinalSelectParams);
return $this->getEntityManager()->getQuery()->createSelectQuery($scope, $selectParams);
return $this->getEntityManager()->getQueryComposer()->createSelectQuery($scope, $selectParams);
}
$methodName = 'getActivities' . $scope . 'Query';
@@ -1223,7 +1223,7 @@ class Activities implements
$selectParams = $selectManager->mergeSelectParams($selectParams, $additinalSelectParams);
return $this->getEntityManager()->getQuery()->createSelectQuery($scope, $selectParams);
return $this->getEntityManager()->getQueryComposer()->createSelectQuery($scope, $selectParams);
}
protected function getActivitiesSelectParams(Entity $entity, $scope, array $statusList = [], $isHistory)
@@ -1403,10 +1403,15 @@ class Activities implements
$userIdList = [];
$userList = $this->getEntityManager()->getRepository('User')->select(['id', 'name'])->leftJoin([['teams', 'teams']])->where([
'isActive' => true,
'teamsMiddle.teamId' => $teamIdList
])->distinct()->find([], true);
$userList = $this->getEntityManager()->getRepository('User')
->select(['id', 'name'])
->leftJoin('teams')
->where([
'isActive' => true,
'teamsMiddle.teamId' => $teamIdList
])
->distinct()
->find();
$userNames = (object) [];
@@ -1667,6 +1672,7 @@ class Activities implements
public function getUpcomingActivities($userId, $params = [], $entityTypeList = null, $futureDays = null)
{
$user = $this->getEntityManager()->getEntity('User', $userId);
$this->accessCheck($user);
if (!$entityTypeList) {
@@ -1682,6 +1688,8 @@ class Activities implements
$taskBeforeString = (new \DateTime())->modify('+' . $upcomingTaskFutureDays . ' days')->format('Y-m-d H:i:s');
$unionPartList = [];
foreach ($entityTypeList as $entityType) {
if (!$this->getMetadata()->get(['scopes', $entityType, 'activity']) && $entityType !== 'Task') continue;
if (!$this->getAcl()->checkScope($entityType, 'read')) continue;
@@ -1771,8 +1779,7 @@ class Activities implements
]
], $selectParams);
}
$sql = $this->getEntityManager()->getQuery()->createSelectQuery($entityType, $selectParams);
$sql = $this->getEntityManager()->getQueryComposer()->createSelectQuery($entityType, $selectParams);
$unionPartList[] = '' . $sql . '';
}
@@ -1817,7 +1824,7 @@ class Activities implements
return [
'total' => $totalCount,
'list' => $entityDataList
'list' => $entityDataList,
];
}
}
@@ -160,7 +160,7 @@ class Campaign extends \Espo\Services\Record implements
$this->getEntityManager()->getRepository('Opportunity')->handleSelectParams($params);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Opportunity', $params);
$sql = $this->getEntityManager()->getQueryComposer()->createSelectQuery('Opportunity', $params);
$pdo = $this->getEntityManager()->getPDO();
$sth = $pdo->prepare($sql);
@@ -86,21 +86,21 @@ class MassEmail extends \Espo\Services\Record implements
{
parent::afterDeleteEntity($massEmail);
$selectParams = $this->getEntityManager()->createSelectBuilder()
$delete = $this->getEntityManager()->getQueryBuilder()
->delete()
->from('EmailQueueItem')
->where([
'massEmailId' => $massEmail->id,
])
->build();
$sql = $this->getEntityManager()->getQuery()->createDeleteQuery('EmailQueueItem', $selectParams->getRaw());
$this->getEntityManager()->runQuery($sql);
$this->getEntityManager()->getQueryExecutor()->run($delete);
}
protected function cleanupQueueItems(Entity $massEmail)
{
$selectParams = $this->getEntityManager()->createSelectBuilder()
$delete = $this->getEntityManager()->getQueryBuilder()
->delete()
->from('EmailQueueItem')
->where([
'massEmailId' => $massEmail->id,
@@ -108,9 +108,7 @@ class MassEmail extends \Espo\Services\Record implements
])
->build();
$sql = $this->getEntityManager()->getQuery()->createDeleteQuery('EmailQueueItem', $selectParams->getRaw());
$this->getEntityManager()->runQuery($sql);
$this->getEntityManager()->getQueryExecutor()->run($delete);
}
public function createQueue(Entity $massEmail, bool $isTest = false, $additionalTargetList = [])
@@ -99,7 +99,7 @@ class Opportunity extends \Espo\Services\Record
$this->getEntityManager()->getRepository('Opportunity')->handleSelectParams($selectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Opportunity', $selectParams);
$sql = $this->getEntityManager()->getQueryComposer()->createSelectQuery('Opportunity', $selectParams);
$sth = $pdo->prepare($sql);
$sth->execute();
@@ -176,7 +176,7 @@ class Opportunity extends \Espo\Services\Record
$this->getEntityManager()->getRepository('Opportunity')->handleSelectParams($selectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Opportunity', $selectParams);
$sql = $this->getEntityManager()->getQueryComposer()->createSelectQuery('Opportunity', $selectParams);
$sth = $pdo->prepare($sql);
$sth->execute();
@@ -234,7 +234,7 @@ class Opportunity extends \Espo\Services\Record
$this->getEntityManager()->getRepository('Opportunity')->handleSelectParams($selectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Opportunity', $selectParams);
$sql = $this->getEntityManager()->getQueryComposer()->createSelectQuery('Opportunity', $selectParams);
$sth = $pdo->prepare($sql);
$sth->execute();
@@ -294,7 +294,7 @@ class Opportunity extends \Espo\Services\Record
$this->getEntityManager()->getRepository('Opportunity')->handleSelectParams($selectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Opportunity', $selectParams);
$sql = $this->getEntityManager()->getQueryComposer()->createSelectQuery('Opportunity', $selectParams);
$sth = $pdo->prepare($sql);
$sth->execute();
@@ -111,7 +111,7 @@ class TargetList extends \Espo\Services\Record
foreach ($this->targetsLinkList as $link) {
$foreignEntityType = $entity->getRelationParam($link, 'entity');
$count += $this->getEntityManager()->getRepository($foreignEntityType)->join(['targetLists'])->where([
$count += $this->getEntityManager()->getRepository($foreignEntityType)->join('targetLists')->where([
'targetListsMiddle.targetListId' => $entity->id,
'targetListsMiddle.optedOut' => 1,
])->count();
@@ -209,7 +209,7 @@ class TargetList extends \Espo\Services\Record
}
$pdo = $this->getEntityManager()->getPDO();
$query = $this->getEntityManager()->getQuery();
$query = $this->getEntityManager()->getQueryComposer();
$sql = null;
switch ($link) {
@@ -237,7 +237,7 @@ class TargetList extends \Espo\Services\Record
protected function findLinkedOptedOut(string $id, array $params) : RecordCollection
{
$pdo = $this->getEntityManager()->getPDO();
$query = $this->getEntityManager()->getQuery();
$query = $this->getEntityManager()->getQueryComposer();
$sqlContact = $query->createSelectQuery('Contact', array(
'select' => ['id', 'name', 'createdAt', ['VALUE:Contact', '_scope']],
+3 -1
View File
@@ -29,10 +29,12 @@
namespace Espo\ORM;
use Traversable;
/**
* A collection of entities.
*/
interface Collection extends \Traversable
interface Collection extends Traversable
{
/**
* Get an array of StdClass objects.
@@ -29,12 +29,14 @@
namespace Espo\ORM;
use Espo\ORM\{
QueryParams\Select,
};
/**
* Executes queries by a given RDBSelect instances.
*
* @todo Add `select` method returning an array of StdClass objects.
* Creates collections.
*/
class RDBQueryExecutor
class CollectionFactory
{
protected $entityManager;
@@ -43,20 +45,23 @@ class RDBQueryExecutor
$this->entityManager = $entityManager;
}
public function update(RDBSelect $select, array $values)
public function create(?string $entityType = null, array $data = []) : EntityCollection
{
$params = $select->getRawParams();
$params['update'] = $values;
$sql = $this->entityManager->getQuery()->createUpdateQuery($select->getEntityType(), $params);
$this->entityManager->runQuery($sql, true);
return new EntityCollection($data, $entityType, $this->entityManager->getEntityFactory());
}
public function delete(RDBSelect $select)
public function createFromSql(string $entityType, string $sql) : SthCollection
{
$sql = $this->entityManager->getQuery()->createDeleteQuery($select->getEntityType(), $select->getRawParams());
return SthCollection::fromSql($entityType, $sql, $this->entityManager);
}
$this->entityManager->runQuery($sql, true);
public function createFromQuery(Select $query) : SthCollection
{
return SthCollection::fromQuery($query, $this->entityManager);
}
public function createFromSthCollection(SthCollection $sthCollection) : EntityCollection
{
return EntityCollection::fromSthCollection($sthCollection);
}
}
+1 -1
View File
@@ -32,7 +32,7 @@ namespace Espo\ORM\DB\Query;
/**
* @deprecated
*/
abstract class Base extends BaseQuery
abstract class Base extends Espo\ORM\QueryComposer\BaseQueryComposer
{
}
+21 -1
View File
@@ -29,10 +29,15 @@
namespace Espo\ORM;
use Iterator;
use Countable;
use ArrayAccess;
use SeekableIterator;
/**
* A standard collection of entities. It allocates a memory for all entities.
*/
class EntityCollection implements \Iterator, \Countable, \ArrayAccess, \SeekableIterator, Collection
class EntityCollection implements Collection, Iterator, Countable, ArrayAccess, SeekableIterator
{
private $entityFactory = null;
@@ -300,4 +305,19 @@ class EntityCollection implements \Iterator, \Countable, \ArrayAccess, \Seekable
{
return $this->isFetched;
}
public static function fromSthCollection(SthCollection $sthCollection) : self
{
$entityList = [];
foreach ($sthCollection as $entity) {
$entityList[] = $entity;
}
$obj = new EntityCollection($entityList, $sthCollection->getEntityType());
$obj->setAsFetched();
return $obj;
}
}
+95 -45
View File
@@ -29,15 +29,17 @@
namespace Espo\ORM;
use Espo\Core\Exceptions\Error;
use Espo\ORM\DB\{
Mapper,
Query\BaseQuery as Query,
use Espo\ORM\{
Mapper\Mapper,
QueryComposer\QueryComposer,
Repository\RepositoryFactory,
Repository\Repository,
};
use PDO;
use PDOStatement;
use Exception;
use RuntimeException;
/**
* A central access point to ORM functionality.
@@ -50,6 +52,8 @@ class EntityManager
protected $entityFactory;
protected $collectionFactory;
protected $repositoryFactory;
protected $mappers = [];
@@ -60,7 +64,7 @@ class EntityManager
protected $params = [];
protected $query;
protected $queryComposer;
protected $defaultMapperName = 'RDB';
@@ -95,20 +99,43 @@ class EntityManager
$this->repositoryFactory = $repositoryFactory;
$this->queryExecutor = new RDBQueryExecutor($this);
$this->initQueryComposer();
$this->queryExecutor = new QueryExecutor($this);
$this->queryBuilder = new QueryBuilder();
$this->collectionFactory = new CollectionFactory($this);
}
protected function initQueryComposer()
{
$className = $this->params['queryComposerClassName'] ?? null;
if (!$className) {
$platform = $this->params['platform'];
$className = 'Espo\\ORM\\QueryComposer\\' . ucfirst($platform) . 'QueryComposer';
}
if (!$className || !class_exists($className)) {
throw new RuntimeException("Query composer {$name} could not be created.");
}
$this->queryComposer = new $className($this->getPDO(), $this->entityFactory, $this->metadata);
}
/**
* Get a Query.
* @todo Remove in v7.0.
* @deprecated
*/
public function getQuery() : Query
public function getQuery() : QueryComposer
{
if (empty($this->query)) {
$platform = $this->params['platform'];
$className = 'Espo\\ORM\\DB\\Query\\' . ucfirst($platform) . 'Query';
$this->query = new $className($this->getPDO(), $this->entityFactory, $this->metadata);
}
return $this->query;
return $this->queryComposer;
}
public function getQueryComposer() : QueryComposer
{
return $this->queryComposer;
}
protected function getMapperClassName(string $name) : string
@@ -118,12 +145,12 @@ class EntityManager
switch ($name) {
case 'RDB':
$platform = $this->params['platform'];
$className = 'Espo\\ORM\\DB\\' . ucfirst($platform) . 'Mapper';
$className = 'Espo\\ORM\\Mapper\\' . ucfirst($platform) . 'Mapper';
break;
}
if (!$className || !class_exists($className)) {
throw new Error("Mapper {$name} does not exist.");
throw new RuntimeException("Mapper '{$name}' does not exist.");
}
return $className;
@@ -140,7 +167,7 @@ class EntityManager
if (empty($this->mappers[$className])) {
$this->mappers[$className] = new $className(
$this->getPDO(), $this->entityFactory, $this->getQuery(), $this->metadata
$this->getPDO(), $this->entityFactory, $this->collectionFactory, $this->getQueryComposer(), $this->metadata
);
}
@@ -187,7 +214,7 @@ class EntityManager
public function getEntity(string $entityType, ?string $id = null) : ?Entity
{
if (!$this->hasRepository($entityType)) {
throw new Error("ORM: Repository '{$entityType}' does not exist.");
throw new RuntimeException("ORM: Repository '{$entityType}' does not exist.");
}
return $this->getRepository($entityType)->get($id);
@@ -214,7 +241,7 @@ class EntityManager
}
/**
* Create entity (store it in database).
* Create entity (store it in a database).
*
* @param StdClass|array $data Entity attributes.
*/
@@ -222,13 +249,14 @@ class EntityManager
{
$entity = $this->getEntity($entityType);
$entity->set($data);
$this->saveEntity($entity, $options);
return $entity;
}
/**
* Fetch an entity (from database).
* Fetch an entity (from a database).
*/
public function fetchEntity(string $entityType, string $id) : ?Entity
{
@@ -253,7 +281,7 @@ class EntityManager
public function getRepository(string $entityType) : ?Repository
{
if (!$this->hasRepository($entityType)) {
throw new Error("Repository '{$entityType}' does not exist.");
throw new RuntimeException("Repository '{$entityType}' does not exist.");
}
if (empty($this->repositoryHash[$entityType])) {
@@ -264,13 +292,17 @@ class EntityManager
}
/**
* Create a select builder.
* Get a query builder.
*/
public function createSelectBuilder() : RDBSelectBuilder
public function getQueryBuilder() : QueryBuilder
{
return new RDBSelectBuilder($this);
return $this->queryBuilder;
}
/**
* @deprecated
* @todo Remove.
*/
public function setMetadata(array $data)
{
$this->metadata->setData($data);
@@ -282,7 +314,7 @@ class EntityManager
}
/**
* Get an instance of PDO.
* Get a PDO instance.
*/
public function getPDO() : PDO
{
@@ -294,19 +326,11 @@ class EntityManager
}
/**
* Create a collection. Entity type can be omitted.
* Create a collection. An entity type can be omitted.
*/
public function createCollection(?string $entityType = null, array $data = []) : EntityCollection
{
return new EntityCollection($data, $entityType, $this->entityFactory);
}
/**
* Create an STH collection. An STH collection is preferable when a select query returns a large number of rows.
*/
public function createSthCollection(string $entityType, array $selectParams = []) : SthCollection
{
return new SthCollection($entityType, $this, $selectParams);
return $this->collectionFactory->create($data, $entityType);
}
public function getEntityFactory() : EntityFactory
@@ -314,30 +338,56 @@ class EntityManager
return $this->entityFactory;
}
public function getCollectionFactory() : CollectionFactory
{
return $this->collectionFactory;
}
/**
* Get a Query Executor.
*/
public function getQueryExecutor() : RDBQueryExecutor
public function getQueryExecutor() : QueryExecutor
{
return $this->queryExecutor;
}
/**
* Run a query. Returns a result.
* Run a Query.
*/
public function runQuery(Query $query) : PDOStatement
{
return $this->queryExecutor->run($query);
}
/**
* Run a SQL query.
*
* @param $rerunIfDeadlock Query will be re-run if a deadlock occurs.
*/
public function runQuery(string $query, bool $rerunIfDeadlock = false)
public function runSql(string $sql, bool $rerunIfDeadlock = false) : PDOStatement
{
$pdoStatement = null;
try {
return $this->getPDO()->query($query);
$pdoStatement = $this->getPDO()->query($sql);
} catch (Exception $e) {
if ($rerunIfDeadlock) {
if (isset($e->errorInfo) && $e->errorInfo[0] == 40001 && $e->errorInfo[1] == 1213) {
return $this->getPDO()->query($query);
}
if (!$rerunIfDeadlock) {
throw $e;
}
if (isset($e->errorInfo) && $e->errorInfo[0] == 40001 && $e->errorInfo[1] == 1213) {
$pdoStatement = $this->getPDO()->query($sql);
}
if (!$pdoStatement) {
throw $e;
}
throw $e;
}
if (!$pdoStatement) {
throw new RuntimeException("Query execution failure.");
}
return $pdoStatement;
}
}
File diff suppressed because it is too large Load Diff
@@ -27,7 +27,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\DB;
namespace Espo\ORM\Mapper;
use Espo\ORM\{
Entity,
@@ -27,45 +27,46 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\DB;
namespace Espo\ORM\Mapper;
use Espo\ORM\{
Entity,
Collection,
QueryParams\Select,
};
interface Mapper
{
/**
* Select an entity by ID.
* Get the first entity from DB.
*/
public function selectById(Entity $entity, string $id, ?array $params = null) : ?Entity;
public function selectOne(Select $select) : ?Entity;
/**
* Select a list of entities according to given parameters.
*/
public function select(Entity $entity, ?array $params = null) : Collection;
public function select(Select $select) : Collection;
/**
* Returns count of records according to given parameters.
*
* @return Record count.
*/
public function count(Entity $entity, ?array $params = null) : int;
public function count(Select $select) : int;
/**
* Selects related entity or list of entities.
*
* @return List of entities or one entity.
*/
public function selectRelated(Entity $entity, string $relationName, ?array $params = null);
public function selectRelated(Entity $entity, string $relationName, ?Select $select = null);
/**
* Returns count of related records according to given parameters.
*
* @return A number of records.
*/
public function countRelated(Entity $entity, string $relationName, ?array $params = null) : int;
public function countRelated(Entity $entity, string $relationName, ?Select $select = null) : int;
/**
* Insert an entity into DB.
@@ -27,7 +27,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\DB;
namespace Espo\ORM\Mapper;
class MysqlMapper extends BaseMapper
{
+45 -8
View File
@@ -29,8 +29,11 @@
namespace Espo\ORM;
use Espo\Core\Utils\Util;
use InvalidArgumentException;
/**
* Metadata of all entities.
*/
class Metadata
{
protected $data;
@@ -50,13 +53,17 @@ class Metadata
*/
public function get(string $entityType, $key = null, $default = null)
{
if (!array_key_exists($entityType, $this->data)) {
if (!$this->has($entityType)) {
return null;
}
$data = $this->data[$entityType];
if (!$key) return $data;
return Util::getValueByKey($data, $key, $default);
$data = $this->data[$entityType];
if ($key === null) {
return $data;
}
return self::getValueByKey($data, $key, $default);
}
/**
@@ -64,9 +71,39 @@ class Metadata
*/
public function has(string $entityType) : bool
{
if (!array_key_exists($entityType, $this->data)) {
return false;
return array_key_exists($entityType, $this->data);
}
private static function getValueByKey(array $data, $key = null, $default = null)
{
if (!is_string($key) && !is_array($key) && !is_null($key)) {
throw new InvalidArgumentException();
}
return true;
if (is_null($key) || empty($key)) {
return $data;
}
if (!is_string($key) && !is_array($key)) {
throw new InvalidArgumentException();
}
$path = $key;
if (is_string($key)) {
$path = explode('.', $key);
}
$item = $data;
foreach ($path as $k) {
if (!array_key_exists($k, $item)) {
return $default;
}
$item = $item[$k];
}
return $item;
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM;
use Espo\ORM\{
QueryParams\SelectBuilder,
QueryParams\UpdateBuilder,
QueryParams\DeleteBuilder,
QueryParams\InsertBuilder,
QueryParams\Query,
QueryParams\Builder,
};
use ReflectionClass;
use RuntimeException;
/**
* Creates query builders for specific query types.
*/
class QueryBuilder
{
public function __construct()
{
}
/**
* Proceed with SELECT builder.
*/
public function select() : SelectBuilder
{
return new SelectBuilder();
}
/**
* Proceed with UPDATE builder.
*/
public function update() : UpdateBuilder
{
return new UpdateBuilder();
}
/**
* Proceed with DELETE builder.
*/
public function delete() : DeleteBuilder
{
return new DeleteBuilder();
}
/**
* Proceed with INSERT builder.
*/
public function insert() : InsertBuilder
{
return new InsertBuilder();
}
/**
* Cone an existing query and proceed modifying it.
*/
public function clone(Query $query) : Builder
{
$class = new ReflectionClass($query);
$methodName = ucfirst($class->getShortName());
if (!method_exists($this, $methodName)) {
throw new RuntimeException("Can't clone an unsupported query.");
}
return $this->$methodName()->clone($query);
}
}
@@ -27,23 +27,31 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\DB\Query;
namespace Espo\ORM\QueryComposer;
use Espo\ORM\{
Entity,
EntityFactory,
Metadata,
DB\Helper,
DB\Query\Functions,
Mapper\Helper,
QueryParams\Query as Query,
QueryParams\Select as SelectQuery,
QueryParams\Update as UpdateQuery,
QueryParams\Insert as InsertQuery,
QueryParams\Delete as DeleteQuery,
};
use PDO;
use RuntimeException;
use LogicException;
/**
* Composes SQL queries.
*
* @todo Add method that wraps into ``.
* @todo Break into sub-classes. Put sub-classes into `\Parts` namespace.
*/
abstract class BaseQuery
abstract class BaseQueryComposer implements QueryComposer
{
protected static $paramList = [
'select',
@@ -67,7 +75,7 @@ abstract class BaseQuery
'maxTextColumnsLength',
'useIndex',
'withDeleted',
'update',
'set',
];
protected static $sqlOperators = [
@@ -162,24 +170,76 @@ abstract class BaseQuery
}
/**
* Compose a SELECT query.
* {@inheritdoc}
*/
public function createSelectQuery(string $entityType, ?array $params = nul) : string
public function compose(Query $query) : string
{
$params = $params ?? [];
if ($query instanceof SelectQuery) {
return $this->composeSelect($query);
}
return $this->createSelectQueryInternal($entityType, $params);
if ($query instanceof UpdateQuery) {
return $this->composeUpdate($query);
}
if ($query instanceof InsertQuery) {
return $this->composeInsert($query);
}
if ($query instanceof DeleteQuery) {
return $this->composeDelete($query);
}
throw new RuntimeException("ORM Query: Query params could not be handled.");
}
protected function composeSelect(SelectQuery $queryParam) : string
{
$params = $queryParam->getRawParams();
return $this->createSelectQueryInternal($params);
}
protected function composeUpdate(UpdateQuery $queryParam) : string
{
$params = $queryParam->getRawParams();
return $this->createUpdateQuery($params);
}
protected function composeDelete(DeleteQuery $queryParam) : string
{
$params = $queryParam->getRawParams();
return $this->createDeleteQuery($params);
}
protected function composeInsert(InsertQuery $queryParam) : string
{
$params = $queryParam->getRawParams();
return $this->createInsertQuery($params);
}
/**
* Compose a DELETE query.
* @deprecated
* @todo Remove in v6.4.
*/
public function createDeleteQuery(string $entityType, ?array $params = null) : string
public function createSelectQuery(string $entityType, ?array $params = null) : string
{
$params = $params ?? [];
$params['from'] = $entityType;
return $this->compose(SelectQuery::fromRaw($params));
}
protected function createDeleteQuery(array $params = null) : string
{
$params = $this->normilizeParams(self::DELETE_METHOD, $params);
$entityType = $params['from'];
$entity = $this->getSeed($entityType);
$wherePart = $this->getWherePart($entity, $params['whereClause'], 'AND', $params);
@@ -197,16 +257,13 @@ abstract class BaseQuery
return $sql;
}
/**
* Compose an UPDATE query.
*/
public function createUpdateQuery(string $entityType, ?array $params = null) : string
protected function createUpdateQuery(array $params = null) : string
{
$params = $params ?? [];
$params = $this->normilizeParams(self::UPDATE_METHOD, $params);
$values = $params['update'];
$entityType = $params['from'];
$values = $params['set'];
$entity = $this->getSeed($entityType);
@@ -228,13 +285,15 @@ abstract class BaseQuery
return $sql;
}
public function createInsertQuery(string $entityType, array $params) : string
protected function createInsertQuery(array $params) : string
{
$params = $this->normilizeInsertParams($params);
$entityType = $params['into'];
$columns = $params['columns'];
$values = $params['values'];
$update = $params['update'];
$updateSet = $params['updateSet'];
$valuesSelectParams = $params['valuesSelectParams'];
$columnsPart = $this->getInsertColumnsPart($columns);
@@ -243,8 +302,8 @@ abstract class BaseQuery
$updatePart = null;
if ($update) {
$updatePart = $this->getInsertUpdatePart($update);
if ($updateSet) {
$updatePart = $this->getInsertUpdatePart($updateSet);
}
return $this->composeInsertQuery($this->toDb($entityType), $columnsPart, $valuesPart, $updatePart);
@@ -330,10 +389,10 @@ abstract class BaseQuery
}
}
$update = $params['update'] = $params['update'] ?? null;
$updateSet = $params['updateSet'] = $params['updateSet'] ?? null;
if ($update && !is_array($update)) {
throw new RuntimeException("ORM Query: Bad 'update' param.");
if ($updateSet && !is_array($updateSet)) {
throw new RuntimeException("ORM Query: Bad 'updateSet' param.");
}
return $params;
@@ -365,24 +424,26 @@ abstract class BaseQuery
}
if ($method !== self::UPDATE_METHOD) {
if (isset($params['update'])) {
throw new RuntimeException("ORM Query: Param 'update' is not allowed for '{$method}'.");
if (isset($params['set'])) {
throw new RuntimeException("ORM Query: Param 'set' is not allowed for '{$method}'.");
}
}
if (isset($params['update']) && !is_array($params['update'])) {
throw new RuntimeException("ORM Query: Param 'update' should be an array.");
if (isset($params['set']) && !is_array($params['set'])) {
throw new RuntimeException("ORM Query: Param 'set' should be an array.");
}
return $params;
}
protected function createSelectQueryInternal(string $entityType, ?array $params = null) : string
protected function createSelectQueryInternal(array $params = null) : string
{
$entity = $this->getSeed($entityType);
$params = $this->normilizeParams(self::SELECT_METHOD, $params);
$entityType = $params['from'];
$entity = $this->getSeed($entityType);
$isAggregation = (bool) ($params['aggregation'] ?? null);
$whereClause = $params['whereClause'] ?? [];
@@ -405,9 +466,7 @@ abstract class BaseQuery
}
if (!$isAggregation) {
$selectPart = $this->getSelectPart(
$entity, $params['select'], $params['distinct'], $params['skipTextColumns'], $params['maxTextColumnsLength'], $params
);
$selectPart = $this->getSelectPart($entity, $params);
$orderPart = $this->getOrderPart($entity, $params['orderBy'], $params['order'], $params);
@@ -579,26 +638,14 @@ abstract class BaseQuery
}
}
if (count($extraSelect)) {
$extraSelectPart = $this->getSelectPart(
$entity, $extraSelect, false
);
$extraSelectPart = $this->getSelectPart($entity, ['select' => $extraSelect]);
if ($extraSelectPart) {
$selectPart .= ', ' . $extraSelectPart;
}
}
}
if (
!empty($params['additionalColumns']) && is_array($params['additionalColumns']) && !empty($params['relationName'])
) {
$alias = $this->sanitizeSelectAlias(lcfirst($params['relationName']));
foreach ($params['additionalColumns'] as $column => $field) {
$itemAlias = $this->sanitizeSelectAlias($field);
$selectPart .= ", " . $alias . "." . $this->toDb($this->sanitize($column)) . " AS `{$itemAlias}`";
}
}
if (!empty($params['additionalSelectColumns']) && is_array($params['additionalSelectColumns'])) {
foreach ($params['additionalSelectColumns'] as $column => $field) {
$itemAlias = $this->sanitizeSelectAlias($field);
@@ -623,7 +670,7 @@ abstract class BaseQuery
if (strpos($function, 'YEAR_') === 0 && $function !== 'YEAR_NUMBER') {
$fiscalShift = substr($function, 5);
if (is_numeric($fiscalShift)) {
$fiscalShift = intval($fiscalShift);
$fiscalShift = (int) $fiscalShift;
$fiscalFirstMonth = $fiscalShift + 1;
return
@@ -636,7 +683,7 @@ abstract class BaseQuery
if (strpos($function, 'QUARTER_') === 0 && $function !== 'QUARTER_NUMBER') {
$fiscalShift = substr($function, 8);
if (is_numeric($fiscalShift)) {
$fiscalShift = intval($fiscalShift);
$fiscalShift = (int) $fiscalShift;
$fiscalFirstMonth = $fiscalShift + 1;
$fiscalDistractedMonth = 12 - $fiscalFirstMonth;
@@ -694,10 +741,12 @@ abstract class BaseQuery
case 'DAY':
return "DATE_FORMAT({$part}, '%Y-%m-%d')";
case 'WEEK_0':
return "CONCAT(SUBSTRING(YEARWEEK({$part}, 6), 1, 4), '/', TRIM(LEADING '0' FROM SUBSTRING(YEARWEEK({$part}, 6), 5, 2)))";
return "CONCAT(SUBSTRING(YEARWEEK({$part}, 6), 1, 4), '/', ".
"TRIM(LEADING '0' FROM SUBSTRING(YEARWEEK({$part}, 6), 5, 2)))";
case 'WEEK':
case 'WEEK_1':
return "CONCAT(SUBSTRING(YEARWEEK({$part}, 3), 1, 4), '/', TRIM(LEADING '0' FROM SUBSTRING(YEARWEEK({$part}, 3), 5, 2)))";
return "CONCAT(SUBSTRING(YEARWEEK({$part}, 3), 1, 4), '/', ".
"TRIM(LEADING '0' FROM SUBSTRING(YEARWEEK({$part}, 3), 5, 2)))";
case 'QUARTER':
return "CONCAT(YEAR({$part}), '_', QUARTER({$part}))";
case 'MONTH_NUMBER':
@@ -763,7 +812,7 @@ abstract class BaseQuery
$offsetHoursString = substr($offsetHoursString, 1, -1);
}
$offset = floatval($offsetHoursString);
$offsetHours = intval(floor(abs($offset)));
$offsetHours = (int) (floor(abs($offset)));
$offsetMinutes = (abs($offset) - $offsetHours) * 60;
$offsetString =
str_pad((string) $offsetHours, 2, '0', \STR_PAD_LEFT) .
@@ -857,6 +906,7 @@ abstract class BaseQuery
$attribute = substr($attribute, 1, -1);
}
}
if (!empty($function)) {
$function = strtoupper($this->sanitize($function));
}
@@ -866,12 +916,13 @@ abstract class BaseQuery
if ($function && in_array($function, Functions::$multipleArgumentsFunctionList)) {
$arguments = $attribute;
$argumentList = $this->parseArgumentListFromFunctionContent($arguments);
$argumentList = self::parseArgumentListFromFunctionContent($arguments);
$argumentPartList = [];
foreach ($argumentList as $argument) {
$argumentPartList[] = $this->getFunctionArgumentPart($entity, $argument, $distinct, $params);
}
$part = implode(', ', $argumentPartList);
} else {
@@ -890,8 +941,9 @@ abstract class BaseQuery
return self::getAllAttributesFromComplexExpressionImplementation($expression);
}
protected static function getAllAttributesFromComplexExpressionImplementation(string $expression, ?array &$list = null) : array
{
protected static function getAllAttributesFromComplexExpressionImplementation(
string $expression, ?array &$list = null
) : array {
if (!$list) $list = [];
$arguments = $expression;
@@ -1117,6 +1169,7 @@ abstract class BaseQuery
$params['leftJoins'][] = $j;
}
}
if (!empty($fieldDefs[$type]['joins'])) {
foreach ($fieldDefs[$type]['joins'] as $j) {
$jAlias = $this->obtainJoinAlias($j);
@@ -1132,11 +1185,14 @@ abstract class BaseQuery
}
}
// Some fields may need additional select items add to a query.
if (!empty($fieldDefs[$type]['additionalSelect'])) {
$params['extraAdditionalSelect'] = $params['extraAdditionalSelect'] ?? [];
foreach ($fieldDefs[$type]['additionalSelect'] as $value) {
$value = str_replace('{alias}', $alias, $value);
$value = str_replace('{attribute}', $attribute, $value);
if (!in_array($value, $params['extraAdditionalSelect'])) {
$params['extraAdditionalSelect'][] = $value;
}
@@ -1147,115 +1203,170 @@ abstract class BaseQuery
return $part;
}
protected function getSelectPart(
Entity $entity,
?array $itemList = null,
bool $distinct = false,
bool $skipTextColumns = false,
?int $maxTextColumnsLength = null,
?array &$params = null
) : string {
$select = '';
$arr = [];
$specifiedList = is_array($itemList) ? true : false;
protected function getSelectPart(Entity $entity, array &$params) : string
{
$params = $params ?? [];
if (empty($itemList)) {
$attributeList = $entity->getAttributeList();
} else {
$attributeList = $itemList;
$itemList = $params['select'] ?? [];
$noSelectSpecified = !count($itemList);
if (!$noSelectSpecified && $itemList[0] === '*') {
array_shift($itemList);
foreach (array_reverse($entity->getAttributeList()) as $item) {
array_unshift($itemList, $item);
}
}
if ($params && isset($params['additionalSelect'])) {
if ($noSelectSpecified) {
$itemList = $entity->getAttributeList();
}
// @todo Get rid of 'additionalSelect' parameter.
if (isset($params['additionalSelect'])) {
foreach ($params['additionalSelect'] as $item) {
$attributeList[] = $item;
$itemList[] = $item;
}
}
foreach ($attributeList as $i => $attribute) {
if (is_string($attribute)) {
if (strpos($attribute, ':')) {
$attributeList[$i] = [
$attribute,
$attribute
];
foreach ($itemList as $i => $item) {
if (is_string($item)) {
if (strpos($item, ':')) {
$itemList[$i] = [$item, $item];
continue;
}
continue;
}
}
foreach ($attributeList as $attribute) {
$attributeType = null;
if (is_string($attribute)) {
$attributeType = $entity->getAttributeType($attribute);
}
if ($skipTextColumns) {
if ($attributeType === $entity::TEXT) {
continue;
}
$itemPairList = [];
foreach ($itemList as $item) {
$pair = $this->getSelectPartItemPair($entity, $params, $item);
if ($pair === null) {
continue;
}
if (is_array($attribute) && count($attribute) == 2) {
if (stripos($attribute[0], 'VALUE:') === 0) {
$part = substr($attribute[0], 6);
if ($part !== false) {
$part = $this->quote($part);
} else {
$part = $this->quote('');
}
$itemPairList[] = $pair;
}
if (!count($itemPairList)) {
throw new RuntimeException("ORM Query: Select part can't be empty.");
}
$selectPartItemList = [];
foreach ($itemPairList as $item) {
$expression = $item[0];
$alias = $this->sanitizeSelectAlias($item[1]);
if ($expression === '' || $alias === '') {
throw new RuntimeException("Bad select expression.");
}
$selectPartItemList[] = "{$expression} AS `{$alias}`";
}
$selectPart = implode(', ', $selectPartItemList);
return $selectPart;
}
protected function getSelectPartItemPair(Entity $entity, array &$params, $attribute) : ?array
{
$maxTextColumnsLength = $params['maxTextColumnsLength'] ?? null;
$skipTextColumns = $params['skipTextColumns'] ?? false;
$distinct = $params['distinct'] ?? false;
$attributeType = null;
if (!is_array($attribute) && !is_string($attribute)) {
throw new RuntimeException("ORM Query: Bad select item.");
}
if (is_string($attribute)) {
$attributeType = $entity->getAttributeType($attribute);
}
if ($skipTextColumns) {
if ($attributeType === $entity::TEXT) {
return null;
}
}
if (is_array($attribute) && count($attribute) == 2) {
$alias = $attribute[1];
if (stripos($attribute[0], 'VALUE:') === 0) {
$part = substr($attribute[0], 6);
if ($part !== false) {
$part = $this->quote($part);
} else {
if (!$entity->hasAttribute($attribute[0])) {
$part = $this->convertComplexExpression($entity, $attribute[0], $distinct, $params);
} else {
$fieldDefs = $entity->getAttributes()[$attribute[0]];
if (!empty($fieldDefs['select'])) {
$part = $this->getAttributeSql($entity, $attribute[0], 'select', $params);
} else {
if (!empty($fieldDefs['noSelect'])) {
continue;
}
if (!empty($fieldDefs['notStorable'])) {
continue;
}
$part = $this->getFieldPath($entity, $attribute[0], $params);
}
}
$part = $this->quote('');
}
$arr[] = $part . ' AS `' . $this->sanitizeSelectAlias($attribute[1]) . '`';
continue;
return [$part, $alias];
}
$attribute = $this->sanitizeSelectItem($attribute);
if (!$entity->hasAttribute($attribute[0])) {
$part = $this->convertComplexExpression($entity, $attribute[0], $distinct, $params);
if ($entity->hasAttribute($attribute)) {
$fieldDefs = $entity->getAttributes()[$attribute];
} else {
$part = $this->convertComplexExpression($entity, $attribute, $distinct, $params);
$arr[] = $part . ' AS `' . $this->sanitizeSelectAlias($attribute) . '`';
continue;
return [$part, $alias];
}
$fieldDefs = $entity->getAttributes()[$attribute[0]];
if (!empty($fieldDefs['select'])) {
$fieldPath = $this->getAttributeSql($entity, $attribute, 'select', $params);
} else {
if (!empty($fieldDefs['notStorable']) && ($fieldDefs['type'] ?? null) !== 'foreign') {
continue;
}
if ($attributeType === null) {
continue;
}
$fieldPath = $this->getFieldPath($entity, $attribute, $params);
if ($attributeType === $entity::TEXT && $maxTextColumnsLength !== null) {
$fieldPath = 'LEFT(' . $fieldPath . ', '. intval($maxTextColumnsLength) . ')';
}
$part = $this->getAttributeSql($entity, $attribute[0], 'select', $params);
return [$part, $alias];
}
$arr[] = $fieldPath . ' AS `' . $attribute . '`';
if (!empty($fieldDefs['noSelect'])) {
return null;
}
if (!empty($fieldDefs['notStorable'])) {
return null;
}
$part = $this->getAttributePath($entity, $attribute[0], $params);
return [$part, $alias];
}
$select = implode(', ', $arr);
if (!$entity->hasAttribute($attribute)) {
$expression = $this->sanitizeSelectItem($attribute);
return $select;
$part = $this->convertComplexExpression($entity, $expression, $distinct, $params);
return [$part, $attribute];
}
if ($entity->getAttributeParam($attribute, 'select')) {
$fieldPath = $this->getAttributeSql($entity, $attribute, 'select', $params);
return [$fieldPath, $attribute];
}
if ($attributeType === null) {
return null;
}
if ($entity->getAttributeParam($attribute, 'notStorable') && $attributeType !== Entity::FOREIGN) {
return null;
}
$fieldPath = $this->getAttributePath($entity, $attribute, $params);
if ($attributeType === Entity::TEXT && $maxTextColumnsLength !== null) {
$fieldPath = 'LEFT(' . $fieldPath . ', '. strval($maxTextColumnsLength) . ')';
}
return [$fieldPath, $attribute];
}
protected function getBelongsToJoinItemPart(Entity $entity, $relationName, $r = null, $alias = null)
@@ -1276,7 +1387,8 @@ abstract class BaseQuery
if ($alias) {
return "JOIN `" . $this->toDb($r['entity']) . "` AS `" . $alias . "` ON ".
$this->toDb($entity->getEntityType()) . "." . $this->toDb($key) . " = " . $alias . "." . $this->toDb($foreignKey);
$this->toDb($entity->getEntityType()) . "." . $this->toDb($key) . " = " .
$alias . "." . $this->toDb($foreignKey);
}
}
@@ -1362,7 +1474,7 @@ abstract class BaseQuery
if ($useColumnAlias) {
$fieldPath = '`'. $this->sanitizeSelectAlias($field) . '`';
} else {
$fieldPath = $this->getFieldPathForOrderBy($entity, $field, $params);
$fieldPath = $this->getAttributePathForOrderBy($entity, $field, $params);
}
$listQuoted = [];
$list = array_reverse(explode(',', $list));
@@ -1403,7 +1515,7 @@ abstract class BaseQuery
if ($useColumnAlias) {
$fieldPath = '`'. $this->sanitizeSelectAlias($orderBy) . '`';
} else {
$fieldPath = $this->getFieldPathForOrderBy($entity, $orderBy, $params);
$fieldPath = $this->getAttributePathForOrderBy($entity, $orderBy, $params);
}
return "{$fieldPath} " . $order;
@@ -1426,7 +1538,7 @@ abstract class BaseQuery
return $sql;
}
protected function getFieldPathForOrderBy(Entity $entity, string $orderBy, array $params) : ?string
protected function getAttributePathForOrderBy(Entity $entity, string $orderBy, array $params) : ?string
{
if (strpos($orderBy, '.') !== false || strpos($orderBy, ':') !== false) {
return $this->convertComplexExpression(
@@ -1437,7 +1549,7 @@ abstract class BaseQuery
);
}
return $this->getFieldPath($entity, $orderBy, $params);
return $this->getAttributePath($entity, $orderBy, $params);
}
protected function getAggregationSelectPart(Entity $entity, string $aggregation, string $aggregationBy, bool $distinct = false) : ?string
@@ -1461,6 +1573,7 @@ abstract class BaseQuery
/**
* Quote a value.
* @todo Make protected.
*/
public function quote($value) : string
{
@@ -1474,16 +1587,16 @@ abstract class BaseQuery
}
/**
* Convert a camelCase string to a corresponding representation for DB.
* {@inheritdoc)
*/
public function toDb(string $attribute) : string
public function toDb(string $string) : string
{
if (!array_key_exists($attribute, $this->attributeDbMapCache)) {
$attribute[0] = strtolower($attribute[0]);
$this->attributeDbMapCache[$attribute] = preg_replace_callback('/([A-Z])/', [$this, 'toDbMatchConversion'], $attribute);
if (!array_key_exists($string, $this->attributeDbMapCache)) {
$string[0] = strtolower($string[0]);
$this->attributeDbMapCache[$string] = preg_replace_callback('/([A-Z])/', [$this, 'toDbMatchConversion'], $string);
}
return $this->attributeDbMapCache[$attribute];
return $this->attributeDbMapCache[$string];
}
protected function toDbMatchConversion(array $matches) : string
@@ -1533,13 +1646,15 @@ abstract class BaseQuery
return $aliases;
}
protected function getFieldPath(Entity $entity, $field, &$params = null) : ?string
protected function getAttributePath(Entity $entity, string $attribute, ?array &$params = null) : ?string
{
if (!isset($entity->getAttributes()[$field])) {
if (!isset($entity->getAttributes()[$attribute])) {
return null;
}
$f = $entity->getAttributes()[$field];
$entityType = $entity->getEntityType();
$f = $entity->getAttributes()[$attribute];
$relationType = $f['type'];
@@ -1553,46 +1668,48 @@ abstract class BaseQuery
return null;
}
$fieldPath = '';
switch ($relationType) {
case 'foreign':
if (isset($f['relation'])) {
$relationName = $f['relation'];
$relationName = $f['relation'] ?? null;
$foreign = $f['foreign'];
if (is_array($foreign)) {
$wsCount = 0;
foreach ($foreign as $i => $value) {
if ($value == ' ') {
$foreign[$i] = '\' \'';
$wsCount ++;
} else {
$item = $this->getAlias($entity, $relationName) . '.' . $this->toDb($value);
$foreign[$i] = "IFNULL({$item}, '')";
}
}
$fieldPath = 'TRIM(CONCAT(' . implode(', ', $foreign). '))';
if ($wsCount > 1) {
$fieldPath = "REPLACE({$fieldPath}, ' ', ' ')";
}
$fieldPath = "NULLIF({$fieldPath}, '')";
} else {
$expression = $this->getAlias($entity, $relationName) . '.' . $foreign;
$fieldPath = $this->convertComplexExpression($entity, $expression, false, $params);
}
if (!$relationName) {
return null;
}
break;
default:
$fieldPath = $this->toDb($entity->getEntityType()) . '.' . $this->toDb($this->sanitize($field));
$relationName = $f['relation'];
$foreign = $f['foreign'];
if (is_array($foreign)) {
$wsCount = 0;
foreach ($foreign as $i => $value) {
if ($value == ' ') {
$foreign[$i] = '\' \'';
$wsCount ++;
} else {
$item = $this->getAlias($entity, $relationName) . '.' . $this->toDb($value);
$foreign[$i] = "IFNULL({$item}, '')";
}
}
$path = 'TRIM(CONCAT(' . implode(', ', $foreign). '))';
if ($wsCount > 1) {
$path = "REPLACE({$path}, ' ', ' ')";
}
$path = "NULLIF({$path}, '')";
} else {
$expression = $this->getAlias($entity, $relationName) . '.' . $foreign;
$path = $this->convertComplexExpression($entity, $expression, false, $params);
}
return $path;
}
return $fieldPath;
return $this->toDb($entityType) . '.' . $this->toDb($this->sanitize($attribute));
}
protected function getWherePart(
@@ -1777,7 +1894,7 @@ abstract class BaseQuery
$params
);
} else {
$leftPart = $this->getFieldPath($entity, $field, $params);
$leftPart = $this->getAttributePath($entity, $field, $params);
}
}
}
@@ -1864,10 +1981,7 @@ abstract class BaseQuery
return $joinAlias;
}
/**
* Convert a value to a string.
*/
public function stringifyValue($value) : string
protected function stringifyValue($value) : string
{
if (is_array($value)) {
$arr = [];
@@ -1892,6 +2006,7 @@ abstract class BaseQuery
/**
* Sanitize an alias for a SELECT statement.
* @todo Make protected?
*/
public function sanitizeSelectAlias(string $string) : string
{
@@ -2043,8 +2158,9 @@ abstract class BaseQuery
}
}
protected function getJoinItemPart(Entity $entity, $name, $isLeft = false, $conditions = [], $alias = null, array $params = [])
{
protected function getJoinItemPart(
Entity $entity, string $name, bool $isLeft = false, array $conditions = [], ?string $alias = null, array $params = []
) : string {
$prefix = ($isLeft) ? 'LEFT ' : '';
if (!$entity->hasRelation($name)) {
@@ -27,7 +27,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\DB\Query;
namespace Espo\ORM\QueryComposer;
class Functions
{
@@ -27,9 +27,9 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\DB\Query;
namespace Espo\ORM\QueryComposer;
class MysqlQuery extends BaseQuery
class MysqlQueryComposer extends BaseQueryComposer
{
public function limit(string $sql, ?int $offset = null, ?int $limit = null) : string
{
@@ -0,0 +1,57 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\QueryComposer;
use Espo\ORM\{
QueryParams\Query as Query,
};
interface QueryComposer
{
/**
* Compose a SQL query by a given query parameters.
*/
public function compose(Query $query) : string;
/**
* Convert a camelCase string to a corresponding representation for DB.
*/
public function toDb(string $string) : string;
/**
* Sanitize a string.
*/
public function sanitize(string $string) : string;
/**
* Sanitize an alias for a SELECT statement.
*/
public function sanitizeSelectAlias(string $string);
}
+56
View File
@@ -0,0 +1,56 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM;
use Espo\ORM\{
QueryParams\Query,
};
use PDOStatement;
/**
* Runs queries by given query params instances.
*/
class QueryExecutor
{
protected $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function run(Query $query) : PDOStatement
{
$sql = $this->entityManager->getQueryComposer()->compose($query);
return $this->entityManager->runSql($sql, true);
}
}
@@ -0,0 +1,56 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\QueryParams;
use RuntimeException;
trait BaseBuilderTrait
{
protected $params = [];
public function __construct()
{
}
protected function isEmpty() : bool
{
return empty($this->params);
}
protected function cloneInternal(Query $query)
{
if (!$this->isEmpty()) {
throw new RuntimeException("Clone can be called only on a new empty builder instance.");
}
$this->params = $query->getRawParams();
}
}
@@ -27,40 +27,35 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM;
namespace Espo\ORM\QueryParams;
/**
* Select parameters.
*/
class RDBSelect
trait BaseTrait
{
protected $entityType;
protected $params;
public function __construct(string $entityType, array $params)
{
$this->entityType = $entityType;
$this->params = $params;
}
/**
* Get an entity type.
*/
public function getEntityType() : string
{
return $this->entityType;
}
protected $params = [];
/**
* Get parameters in RAW format.
*/
public function getRawParams() : array
{
$params = $this->params;
return $this->params;
}
$params['from'] = $this->entityType
/**
* Create from RAW params.
*/
public static function fromRaw(array $params) : self
{
$obj = new self();
return $params;
$obj->validateRawParams($params);
$obj->params = $params;
return $obj;
}
protected function validateRawParams(array $params)
{
}
}
@@ -0,0 +1,49 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\QueryParams;
/**
* Builds query parameters.
* Builder instances are one-off, meaning that you need to instantiate it for every new building process.
*/
interface Builder
{
/**
* Build a query instance.
* @todo Uncomment when 7.4 is a min supported PHP version. Need the support of contravariant method parameters.
*/
//public function build() : Query;
/**
* Clone an existing query for a further modification and building.
* @todo Uncomment when 7.4 is a min supported PHP version. Need the support of contravariant method parameters.
*/
//public function clone(Query $query) : self;
}
@@ -0,0 +1,46 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\QueryParams;
use RuntimeException;
/**
* Delete parameters.
*/
class Delete implements Query
{
use SelectingTrait;
use BaseTrait;
protected function validateRawParams(array $params)
{
$this->validateRawParamsSelecting($params);
}
}
@@ -0,0 +1,63 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\QueryParams;
class DeleteBuilder implements Builder
{
use SelectingBuilderTrait;
/**
* Build a DELETE query.
*/
public function build() : Delete
{
return Delete::fromRaw($this->params);
}
/**
* Clone an existing query for a subsequent modifying and building.
*/
public function clone(Delete $query) : self
{
$this->cloneInternal($query);
return $this;
}
/**
* Apply LIMIT.
*/
public function limit(?int $limit = null) : self
{
$this->params['limit'] = $limit;
return $this;
}
}
@@ -0,0 +1,69 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\QueryParams;
use RuntimeException;
/**
* Insert parameters.
*/
class Insert implements Query
{
use BaseTrait;
protected function validateRawParams(array $params)
{
$into = $params['into'] ?? null;
if (!$into || !is_string($into)) {
throw new RuntimeException("Bad or missing 'into' parameter.");
}
$columns = $params['columns'] = $params['columns'] ?? [];
if (!is_array($columns)) {
throw new RuntimeException("Bad 'columns' parameter.");
}
$values = $params['values'] = $params['values'] ?? [];
$valuesSelectParams = $params['valuesSelectParams'] = $params['valuesSelectParams'] ?? null;
if (!is_array($values)) {
throw new RuntimeException("Bad 'values' parameter.");
}
$updateSet = $params['updateSet'] = $params['updateSet'] ?? null;
if ($updateSet && !is_array($updateSet)) {
throw new RuntimeException("Bad 'updateSet' parameter.");
}
}
}
@@ -0,0 +1,103 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\QueryParams;
class InsertBuilder implements Builder
{
use BaseBuilderTrait;
protected $params = [];
/**
* Build a INSERT query.
*/
public function build() : Insert
{
return Insert::fromRaw($this->params);
}
/**
* Clone an existing query for a subsequent modifying and building.
*/
public function clone(Insert $query) : self
{
$this->cloneInternal($query);
}
/**
* Into what entity type to insert.
*/
public function into(string $entityType) : self
{
$this->params['into'] = $entityType;
return $this;
}
/**
* What columns to set with values. A list of columns.
*/
public function columns(array $columns) : self
{
$this->params['columns'] = $columns;
return $this;
}
/**
* What values to insert. A key-value map or a list of key-value maps.
*/
public function values(array $values) : self
{
$this->params['values'] = $values;
return $this;
}
/**
* Values to set on duplicate key. A key-value map.
*/
public function updateSet(array $updateSet) : self
{
$this->params['updateSet'] = $updateSet;
return $this;
}
/**
* For a mass insert by a select sub-query.
*/
public function valuesQuery(Select $query) : self
{
$this->params['valuesSelectParams'] = $query->getRawParams();
return $this;
}
}
@@ -27,14 +27,15 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\Repositories;
namespace Espo\ORM\QueryParams;
use Espo\ORM\Entity;
interface Removable
/**
* Query parameters. Instances are immutable. Need to clone with a builder to get a copy for a further modification.
*/
interface Query
{
/**
* Remove a record (mark as deleted).
* Get parameters in RAW format.
*/
public function remove(Entity $entity);
public function getRawParams() : array;
}
@@ -0,0 +1,65 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\QueryParams;
/**
* Select parameters.
*
* @todo Add validation and normalization (from ORM\DB\BaseQuery).
*/
class Select implements Query
{
use SelectingTrait;
use BaseTrait;
const ORDER_ASC = 'ASC';
const ORDER_DESC = 'DESC';
/**
* @todo Remove?
*/
public function isSth() : bool
{
return $this->params['sth'] ?? false;
}
/**
* Get select items.
*/
public function getSelect() : array
{
return $this->params['select'] ?? [];
}
protected function validateRawParams(array $params)
{
$this->validateRawParamsSelecting($params);
}
}
@@ -0,0 +1,150 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\QueryParams;
use InvalidArgumentException;
class SelectBuilder implements Builder
{
use SelectingBuilderTrait;
/**
* Build a SELECT query.
*/
public function build() : Select
{
return Select::fromRaw($this->params);
}
/**
* Clone an existing query for a subsequent modifying and building.
*/
public function clone(Select $query) : self
{
$this->cloneInternal($query);
return $this;
}
/**
* Set to return STH collection. Recommended for fetching large number of records.
*
* @todo Remove.
*/
public function sth() : self
{
$this->params['returnSthCollection'] = true;
return $this;
}
/**
* Apply OFFSET and LIMIT.
*/
public function limit(?int $offset = null, ?int $limit = null) : self
{
$this->params['offset'] = $offset;
$this->params['limit'] = $limit;
return $this;
}
/**
* Specify SELECT. Columns and expressions to be selected. If not called, then all entity attributes will be selected.
* Passing an array will reset previously set items.
* Passing a string will append an item.
*
* Usage options:
* * `select([$item1, $item2, ...])`
* * `select(string $expression)`
* * `select(string $expression, string $alias)`
*
* @param array|string $select
*/
public function select($select, ?string $alias = null) : self
{
if (is_array($select)) {
$this->params['select'] = $select;
return $this;
}
if (is_string($select)) {
$this->params['select'] = $this->params['select'] ?? [];
if ($alias) {
$this->params['select'][] = [$select, $alias];
} else {
$this->params['select'][] = $select;
}
return $this;
}
throw new InvalidArgumentException();
}
/**
* Specify GROUP BY.
*/
public function groupBy(array $groupBy) : self
{
$this->params['groupBy'] = $groupBy;
return $this;
}
/**
* Add a HAVING clause.
*
* Two usage options:
* * `having(array $havingClause)`
* * `having(string $key, string $value)`
*
* @param array|string $keyOrClause
* @param ?array|string $value
*/
public function having($keyOrClause = [], $value = null) : self
{
$this->applyWhereClause('havingClause', $keyOrClause, $value);
return $this;
}
/**
* @todo Remove?
*/
public function withDeleted() : self
{
$this->params['withDeleted'] = true;
return $this;
}
}
@@ -0,0 +1,208 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\QueryParams;
trait SelectingBuilderTrait
{
use BaseBuilderTrait;
/**
* Set FROM parameter. For what entity type to build a query.
*/
public function from(string $entityType) : self
{
if (isset($this->params['from'])) {
throw new LogicException("Method 'from' can be called only once.");
}
$this->params['from'] = $entityType;
return $this;
}
/**
* Set DISTINCT parameter.
*/
public function distinct() : self
{
$this->params['distinct'] = true;
return $this;
}
/**
* Add a WHERE clause.
*
* Two usage options:
* * `where(array $whereClause)`
* * `where(string $key, string $value)`
*
* @param array|string $keyOrClause A key or where clause.
* @param ?array|string $value A value. If the first argument is an array, then should be omited.
*/
public function where($keyOrClause = [], $value = null) : self
{
$this->applyWhereClause('whereClause', $keyOrClause, $value);
return $this;
}
protected function applyWhereClause(string $type, $keyOrClause, $value)
{
$this->params[$type] = $this->params[$type] ?? [];
$original = $this->params[$type];
if (!is_string($keyOrClause) && !is_array($keyOrClause)) {
throw new InvalidArgumentException();
}
if (is_array($keyOrClause)) {
$new = $keyOrClause;
}
if (is_string($keyOrClause)) {
$new = [$keyOrClause => $value];
}
$containsSameKeys = (bool) count(
array_intersect(
array_keys($new),
array_keys($original)
)
);
if ($containsSameKeys) {
$this->params[$type][] = $new;
return $this;
}
$this->params[$type] = $new + $original;
return $this;
}
/**
* Apply ORDER.
*
* @param string|array $orderBy An attribute to order by or order definitions as an array.
* @param bool|string $direction 'ASC' or 'DESC'. TRUE for DESC order.
* If the first argument is an array then should be omitied.
*/
public function order($orderBy, $direction = Select::ORDER_ASC) : self
{
if (!$orderBy) {
throw InvalidArgumentException();
}
$this->params['orderBy'] = $orderBy;
$this->params['order'] = $direction;
return $this;
}
/**
* Add JOIN.
*
* @param string $relationName A relationName or table. A relationName is in camelCase, a table is in CamelCase.
*
*/
public function join($relationName, ?string $alias = null, ?array $conditions = null) : self
{
if (empty($this->params['joins'])) {
$this->params['joins'] = [];
}
if (is_array($relationName)) {
$joinList = $relationName;
foreach ($joinList as $item) {
$this->params['joins'][] = $item;
}
return $this;
}
if (is_null($alias) && is_null($conditions)) {
$this->params['joins'][] = $relationName;
return $this;
}
if (is_null($conditions)) {
$this->params['joins'][] = [$relationName, $alias];
return $this;
}
$this->params['joins'][] = [$relationName, $alias, $conditions];
return $this;
}
/**
* Add LEFT JOIN.
*
* @param string $relationName A relationName or table. A relationName is in camelCase, a table is in CamelCase.
*/
public function leftJoin($relationName, ?string $alias = null, ?array $conditions = null) : self
{
if (empty($this->params['leftJoins'])) {
$this->params['leftJoins'] = [];
}
if (is_array($relationName)) {
$joinList = $relationName;
foreach ($joinList as $item) {
$this->params['leftJoins'][] = $item;
}
return $this;
}
if (is_null($alias) && is_null($conditions)) {
$this->params['leftJoins'][] = $relationName;
return $this;
}
if (is_null($conditions)) {
$this->params['leftJoins'][] = [$relationName, $alias];
return $this;
}
$this->params['leftJoins'][] = [$relationName, $alias, $conditions];
return $this;
}
}
@@ -27,27 +27,26 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\Repositories;
namespace Espo\ORM\QueryParams;
use Espo\ORM\{
Entity,
Collection,
};
use RuntimeException;
interface Findable
trait SelectingTrait
{
/**
* A number of records matching specific parameters.
* Get an entity type.
*/
public function count(array $params) : int;
public function getFrom() : string
{
return $this->params['from'];
}
/**
* Find records matching specific parameters.
*/
public function find(array $params) : Collection;
protected static function validateRawParamsSelecting(array $params)
{
$from = $params['from'] ?? null;
/**
* Find the first record matching specific parameters.
*/
public function findOne(array $params) : ?Entity;
if (!$from || !is_string($from)) {
throw new RuntimeException("Select params: Missing 'from'.");
}
}
}
@@ -0,0 +1,52 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\QueryParams;
use RuntimeException;
/**
* Update parameters.
*/
class Update implements Query
{
use SelectingTrait;
use BaseTrait;
protected function validateRawParams(array $params)
{
$this->validateRawParamsSelecting($params);
$set = $params['set'] ?? null;
if (!$set || !is_array($set)) {
throw new RuntimeException("Update params: Bad or missing 'set' parameter.");
}
}
}
@@ -27,37 +27,46 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM;
namespace Espo\ORM\QueryParams;
use Espo\ORM\DB\Query\BaseQuery as Query;
use LogicException;
use PDO;
class Sth2Collection extends SthCollection
class UpdateBuilder implements Builder
{
public function __construct(
string $entityType, EntityFactory $entityFactory, Query $query, PDO $pdo, array $selectParams = []
) {
$this->selectParams = $selectParams;
$this->entityType = $entityType;
use SelectingBuilderTrait;
$this->entityFactory = $entityFactory;
$this->query = $query;
$this->pdo = $pdo;
/**
* Build a UPDATE query.
*/
public function build() : Update
{
return Update::fromRaw($this->params);
}
protected function getQuery()
/**
* Clone an existing query for a subsequent modifying and building.
*/
public function clone(Delete $query) : self
{
return $this->query;
$this->cloneInternal($query);
return $this;
}
protected function getPdo()
public function set(array $set) : self
{
return $this->pdo;
$this->params['set'] = $set;
return $this;
}
protected function getEntityFactory()
/**
* Apply LIMIT.
*/
public function limit(?int $limit = null) : self
{
return $this->entityFactory;
$this->params['limit'] = $limit;
return $this;
}
}
-374
View File
@@ -1,374 +0,0 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM;
use Espo\Core\Exceptions\Error;
use Espo\ORM\{
Repositories\Findable,
Collection,
Entity,
};
/**
* Builds select parameters for an RDB repository and invokes findable methods.
*/
class RDBSelectBuilder implements Findable
{
protected $whereClause = [];
protected $havingClause = [];
protected $params = [];
protected $entityManager;
protected $repository;
protected $entityType = null;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function from(string $entityType) : self
{
if ($this->repository) {
throw new Error("SelectBuilder: Method 'from' can be called only once.");
}
$this->entityType = $entityType;
$this->repository = $this->entityManager->getRepository($entityType);
return $this;
}
protected function isExecutable() : bool
{
return (bool) $this->entityType;
}
protected function processExecutableCheck()
{
if (!$this->isExecutable()) {
throw new Error("SelectBuilder: Method 'from' must be called.");
}
}
public function find(array $params = []) : Collection
{
$this->processExecutableCheck();
$params = $this->getMergedParams($params);
return $this->repository->find($params);
}
public function findOne(array $params = []) : ?Entity
{
$this->processExecutableCheck();
$params = $this->getMergedParams($params);
return $this->repository->findOne($params);
}
public function count(array $params = []) : int
{
$this->processExecutableCheck();
$params = $this->getMergedParams($params);
return $this->repository->count($params);
}
public function max(string $attribute)
{
$this->processExecutableCheck();
$params = $this->getMergedParams();
return $this->repository->max($attribute, $params);
}
public function min(string $attribute)
{
$this->processExecutableCheck();
$params = $this->getMergedParams();
return $this->repository->min($attribute, $params);
}
public function sum(string $attribute)
{
$this->processExecutableCheck();
$params = $this->getMergedParams();
return $this->repository->sum($attribute, $params);
}
/**
* Add JOIN.
*
* @param string|array $relationName A relationName or table. A relationName is in camelCase, a table is in CamelCase.
*
* Usage options:
* * `join(string $relationName)`
* * `join(array $joinDefinitionList)`
*
* Usage examples:
* ```
* ->join($relationName)
* ->join($relationName, $alias, $conditions)
* ->join([$relationName1, $relationName2, ...])
* ->join([[$relationName, $alias], ...])
* ->join([[$relationName, $alias, $conditions], ...])
* ```
*/
public function join($relationName, ?string $alias = null, ?array $conditions = null) : self
{
if (empty($this->params['joins'])) {
$this->params['joins'] = [];
}
if (is_array($relationName)) {
$joinList = $relationName;
foreach ($joinList as $item) {
$this->params['joins'][] = $item;
}
return $this;
}
if (is_null($alias) && is_null($conditions)) {
$this->params['joins'][] = $relationName;
return $this;
}
if (is_null($conditions)) {
$this->params['joins'][] = [$relationName, $alias];
return $this;
}
$this->params['joins'][] = [$relationName, $alias, $conditions];
return $this;
}
/**
* Add LEFT JOIN.
*
* @param string|array $relationName A relationName or table. A relationName is in camelCase, a table is in CamelCase.
*
* This method works the same way as `join` method.
*/
public function leftJoin($relationName, ?string $alias = null, ?array $conditions = null) : self
{
if (empty($this->params['leftJoins'])) {
$this->params['leftJoins'] = [];
}
if (is_array($relationName)) {
$joinList = $relationName;
foreach ($joinList as $item) {
$this->params['leftJoins'][] = $item;
}
return $this;
}
if (is_null($alias) && is_null($conditions)) {
$this->params['leftJoins'][] = $relationName;
return $this;
}
if (is_null($conditions)) {
$this->params['leftJoins'][] = [$relationName, $alias];
return $this;
}
$this->params['leftJoins'][] = [$relationName, $alias, $conditions];
return $this;
}
/**
* Set DISTINCT parameter.
*/
public function distinct() : self
{
$this->params['distinct'] = true;
return $this;
}
/**
* Set to return STH collection. Recommended fetching large number of records.
*/
public function sth() : self
{
$this->params['returnSthCollection'] = true;
return $this;
}
/**
* Add a WHERE clause.
*
* Two usage options:
* * `where(array $whereClause)`
* * `where(string $key, string $value)`
*/
public function where($param1 = [], $param2 = null) : self
{
if (is_array($param1)) {
$this->whereClause = $param1 + $this->whereClause;
} else {
if (!is_null($param2)) {
$this->whereClause[] = [$param1 => $param2];
}
}
return $this;
}
/**
* Add a HAVING clause.
*
* Two usage options:
* * `having(array $havingClause)`
* * `having(string $key, string $value)`
*/
public function having($param1 = [], $param2 = null) : self
{
if (is_array($param1)) {
$this->havingClause = $param1 + $this->havingClause;
} else {
if (!is_null($param2)) {
$this->havingClause[] = [$param1 => $param2];
}
}
return $this;
}
/**
* Apply ORDER.
*
* @param string|array $attribute An attribute to order by or order definitions as an array.
* @param bool|string $direction TRUE for DESC order.
*/
public function order($attribute = 'id', $direction = 'ASC') : self
{
$this->params['orderBy'] = $attribute;
$this->params['order'] = $direction;
return $this;
}
/**
* Apply OFFSET and LIMIT.
*/
public function limit(?int $offset = null, ?int $limit = null) : self
{
$this->params['offset'] = $offset;
$this->params['limit'] = $limit;
return $this;
}
/**
* Specify SELECT. Which attributes to select. All attributes are selected by default.
*/
public function select(array $select) : self
{
$this->params['select'] = $select;
return $this;
}
/**
* Specify GROUP BY.
*/
public function groupBy(array $groupBy) : self
{
$this->params['groupBy'] = $groupBy;
return $this;
}
/**
* Builds result select parameters.
*/
public function build() : RDBSelect
{
$this->processExecutableCheck();
return new RDBSelect($this->entityType, $this->getMergedParams());
}
protected function getMergedParams(array $params = []) : array
{
if (isset($params['whereClause'])) {
$params['whereClause'] = $params['whereClause'];
if (!empty($this->whereClause)) {
$params['whereClause'][] = $this->whereClause;
}
} else {
$params['whereClause'] = $this->whereClause;
}
if (!empty($params['havingClause'])) {
$params['havingClause'] = $params['havingClause'];
if (!empty($this->havingClause)) {
$params['havingClause'][] = $this->havingClause;
}
} else {
$params['havingClause'] = $this->havingClause;
}
if (empty($params['whereClause'])) {
unset($params['whereClause']);
}
if (empty($params['havingClause'])) {
unset($params['havingClause']);
}
if (!empty($params['leftJoins']) && !empty($this->params['leftJoins'])) {
foreach ($this->params['leftJoins'] as $j) {
$params['leftJoins'][] = $j;
}
}
if (!empty($params['joins']) && !empty($this->params['joins'])) {
foreach ($this->params['joins'] as $j) {
$params['joins'][] = $j;
}
}
$params = array_replace_recursive($this->params, $params);
return $params;
}
}
@@ -0,0 +1,68 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\Repository;
use Espo\ORM\{
Entity,
QueryParams\Select,
};
class EmptyHookMediator implements HookMediator
{
public function beforeSave(Entity $entity, array $options)
{}
public function afterSave(Entity $entity, array $options)
{}
public function beforeRemove(Entity $entity, array $options)
{}
public function afterRemove(Entity $entity, array $options)
{}
public function beforeRelate(Entity $entity, string $relationName, Entity $foreignEntity, ?array $columnData, array $options)
{}
public function afterRelate(Entity $entity, string $relationName, Entity $foreignEntity, ?array $columnData, array $options)
{}
public function beforeUnrelate(Entity $entity, string $relationName, Entity $foreignEntity, array $options)
{}
public function afterUnrelate(Entity $entity, string $relationName, Entity $foreignEntity, array $options)
{}
public function beforeMassRelate(Entity $entity, string $relationName, Select $query, array $options)
{}
public function afterMassRelate(Entity $entity, string $relationName, Select $query, array $options)
{}
}
@@ -27,34 +27,32 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\Repositories;
namespace Espo\ORM\Repository;
use Espo\ORM\Entity;
use Espo\ORM\{
Entity,
QueryParams\Select,
};
interface Relatable
interface HookMediator
{
/**
* Find records records matching specific parameters.
*/
public function findRelated(Entity $entity, string $relationName, array $params);
public function beforeSave(Entity $entity, array $options);
/**
* A number of related records matching specific parameters.
*/
public function countRelated(Entity $entity, string $relationName, array $params = []) : int;
public function afterSave(Entity $entity, array $options);
/**
* Whether records are related.
*/
public function isRelated(Entity $entity, string $relationName, $foreign) : bool;
public function beforeRemove(Entity $entity, array $options);
/**
* Relate records.
*/
public function relate(Entity $entity, string $relationName, $foreign);
public function afterRemove(Entity $entity, array $options);
/**
* Unrelate records.
*/
public function unrelate(Entity $entity, string $relationName, $foreign);
public function beforeRelate(Entity $entity, string $relationName, Entity $foreignEntity, ?array $columnData, array $options);
public function afterRelate(Entity $entity, string $relationName, Entity $foreignEntity, ?array $columnData, array $options);
public function beforeUnrelate(Entity $entity, string $relationName, Entity $foreignEntity, array $options);
public function afterUnrelate(Entity $entity, string $relationName, Entity $foreignEntity, array $options);
public function beforeMassRelate(Entity $entity, string $relationName, Select $query, array $options);
public function afterMassRelate(Entity $entity, string $relationName, Select $query, array $options);
}
@@ -0,0 +1,458 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\Repository;
use Espo\ORM\{
Collection,
Entity,
EntityManager,
QueryParams\Select,
QueryParams\SelectBuilder,
Mapper\Mapper,
};
use RuntimeException;
use BadMethodCallException;
use StdClass;
/**
* An access point for a specific relation of a record.
*/
class RDBRelation
{
protected $entityManager;
protected $hookMediator;
protected $entity;
protected $entityType;
protected $foreignEntityType = null;
protected $relationName;
protected $relationType = null;
protected $builder = null;
protected $noBuilder = false;
public function __construct(EntityManager $entityManager, Entity $entity, string $relationName, HookMediator $hookMediator)
{
$this->entityManager = $entityManager;
$this->entity = $entity;
$this->hookMediator = $hookMediator;
if (!$entity->get('id')) {
throw new RuntimeException("Can't use an entity w/o ID.");
}
if (!$entity->hasRelation($relationName)) {
throw new RuntimeException("Entity does not have a relation '{$relationName}'.");
}
$this->relationName = $relationName;
$this->relationType = $entity->getRelationType($relationName);
$this->foreignEntityType = $entity->getRelationParam($relationName, 'entity');
$this->entityType = $entity->getEntityType();
if ($this->isBelongsToParentType()) {
$this->noBuilder = true;
}
}
protected function createBuilder(?Select $query = null) : RDBRelationSelectBuilder
{
if ($this->noBuilder) {
throw new RuntimeException("Can't use query builder for the '{$this->relationType}' relation type.");
}
return new RDBRelationSelectBuilder($this->entityManager, $this->entity, $this->relationName, $query);
}
/**
* Clone a query.
*/
public function clone(Select $query) : RDBRelationSelectBuilder
{
if ($this->noBuilder) {
throw new RuntimeException("Can't use clone for the '{$this->relationType}' relation type.");
}
if ($query->getFrom() !== $this->foreignEntityType) {
throw new RuntimeException("Passed query doesn't match the entity type.");
}
return $this->createBuilder($query);
}
protected function isBelongsToParentType() : bool
{
return $this->relationType === Entity::BELONGS_TO_PARENT;
}
protected function getMapper() : Mapper
{
return $this->entityManager->getMapper();
}
/**
* Find related records.
*/
public function find() : Collection
{
if ($this->isBelongsToParentType()) {
$collection = $this->entityManager->getCollectionFactory()->create();
$entity = $this->getMapper()->selectRelated($this->entity, $this->relationName);
if ($entity) {
$collection[] = $entity;
}
$collection->setAsFetched();
return $collection;
}
return $this->createBuilder()->find();
}
/**
* Find a first record.
*/
public function findOne() : ?Entity
{
if ($this->isBelongsToParentType()) {
return $this->getMapper()->selectRelated($this->entity, $this->relationName);
}
$collection = $this->limit(0, 1)->find();
if (!count($collection)) {
return null;
}
foreach ($collection as $entity) {
return $entity;
}
}
/**
* Get a number of related records.
*/
public function count() : int
{
return $this->createBuilder()->count();
}
/**
* Add JOIN.
*
* @see Espo\ORM\QueryParams\SelectBuilder::join()
*/
public function join(string $relationName, ?string $alias = null, ?array $conditions = null) : RDBRelationSelectBuilder
{
return $this->createBuilder()->join($relationName, $alias, $conditions);
}
/**
* Add LEFT JOIN.
*
* @see Espo\ORM\QueryParams\SelectBuilder::leftJoin()
*/
public function leftJoin(string $relationName, ?string $alias = null, ?array $conditions = null) : RDBRelationSelectBuilder
{
return $this->createBuilder()->leftJoin($relationName, $alias, $conditions);
}
/**
* Set DISTINCT parameter.
*/
public function distinct() : RDBRelationSelectBuilder
{
return $this->createBuilder()->distinct();
}
/**
* Set to return STH collection. Recommended for fetching large number of records.
*
* @todo Remove.
*/
public function sth() : RDBRelationSelectBuilder
{
return $this->createBuilder()->sth();
}
/**
* Add a WHERE clause.
*
* @see Espo\ORM\QueryParams\SelectBuilder::where()
*
* @param array|string $keyOrClause
* @param ?array|string $value
*/
public function where($keyOrClause = [], $value = null) : RDBRelationSelectBuilder
{
return $this->createBuilder()->where($keyOrClause, $value);
}
/**
* Add a HAVING clause.
*
* @see Espo\ORM\QueryParams\SelectBuilder::having()
*
* @param array|string $keyOrClause
* @param ?array|string $value
*/
public function having($keyOrClause = [], $value = null) : RDBRelationSelectBuilder
{
return $this->createBuilder()->having($keyOrClause, $params2);
}
/**
* Apply ORDER.
*
* @see Espo\ORM\QueryParams\SelectBuilder::order()
*
* @param string|array $orderBy
* @param bool|string $direction
*/
public function order($orderBy, $direction = 'ASC') : RDBRelationSelectBuilder
{
return $this->createBuilder()->order($orderBy, $direction);
}
/**
* Apply OFFSET and LIMIT.
*/
public function limit(?int $offset = null, ?int $limit = null) : RDBRelationSelectBuilder
{
return $this->createBuilder()->limit($offset, $limit);
}
/**
* Specify SELECT. Which attributes to select. All attributes are selected by default.
*
* @see Espo\ORM\QueryParams\SelectBuilder::select()
*
* @param array|string $select
*/
public function select($select, ?string $alias = null) : RDBRelationSelectBuilder
{
return $this->createBuilder()->select($select, $alias);
}
/**
* Specify GROUP BY.
*/
public function groupBy(array $groupBy) : RDBRelationSelectBuilder
{
return $this->createBuilder()->groupBy($groupBy);
}
/**
* @deprecated Use `->select('linkNameMiddle', 'attributeName')` instead.
*/
public function columns(array $columns) : RDBRelationSelectBuilder
{
return $this->createBuilder()->columns($columns);
}
/**
* Apply middle table conditions for a many-to-many relationship.
*
* @see Espo\ORM\Repository\RDBRelationSelectBuilder::columnsWhere()
*/
public function columnsWhere(array $where) : RDBRelationSelectBuilder
{
return $this->createBuilder()->columnsWhere($where);
}
protected function processCheckForeignEntity(Entity $entity)
{
if ($this->foreignEntityType && $this->foreignEntityType !== $entity->getEntityType()) {
throw new InvalidArgumentException("Entity type doesn't match an entity type of the relation.");
}
if (!$entity->id) {
throw new RuntimeException("Can't use an entity w/o ID.");
}
}
public function isRelated(Entity $entity) : bool
{
if (!$entity->id) {
throw new RuntimeException("Can't use an entity w/o ID.");
}
if ($this->isBelongsToParentType()) {
return $this->isRelatedBelongsToParent($entity);
}
$this->processCheckForeignEntity($entity);
return (bool) $this->createBuilder()
->select(['id'])
->where(['id' => $entity->id])
->findOne();
}
protected function isRelatedBelongsToParent(Entity $entity) : bool
{
$fromEntity = $this->entity;
$idAttribute = $this->relationName . 'Id';
$typeAttribute = $this->relationName . 'Type';
if (!$fromEntity->has($idAttribute) || !$fromEntity->has($typeAttribute)) {
$fromEntity = $this->entityManager->getEntity($fromEntity->getEntityType(), $fromEntity->id);
}
if (!$fromEntity) {
return false;
}
return
$fromEntity->get($idAttribute) === $entity->id
&&
$fromEntity->get($typeAttribute) === $entity->getEntityType();
}
/**
* Relate with an entity.
*/
public function relate(Entity $entity, ?array $columnData = null, array $options = [])
{
$this->processCheckForeignEntity($entity);
$this->beforeRelate($entity, $columnData, $options);
$result = $this->getMapper()->relateById($this->entity, $this->relationName, $entity->id, $columnData);
if (!$result) {
return;
}
$this->afterRelate($entity, $columnData, $options);
}
/**
* Unrelate from an entity.
*/
public function unrelate(Entity $entity, array $options = [])
{
$this->processCheckForeignEntity($entity);
$this->beforeUnrelate($entity, $options);
$result = $this->getMapper()->unrelateById($this->entity, $this->relationName, $entity->id);
if (!$result) {
return;
}
$this->afterUnrelate($entity, $options);
}
public function massRelate(Select $query, array $options = [])
{
$this->beforeMassRelate($query, $options);
$this->getMapper()->massRelate($this->entity, $this->relationName, $query);
$this->afterMassRelate($query, $options);
}
/**
* Update relationship columns. For many-to-many relationships.
*/
public function updateColumns(Entity $entity, array $columnData)
{
$this->processCheckForeignEntity($entity);
if ($this->relationType !== Entity::MANY_MANY) {
throw new RuntimeException("Can't update not many-to-many relation.");
}
$this->getMapper()->updateRelationColumns($this->entity, $this->relationName, $entity->id, $columnData);
}
/**
* Get a relationship column value. For many-to-many relationships.
*
* @return mixed
*/
public function getColumn(Entity $entity, string $column)
{
$this->processCheckForeignEntity($entity);
if ($this->relationType !== Entity::MANY_MANY) {
throw new RuntimeException("Can't get a column of not many-to-many relation.");
}
return $this->getMapper()->getRelationColumn($this->entity, $this->relationName, $entity->id, $column);
}
protected function beforeRelate(Entity $entity, ?array $columnData, array $options)
{
$this->hookMediator->beforeRelate($this->entity, $this->relationName, $entity, $columnData, $options);
}
protected function afterRelate(Entity $entity, ?array $columnData, array $options)
{
$this->hookMediator->afterRelate($this->entity, $this->relationName, $entity, $columnData, $options);
}
protected function beforeUnrelate(Entity $entity, array $options)
{
$this->hookMediator->beforeUnrelate($this->entity, $this->relationName, $entity, $options);
}
protected function afterUnrelate(Entity $entity, array $options)
{
$this->hookMediator->afterUnrelate($this->entity, $this->relationName, $entity, $options);
}
protected function beforeMassRelate(Select $query, array $options)
{
$this->hookMediator->beforeMassRelate($this->entity, $this->relationName, $query, $options);
}
protected function afterMassRelate(Select $query, array $options)
{
$this->hookMediator->afterMassRelate($this->entity, $this->relationName, $query, $options);
}
}
@@ -0,0 +1,376 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\Repository;
use Espo\ORM\{
Collection,
Entity,
EntityManager,
QueryParams\Select,
QueryParams\SelectBuilder,
Mapper\Mapper,
};
use RuntimeException;
use BadMethodCallException;
/**
* Builds select parameters for related records for RDB repository.
*/
class RDBRelationSelectBuilder
{
protected $entityManager;
protected $entity;
protected $entityType;
protected $foreignEntityType;
protected $relationName;
protected $relationType = null;
protected $builder = null;
protected $additionalSelect = [];
protected $selectIsAdded = false;
public function __construct(EntityManager $entityManager, Entity $entity, string $relationName, ?Select $query = null)
{
$this->entityManager = $entityManager;
$this->entity = $entity;
$this->relationName = $relationName;
$this->relationType = $entity->getRelationType($relationName);
$this->entityType = $entity->getEntityType();
$this->foreignEntityType = $entity->getRelationParam($relationName, 'entity');
if ($query) {
$this->builder = $this->createSelectBuilder()->clone($query);
} else {
$this->builder = $this->createSelectBuilder()->from($this->foreignEntityType);
}
}
protected function createSelectBuilder() : SelectBuilder
{
return new SelectBuilder($this->entityManager->getQueryComposer());
}
protected function getMapper() : Mapper
{
return $this->entityManager->getMapper();
}
/**
* Additional middle table columns. Only for many-to-many relationships.
*
* Usage example:
* `['columnName' => 'attributeName']`
* Where `attributeName` is a non storable attribute that will be set with a column value.
*
* @todo Remove? Use attribute definitions to detect a proper select expression (in QueryComposer).
* @deprecated Use `->select('middleTable', 'attributeName')` instead.
*/
public function columns(array $columns) : self
{
if (!count($columns)) {
return $this;
}
if ($this->relationType !== Entity::MANY_MANY) {
throw new RuntimeException("Can't select relation columns for not many-to-many relationship.");
}
$middleName = lcfirst(
$this->entity->getRelationParam($this->relationName, 'relationName')
);
foreach ($columns as $column => $alias) {
$this->additionalSelect[] = [
$middleName . '.' . $column,
$alias,
];
}
return $this;
}
/**
* Apply middle table conditions for a many-to-many relationship.
*
* Usage example:
* `->columnsWhere(['column' => $value])`
*/
public function columnsWhere(array $where) : self
{
if ($this->relationType !== Entity::MANY_MANY) {
throw new RuntimeException("Can't add columns where for not many-to-many relationship.");
}
$transformedWhere = $this->applyMiddleAliasToWhere($where);
$this->where($transformedWhere);
return $this;
}
protected function applyMiddleAliasToWhere(array $where) : array
{
$transformedWhere = [];
$middleName = lcfirst(
$this->entity->getRelationParam($this->relationName, 'relationName')
);
foreach ($where as $key => $value) {
$transformedKey = $key;
$transformedValue = $value;
if (is_int($key)) {
$transformedKey = $key;
}
if (
is_string($key) &&
strlen($key) &&
strpos($key, '.') === false &&
$key[0] === strtolower($key[0])
) {
$transformedKey = $middleName . '.' . $key;
}
if (is_array($value)) {
$transformedValue = $this->applyMiddleAliasToWhere($value);
}
$transformedWhere[$transformedKey] = $transformedValue;
}
return $transformedWhere;
}
protected function addAdditionalSelect()
{
if (!count($this->additionalSelect)) {
return;
}
$select = $this->builder->build()->getSelect();
if (!count($select)) {
$this->builder->select('*');
}
foreach ($this->additionalSelect as $item) {
$this->builder->select($item[0], $item[1]);
}
}
/**
* Find related records by a criteria.
*/
public function find() : Collection
{
$this->addAdditionalSelect();
$query = $this->builder->build();
$related = $this->getMapper()->selectRelated($this->entity, $this->relationName, $query);
if ($related instanceof Collection) {
return $related;
}
$collection = $this->entityManager->getCollectionFactory()->create($this->foreignEntityType);
$collection->setAsFetched();
if ($related instanceof Entity) {
$collection[] = $related;
}
return $collection;
}
/**
* Find a first related records by a criteria.
*/
public function findOne() : ?Entity
{
$collection = $this->limit(0, 1)->find();
if (!count($collection)) {
return null;
}
foreach ($collection as $entity) {
return $entity;
}
}
/**
* Get a number of related records that meet criteria.
*/
public function count() : int
{
$query = $this->builder->build();
return $this->getMapper()->countRelated($this->entity, $this->relationName, $query);
}
/**
* Add JOIN.
*
* @see Espo\ORM\QueryParams\SelectBuilder::join()
*/
public function join($relationName, ?string $alias = null, ?array $conditions = null) : self
{
$this->builder->join($relationName, $alias, $conditions);
return $this;
}
/**
* Add LEFT JOIN.
*
* @see Espo\ORM\QueryParams\SelectBuilder::leftJoin()
*/
public function leftJoin($relationName, ?string $alias = null, ?array $conditions = null) : self
{
$this->builder->leftJoin($relationName, $alias, $conditions);
return $this;
}
/**
* Set DISTINCT parameter.
*/
public function distinct() : self
{
$this->builder->distinct();
return $this;
}
/**
* Set to return STH collection. Recommended for fetching large number of records.
*
* @todo Remove.
*/
public function sth() : self
{
$this->builder->sth();
return $this;
}
/**
* Add a WHERE clause.
*
* @see Espo\ORM\QueryParams\SelectBuilder::where()
*
* @param array|string $keyOrClause
* @param ?array|string $value
*/
public function where($keyOrClause = [], $value = null) : self
{
$this->builder->where($keyOrClause, $value);
return $this;
}
/**
* Add a HAVING clause.
*
* @see Espo\ORM\QueryParams\SelectBuilder::having()
*/
public function having($keyOrClause = [], $value = null) : self
{
$this->builder->having($keyOrClause, $params2);
return $this;
}
/**
* Apply ORDER.
*
* @see Espo\ORM\QueryParams\SelectBuilder::order()
*
* @param string|array $orderBy
* @param bool|string $direction
*/
public function order($orderBy, $direction = 'ASC') : self
{
$this->builder->order($orderBy, $direction);
return $this;
}
/**
* Apply OFFSET and LIMIT.
*/
public function limit(?int $offset = null, ?int $limit = null) : self
{
$this->builder->limit($offset, $limit);
return $this;
}
/**
* Specify SELECT. Which attributes to select. All attributes are selected by default.
*
* @see Espo\ORM\QueryParams\SelectBuilder::select()
*
* @param array|string $select
*/
public function select($select, ?string $alias = null) : self
{
$this->builder->select($select, $alias);
return $this;
}
/**
* Specify GROUP BY.
*/
public function groupBy(array $groupBy) : self
{
$this->builder->groupBy($groupBy);
return $this;
}
}
@@ -27,48 +27,51 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\Repositories;
namespace Espo\ORM\Repository;
use Espo\ORM\{
EntityManager,
EntityFactory,
Collection,
Entity,
Repository,
DB\Mapper,
RDBSelectBuilder as RDBSelectBuilder,
Mapper\Mapper,
QueryParams\Select,
};
use StdClass;
use RuntimeException;
use InvalidArgumentException;
class RDB extends Repository implements Findable, Relatable, Removable
class RDBRepository extends Repository
{
protected $mapper;
private $isTableLocked = false;
public function __construct(string $entityType, EntityManager $entityManager, EntityFactory $entityFactory)
{
protected $hookMediator;
public function __construct(
string $entityType, EntityManager $entityManager, EntityFactory $entityFactory, ?HookMediator $hookMediator = null
) {
$this->entityType = $entityType;
$this->entityName = $entityType;
$this->entityFactory = $entityFactory;
$this->seed = $this->entityFactory->create($entityType);
$this->entityClassName = get_class($this->seed);
$this->entityManager = $entityManager;
$this->seed = $this->entityFactory->create($entityType);
$this->hookMediator = $hookMediator ?? (new EmptyHookMediator());
}
protected function getMapper() : Mapper
{
if (empty($this->mapper)) {
$this->mapper = $this->getEntityManager()->getMapper('RDB');
}
return $this->mapper;
return $this->entityManager->getMapper();
}
/**
* @deprecated
* @todo Remove in 6.0.
*/
public function handleSelectParams(&$params)
{
@@ -84,6 +87,7 @@ class RDB extends Repository implements Findable, Relatable, Removable
if ($entity) {
$entity->setIsNew(true);
$entity->populateDefaults();
return $entity;
}
@@ -93,16 +97,17 @@ class RDB extends Repository implements Findable, Relatable, Removable
/**
* Fetch an entity by ID.
*/
public function getById(string $id, array $params = []) : ?Entity
public function getById(string $id) : ?Entity
{
$entity = $this->entityFactory->create($this->entityType);
if (!$entity) return null;
$select = $this->entityManager->getQueryBuilder()
->select()
->from($this->entityType)
->where([
'id' => $id,
])
->build();
if (empty($params['skipAdditionalSelectParams'])) {
$this->handleSelectParams($params);
}
return $this->getMapper()->selectById($entity, $id, $params);
return $this->getMapper()->selectOne($select);
}
public function get(?string $id = null) : ?Entity
@@ -110,19 +115,21 @@ class RDB extends Repository implements Findable, Relatable, Removable
if (is_null($id)) {
return $this->getNew();
}
return $this->getById($id);
}
protected function beforeSave(Entity $entity, array $options = [])
{
}
protected function afterSave(Entity $entity, array $options = [])
protected function processCheckEntity(Entity $entity)
{
if ($entity->getEntityType() !== $this->entityType) {
throw new RuntimeException("An entity type doesn't match the repository.");
}
}
public function save(Entity $entity, array $options = [])
{
$this->processCheckEntity($entity);
$entity->setAsBeingSaved();
if (empty($options['skipBeforeSave']) && empty($options['skipAll'])) {
@@ -161,117 +168,252 @@ class RDB extends Repository implements Findable, Relatable, Removable
return $this->getMapper()->restoreDeleted($this->entityType, $id);
}
protected function beforeRemove(Entity $entity, array $options = [])
{
}
protected function afterRemove(Entity $entity, array $options = [])
/**
* Get an access point for a specific relation of a record.
*/
public function getRelation(Entity $entity, string $relationName) : RDBRelation
{
return new RDBRelation($this->entityManager, $entity, $relationName, $this->hookMediator);
}
/**
* Remove a record (mark as deleted).
*/
public function remove(Entity $entity, array $options = [])
{
$this->processCheckEntity($entity);
$this->beforeRemove($entity, $options);
$this->getMapper()->delete($entity);
$this->afterRemove($entity, $options);
}
/**
* @deprecated Use QueryBuilder instead.
*/
public function deleteFromDb(string $id, bool $onlyDeleted = false)
{
$this->getMapper()->deleteFromDb($this->entityType, $id, $onlyDeleted);
}
public function find(array $params = []) : Collection
/**
* Find records.
*
* @param $params @deprecated Omit it.
*/
public function find(?array $params = []) : Collection
{
// @todo Remove.
if (empty($query['skipAdditionalSelectParams'])) {
$this->handleSelectParams($query);
}
return $this->createSelectBuilder()->find($params);
}
/**
* Find one record.
*
* @param $params @deprecated Omit it.
*/
public function findOne(?array $params = []) : ?Entity
{
// @todo Remove.
if (empty($params['skipAdditionalSelectParams'])) {
$this->handleSelectParams($params);
}
$collection = $this->getMapper()->select($this->seed, $params);
return $collection;
}
public function findOne(array $params = []) : ?Entity
{
unset($params['returnSthCollection']);
$collection = $this->limit(0, 1)->find($params);
if (count($collection)) {
return $collection[0];
foreach ($collection as $entity) {
return $entity;
}
return null;
}
public function findByQuery(string $sql, ?string $collectionType = null)
/**
* Find records by a SQL query.
*/
public function findBySql(string $sql) : Collection
{
if (!$collectionType) {
$collection = $this->getMapper()->selectByQuery($this->seed, $sql);
} else if ($collectionType === EntityManager::STH_COLLECTION) {
$collection = $this->getEntityManager()->createSthCollection($this->entityType);
$collection->setQuery($sql);
}
return $collection;
return $this->getMapper()->selectBySql($this->entityType, $sql);
}
public function findRelated(Entity $entity, string $relationName, array $params = [])
/**
* @deprecated
*/
public function findRelated(Entity $entity, string $relationName, ?array $params = null)
{
$params = $params ?? [];
if ($entity->getEntityType() !== $this->entityType) {
throw new InvalidArgumentException("Not supported entity type.");
}
if (!$entity->id) {
return null;
}
if ($entity->getRelationType($relationName) === Entity::BELONGS_TO_PARENT) {
$entityType = $entity->get($relationName . 'Type');
} else {
$entityType = $entity->getRelationParam($relationName, 'entity');
}
$type = $entity->getRelationType($relationName);
$entityType = $entity->getRelationParam($relationName, 'entity');
if ($entityType && empty($params['skipAdditionalSelectParams'])) {
$this->getEntityManager()->getRepository($entityType)->handleSelectParams($params);
$this->entityManager->getRepository($entityType)->handleSelectParams($params);
}
$result = $this->getMapper()->selectRelated($entity, $relationName, $params);
$additionalColumns = $params['additionalColumns'] ?? [];
unset($params['additionalColumns']);
$additionalColumnsConditions = $params['additionalColumnsConditions'] ?? [];
unset($params['additionalColumnsConditions']);
$select = null;
if ($entityType) {
$params['from'] = $entityType;
$select = Select::fromRaw($params);
}
if ($type === Entity::MANY_MANY && count($additionalColumns)) {
$select = $this->applyRelationAdditionalColumns($entity, $relationName, $additionalColumns, $select);
}
// @todo Get rid of 'additionalColumnsConditions' usage. Use 'whereClause' instead.
if ($type === Entity::MANY_MANY && count($additionalColumnsConditions)) {
$select = $this->applyRelationAdditionalColumnsConditions(
$entity, $relationName, $additionalColumnsConditions, $select
);
}
$result = $this->getMapper()->selectRelated($entity, $relationName, $select);
return $result;
}
public function countRelated(Entity $entity, string $relationName, array $params = []) : int
/**
* @deprecated
*/
public function countRelated(Entity $entity, string $relationName, ?array $params = null) : int
{
$params = $params ?? [];
if ($entity->getEntityType() !== $this->entityType) {
throw new InvalidArgumentException("Not supported entity type.");
}
if (!$entity->id) {
return 0;
}
$entityType = $entity->getRelationParam($relationName, 'entity');
$type = $entity->getRelationType($relationName);
$entityType = $entity->getRelationParam($relationName, 'entity');
if ($entityType && empty($params['skipAdditionalSelectParams'])) {
$this->getEntityManager()->getRepository($entityType)->handleSelectParams($params);
$this->entityManager->getRepository($entityType)->handleSelectParams($params);
}
return intval($this->getMapper()->countRelated($entity, $relationName, $params));
$additionalColumnsConditions = $params['additionalColumnsConditions'] ?? [];
unset($params['additionalColumnsConditions']);
if ($type === Entity::MANY_MANY && count($additionalColumnsConditions)) {
$select = $this->applyRelationAdditionalColumnsConditions($entity, $relationName, $additionalColumnsConditions, $select);
}
$select = null;
if ($entityType) {
$params['from'] = $entityType;
$select = Select::fromRaw($params);
}
return (int) $this->getMapper()->countRelated($entity, $relationName, $select);
}
protected function applyRelationAdditionalColumns(
Entity $entity, string $relationName, array $columns, Select $select
) : Select {
if (empty($columns)) {
return $select;
}
$middleName = lcfirst($entity->getRelationParam($relationName, 'relationName'));
$selectItemList = $select->getSelect();
if (empty($selectItemList)) {
$selectItemList[] = '*';
}
foreach ($columns as $column => $alias) {
$selectItemList[] = [
$middleName . '.' . $column,
$alias
];
}
$select = $this->entityManager->getQueryBuilder()
->clone($select)
->select($selectItemList)
->build();
return $select;
}
protected function applyRelationAdditionalColumnsConditions(
Entity $entity, string $relationName, array $conditions, Select $select
) : Select {
if (empty($conditions)) {
return $select;
}
$middleName = lcfirst($entity->getRelationParam($relationName, 'relationName'));
$builder = $this->entityManager->getQueryBuilder()->clone($select);
foreach ($conditions as $column => $value) {
$builder->where(
$middleName . '.' . $column,
$value
);
}
return $builder->build();
}
/**
* @deprecated
*/
public function isRelated(Entity $entity, string $relationName, $foreign) : bool
{
if (!$entity->id) {
return false;
}
if ($entity->getEntityType() !== $this->entityType) {
throw new InvalidArgumentException("Not supported entity type.");
}
if ($foreign instanceof Entity) {
$id = $foreign->id;
} else if (is_string($foreign)) {
$id = $foreign;
} else {
throw new RuntimeException("Bad 'foreign' value.");
}
if (!$id) {
return false;
}
if (!$id) return false;
if ($entity->getRelationType($relationName) === Entity::BELONGS_TO) {
$foreignEntityType = $entity->getRelationParam($relationName, 'entity');
if (!$foreignEntityType) return false;
if (!$foreignEntityType) {
return false;
}
$foreignId = $entity->get($relationName . 'Id');
@@ -282,24 +424,33 @@ class RDB extends Repository implements Findable, Relatable, Removable
}
}
if (!$foreignId) return false;
if (!$foreignId) {
return false;
}
$foreignEntity = $this->getEntityManager()->getRepository($foreignEntityType)->select(['id'])->where([
'id' => $foreignId,
])->findOne();
$foreignEntity = $this->entityManager->getRepository($foreignEntityType)
->select(['id'])
->where(['id' => $foreignId])
->findOne();
if (!$foreignEntity) return false;
if (!$foreignEntity) {
return false;
}
return $foreignEntity->id === $id;
}
// @todo Use related builder.
return (bool) $this->countRelated($entity, $relationName, [
'whereClause' => [
'id' => $id,
]
],
]);
}
/**
* @deprecated
*/
public function relate(Entity $entity, string $relationName, $foreign, $columnData = null, array $options = [])
{
if (!$entity->id) {
@@ -307,7 +458,11 @@ class RDB extends Repository implements Findable, Relatable, Removable
}
if (! $foreign instanceof Entity && !is_string($foreign)) {
throw new RuntimeException("Bad foreign value.");
throw new RuntimeException("Bad 'foreign' value.");
}
if ($entity->getEntityType() !== $this->entityType) {
throw new InvalidArgumentException("Not supported entity type.");
}
$this->beforeRelate($entity, $relationName, $foreign, $columnData, $options);
@@ -350,6 +505,9 @@ class RDB extends Repository implements Findable, Relatable, Removable
return $result;
}
/**
* @deprecated
*/
public function unrelate(Entity $entity, string $relationName, $foreign, array $options = [])
{
if (!$entity->id) {
@@ -360,6 +518,10 @@ class RDB extends Repository implements Findable, Relatable, Removable
throw new RuntimeException("Bad foreign value.");
}
if ($entity->getEntityType() !== $this->entityType) {
throw new InvalidArgumentException("Not supported entity type.");
}
$this->beforeUnrelate($entity, $relationName, $foreign, $options);
$beforeMethodName = 'beforeUnrelate' . ucfirst($relationName);
@@ -395,6 +557,9 @@ class RDB extends Repository implements Findable, Relatable, Removable
return $result;
}
/**
* @deprecated
*/
public function getRelationColumn(Entity $entity, string $relationName, string $foreignId, string $column)
{
return $this->getMapper()->getRelationColumn($entity, $relationName, $foreignId, $column);
@@ -425,7 +590,7 @@ class RDB extends Repository implements Findable, Relatable, Removable
}
/**
* Update relationship columns.
* @deprecated
*/
public function updateRelation(Entity $entity, string $relationName, $foreign, $columnData)
{
@@ -451,9 +616,12 @@ class RDB extends Repository implements Findable, Relatable, Removable
throw new RuntimeException("Bad foreign value.");
}
return $this->getMapper()->updateRelation($entity, $relationName, $id, $columnData);
return $this->getMapper()->updateRelationColumns($entity, $relationName, $id, $columnData);
}
/**
* @deprecated
*/
public function massRelate(Entity $entity, string $relationName, array $params = [], array $options = [])
{
if (!$entity->id) {
@@ -462,54 +630,76 @@ class RDB extends Repository implements Findable, Relatable, Removable
$this->beforeMassRelate($entity, $relationName, $params, $options);
$this->getMapper()->massRelate($entity, $relationName, $params);
$select = Select::fromRaw($params);
$this->getMapper()->massRelate($entity, $relationName, $select);
$this->afterMassRelate($entity, $relationName, $params, $options);
}
/**
* @param $params @deprecated Omit it.
*/
public function count(array $params = []) : int
{
if (empty($params['skipAdditionalSelectParams'])) {
// @todo Remove it.
if (is_array($params) && empty($params['skipAdditionalSelectParams'])) {
$this->handleSelectParams($params);
}
$count = $this->getMapper()->count($this->seed, $params);
return intval($count);
return $this->createSelectBuilder()->count($params);
}
public function max(string $attribute, array $params = [])
/**
* Get a max value.
*
* @return int|float
*/
public function max(string $attribute)
{
return $this->getMapper()->max($this->seed, $params, $attribute);
return $this->createSelectBuilder()->max($attribute);
}
public function min(string $attribute, array $params = [])
/**
* Get a min value.
*
* @return int|float
*/
public function min(string $attribute)
{
return $this->getMapper()->min($this->seed, $params, $attribute);
return $this->createSelectBuilder()->min($attribute);
}
public function sum(string $attribute, array $params = [])
/**
* Get a sum value.
*
* @return int|float
*/
public function sum(string $attributel)
{
return $this->getMapper()->sum($this->seed, $params, $attribute);
return $this->createSelectBuilder()->sum($attribute);
}
/**
* Clone an existing query for a further modification and usage by 'find' or 'count' methods.
*/
public function clone(Select $query) : RDBSelectBuilder
{
if ($this->entityType !== $query->getFrom()) {
throw new RuntimeException("Can't clone a query of a different entity type.");
}
$builder = new RDBSelectBuilder($this->entityManager, $this->entityType, $query);
return $builder;
//return $builder->clone($query);
}
/**
* Add JOIN.
*
* @param string|array $relationName A relationName or table. A relationName is in camelCase, a table is in CamelCase.
*
* Usage options:
* * `join(string $relationName)`
* * `join(array $joinDefinitionList)`
*
* Usage examples:
* ```
* ->join($relationName)
* ->join($relationName, $alias, $conditions)
* ->join([$relationName1, $relationName2, ...])
* ->join([[$relationName, $alias], ...])
* ->join([[$relationName, $alias, $conditions], ...])
* ```
* @see Espo\ORM\QueryParams\SelectBuilder::join()
*/
public function join($relationName, ?string $alias = null, ?array $conditions = null) : RDBSelectBuilder
{
@@ -519,9 +709,7 @@ class RDB extends Repository implements Findable, Relatable, Removable
/**
* Add LEFT JOIN.
*
* @param string|array $relationName A relationName or table. A relationName is in camelCase, a table is in CamelCase.
*
* This method works the same way as `join` method.
* @see Espo\ORM\QueryParams\SelectBuilder::leftJoin()
*/
public function leftJoin($relationName, ?string $alias = null, ?array $conditions = null) : RDBSelectBuilder
{
@@ -538,6 +726,8 @@ class RDB extends Repository implements Findable, Relatable, Removable
/**
* Set to return STH collection. Recommended fetching large number of records.
*
* @todo Remove.
*/
public function sth() : RDBSelectBuilder
{
@@ -547,9 +737,7 @@ class RDB extends Repository implements Findable, Relatable, Removable
/**
* Add a WHERE clause.
*
* Two usage options:
* * `where(array $whereClause)`
* * `where(string $key, string $value)`
* @see Espo\ORM\QueryParams\SelectBuilder::where()
*/
public function where($param1 = [], $param2 = null) : RDBSelectBuilder
{
@@ -559,9 +747,7 @@ class RDB extends Repository implements Findable, Relatable, Removable
/**
* Add a HAVING clause.
*
* Two usage options:
* * `having(array $havingClause)`
* * `having(string $key, string $value)`
* @see Espo\ORM\QueryParams\SelectBuilder::having()
*/
public function having($param1 = [], $param2 = null) : RDBSelectBuilder
{
@@ -589,10 +775,14 @@ class RDB extends Repository implements Findable, Relatable, Removable
/**
* Specify SELECT. Which attributes to select. All attributes are selected by default.
*
* @see Espo\ORM\QueryParams\SelectBuilder::select()
*
* @param array|string $select
*/
public function select(array $select) : RDBSelectBuilder
public function select($select = [], ?string $alias = null) : RDBSelectBuilder
{
return $this->createSelectBuilder()->select($select);
return $this->createSelectBuilder()->select($select, $alias);
}
/**
@@ -605,19 +795,21 @@ class RDB extends Repository implements Findable, Relatable, Removable
protected function getPDO()
{
return $this->getEntityManager()->getPDO();
return $this->entityManager->getPDO();
}
protected function lockTable()
{
$tableName = $this->getEntityManager()->getQuery()->toDb($this->entityType);
$tableName = $this->entityManager->getQueryComposer()->toDb($this->entityType);
// @todo Use Query to get SQL. Transaction query params.
$this->getPDO()->query("LOCK TABLES `{$tableName}` WRITE");
$this->isTableLocked = true;
}
protected function unlockTable()
{
// @todo Use Query to get SQL.
$this->getPDO()->query("UNLOCK TABLES");
$this->isTableLocked = false;
}
@@ -629,8 +821,28 @@ class RDB extends Repository implements Findable, Relatable, Removable
protected function createSelectBuilder() : RDBSelectBuilder
{
$builder = new RDBSelectBuilder($this->getEntityManager());
$builder->from($this->getEntityType());
$builder = new RDBSelectBuilder($this->entityManager, $this->entityType);
return $builder;
}
protected function beforeSave(Entity $entity, array $options = [])
{
$this->hookMediator->beforeSave($entity, $options);
}
protected function afterSave(Entity $entity, array $options = [])
{
$this->hookMediator->afterSave($entity, $options);
}
protected function beforeRemove(Entity $entity, array $options = [])
{
$this->hookMediator->beforeRemove($entity, $options);
}
protected function afterRemove(Entity $entity, array $options = [])
{
$this->hookMediator->afterRemove($entity, $options);
}
}
@@ -0,0 +1,336 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\Repository;
use Espo\ORM\{
Collection,
Entity,
EntityManager,
QueryParams\Select,
QueryParams\SelectBuilder,
Mapper\Mapper,
};
use RuntimeException;
/**
* Builds select parameters for an RDB repository. Contains 'find' methods.
*/
class RDBSelectBuilder
{
protected $entityManager;
protected $builder;
protected $repository = null;
protected $entityType = null;
public function __construct(EntityManager $entityManager, string $entityType, ?Select $query = null)
{
$this->entityManager = $entityManager;
$this->entityType = $entityType;
$this->repository = $this->entityManager->getRepository($entityType);
if ($query && $query->getFrom() !== $entityType) {
throw new RuntimeException("SelectBuilder: Passed query doesn't match the entity type.");
}
$this->builder = new SelectBuilder($entityManager->getQueryComposer());
if ($query) {
$this->builder->clone($query);
}
if (!$query) {
$this->builder->from($entityType);
}
}
protected function getMapper() : Mapper
{
return $this->entityManager->getMapper();
}
/**
* @param $params @deprecated. Omit it.
*/
public function find(?array $params = null) : Collection
{
$query = $this->getMergedParams($params);
return $this->getMapper()->select($query);
}
/**
* @param $params @deprecated. Omit it.
*/
public function findOne(?array $params = null) : ?Entity
{
if ($params !== null) {
$query = $this->getMergedParams($params);
$collection = $this->repository->clone($query)->limit(0, 1)->find();
} else {
$collection = $this->limit(0, 1)->find();
}
foreach ($collection as $entity) {
return $entity;
}
return null;
}
/**
* Get a number of records.
*
* @param $params @deprecated. Omit it.
*/
public function count(?array $params = null) : int
{
$query = $this->getMergedParams($params);
return $this->getMapper()->count($query);
}
/**
* Get a max value.
*
* @return int|float
*/
public function max(string $attribute)
{
$query = $this->builder->build();
return $this->getMapper()->max($query, $attribute);
}
/**
* Get a min value.
*
* @return int|float
*/
public function min(string $attribute)
{
$query = $this->builder->build();
return $this->getMapper()->min($query, $attribute);
}
/**
* Get a sum value.
*
* @return int|float
*/
public function sum(string $attribute)
{
$query = $this->builder->build();
return $this->getMapper()->sum($query, $attribute);
}
/**
* Add JOIN.
*
* @see Espo\ORM\QueryParams\SelectBuilder::join()
*/
public function join($relationName, ?string $alias = null, ?array $conditions = null) : self
{
$this->builder->join($relationName, $alias, $conditions);
return $this;
}
/**
* Add LEFT JOIN.
*
* @see Espo\ORM\QueryParams\SelectBuilder::leftJoin()
*/
public function leftJoin($relationName, ?string $alias = null, ?array $conditions = null) : self
{
$this->builder->leftJoin($relationName, $alias, $conditions);
return $this;
}
/**
* Set DISTINCT parameter.
*/
public function distinct() : self
{
$this->builder->distinct();
return $this;
}
/**
* Set to return STH collection. Recommended for fetching large number of records.
*
* @todo Remove.
*/
public function sth() : self
{
$this->builder->sth();
return $this;
}
/**
* Add a WHERE clause.
*
* @see Espo\ORM\QueryParams\SelectBuilder::where()
*/
public function where($keyOrClause = [], $value = null) : self
{
$this->builder->where($keyOrClause, $value);
return $this;
}
/**
* Add a HAVING clause.
*
* @see Espo\ORM\QueryParams\SelectBuilder::having()
*/
public function having($keyOrClause = [], $value = null) : self
{
$this->builder->having($keyOrClause, $value);
return $this;
}
/**
* Apply ORDER.
*
* @param string|array $orderBy An attribute to order by or order definitions as an array.
* @param bool|string $direction TRUE for DESC order.
*/
public function order($orderBy = 'id', $direction = 'ASC') : self
{
$this->builder->order($orderBy, $direction);
return $this;
}
/**
* Apply OFFSET and LIMIT.
*/
public function limit(?int $offset = null, ?int $limit = null) : self
{
$this->builder->limit($offset, $limit);
return $this;
}
/**
* Specify SELECT. Which attributes to select. All attributes are selected by default.
*
* @see Espo\ORM\QueryParams\SelectBuilder::select()
*
* @param array|string $select
*/
public function select($select, ?string $alias = null) : self
{
$this->builder->select($select, $alias);
return $this;
}
/**
* Specify GROUP BY.
*/
public function groupBy(array $groupBy) : self
{
$this->builder->groupBy($groupBy);
return $this;
}
/**
* For backward compatibility.
* @todo Remove.
*/
protected function getMergedParams(?array $params = null) : Select
{
if (!$params || empty($params)) {
return $this->builder->build();
}
$params = $params ?? [];
$builtParams = $this->builder->build()->getRawParams();
$whereClause = $builtParams['whereClause'] ?? [];
$havingClause = $builtParams['havingClause'] ?? [];
$joins = $builtParams['joins'] ?? [];
$leftJoins = $builtParams['leftJoins'] ?? [];
if (!empty($params['whereClause'])) {
unset($builtParams['whereClause']);
if (count($whereClause)) {
$params['whereClause'][] = $whereClause;
}
}
if (!empty($params['havingClause'])) {
unset($builtParams['havingClause']);
if (count($havingClause)) {
$params['havingClause'][] = $havingClause;
}
}
if (empty($params['whereClause'])) {
unset($params['whereClause']);
}
if (empty($params['havingClause'])) {
unset($params['havingClause']);
}
if (!empty($params['leftJoins']) && !empty($leftJoins)) {
foreach ($leftJoins as $j) {
$params['leftJoins'][] = $j;
}
}
if (!empty($params['joins']) && !empty($joins)) {
foreach ($joins as $j) {
$params['joins'][] = $j;
}
}
$params = array_replace_recursive($builtParams, $params);
return Select::fromRaw($params);
}
}
@@ -27,7 +27,13 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM;
namespace Espo\ORM\Repository;
use Espo\ORM\{
Entity,
EntityManager,
EntityFactory,
};
/**
* An access point for fetching and storing records.
@@ -40,17 +46,16 @@ abstract class Repository
protected $seed;
protected $entityClassName;
protected $entityType;
public function __construct(string $entityType, EntityManager $entityManager, EntityFactory $entityFactory)
{
$this->entityType = $entityType;
$this->entityFactory = $entityFactory;
$this->seed = $this->entityFactory->create($entityType);
$this->entityClassName = get_class($this->seed);
$this->entityManager = $entityManager;
$this->seed = $this->entityFactory->create($entityType);
}
protected function getEntityFactory() : EntityFactory
@@ -69,12 +74,12 @@ abstract class Repository
}
/**
* Get entity. If $id is NULL, a new entity is returned.
* Get an entity. If $id is NULL, a new entity is returned.
*/
abstract public function get(?string $id = null) : ?Entity;
/**
* Store entity.
* Store an entity.
*/
abstract public function save(Entity $entity);
}
@@ -27,9 +27,9 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM;
namespace Espo\ORM\Repository;
interface RepositoryFactory
{
public function create(string $name) : object;
public function create(string $name) : Repository;
}
+93 -55
View File
@@ -29,103 +29,136 @@
namespace Espo\ORM;
use Espo\ORM\{
QueryParams\Select as SelectQuery,
QueryComposer\QueryComposer as QueryComposer,
};
use IteratorAggregate;
use Countable;
use PDO;
/**
* Reasonable to use when selecting a large number of records.
* It doesn't allocate a memory for every entity.
* Entities are fetched on each iteration while traversing a collection.
*
* STH stands for Statement Handle.
*/
class SthCollection implements \IteratorAggregate, Collection
class SthCollection implements Collection, IteratorAggregate, Countable
{
protected $entityManager = null;
protected $entityManager;
protected $entityType;
protected $selectParams = null;
protected $query = null;
private $sth = null;
private $sql = null;
protected $isFetched = false;
protected $entityList = [];
public function __construct(string $entityType, EntityManager $entityManager = null, array $selectParams = [])
protected function __construct(EntityManager $entityManager)
{
$this->selectParams = $selectParams;
$this->entityType = $entityType;
$this->entityManager = $entityManager;
}
protected function getQuery()
protected function getQueryComposer() : QueryComposer
{
return $this->entityManager->getQuery();
return $this->entityManager->getQueryComposer();
}
protected function getPdo()
{
return $this->entityManager->getPdo();
}
protected function getEntityFactory()
protected function getEntityFactory() : EntityFactory
{
return $this->entityManager->getEntityFactory();
}
/**
* Get select parameters.
*/
public function setSelectParams(array $selectParams)
{
$this->selectParams = $selectParams;
}
/**
* Set an SQL query.
*/
public function setQuery(?string $sql)
protected function setSql(string $sql)
{
$this->sql = $sql;
}
/**
* Run an SQL query.
*/
public function executeQuery()
protected function getPDO() : PDO
{
if ($this->sql) {
$sql = $this->sql;
} else {
$sql = $this->getQuery()->createSelectQuery($this->entityType, $this->selectParams);
}
$sth = $this->getPdo()->prepare($sql);
return $this->entityManager->getPDO();
}
protected function executeQuery()
{
$sql = $this->getSql();
$sth = $this->getPDO()->prepare($sql);
$sth->execute();
$this->sth = $sth;
}
protected function getSql() : string
{
if (!$this->sql) {
$this->sql = $this->getQueryComposer()->compose($this->getQuery());
}
return $this->sql;
}
protected function getQuery() : SelectQuery
{
return $this->query;
}
public function getIterator()
{
return (function () {
if (isset($this->sth)) {
$this->sth->execute();
}
while ($row = $this->fetchRow()) {
$entity = $this->getEntityFactory()->create($this->entityType);
$entity->set($row);
$entity->setAsFetched();
$this->prepareEntity($entity);
$this->entityList[] = $entity;
yield $entity;
}
})();
}
protected function fetchRow()
protected function executeQueryIfNotExecuted()
{
if (!$this->sth) {
$this->executeQuery();
}
}
protected function fetchRow()
{
$this->executeQueryIfNotExecuted();
return $this->sth->fetch(\PDO::FETCH_ASSOC);
}
public function count() : int
{
$this->executeQueryIfNotExecuted();
$rowCount = $this->sth->rowCount();
// MySQL may not return a row count for select queries.
if ($rowCount) {
return $rowCount;
}
return count($this->getValueMapList());
}
protected function prepareEntity(Entity $entity)
{
}
@@ -152,28 +185,13 @@ class SthCollection implements \IteratorAggregate, Collection
return $this->toArray(true);
}
/**
* Mark as fetched from DB.
*/
public function setAsFetched()
{
$this->isFetched = true;
}
/**
* Mark as not fetched from DB.
*/
public function setAsNotFetched()
{
$this->isFetched = false;
}
/**
* Is fetched from DB.
* Whether Is fetched from DB. SthCollection is always fetched.
*/
public function isFetched() : bool
{
return $this->isFetched;
return true;
}
/**
@@ -183,4 +201,24 @@ class SthCollection implements \IteratorAggregate, Collection
{
return $this->entityType;
}
public static function fromQuery(SelectQuery $query, EntityManager $entityManager) : self
{
$obj = new self($entityManager);
$obj->entityType = $query->getFrom();
$obj->query = $query;
return $obj;
}
public static function fromSql(string $entityType, string $sql, EntityManager $entityManager) : self
{
$obj = new self($entityManager);
$obj->entityType = $entityType;
$obj->sql = $sql;
return $obj;
}
}
+29 -25
View File
@@ -94,13 +94,13 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements
$emailAddressList = $this
->select(['name', 'lower', 'invalid', 'optOut', ['ee.primary', 'primary']])
->join([[
->join(
'EntityEmailAddress',
'ee',
[
'ee.emailAddressId:' => 'id',
]
]])
)
->where([
'ee.entityId' => $entity->id,
'ee.entityType' => $entity->getEntityType(),
@@ -249,10 +249,9 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements
public function getEntityByAddress(
string $address, ?string $entityType = null, array $order = ['User', 'Contact', 'Lead', 'Account']
) : ?Entity {
$selectBuilder = $this->getEntityManager()->createSelectBuilder();
$selectBuilder = $this->getEntityManager()->getRepository('EntityEmailAddress')->select();
$selectBuilder
->from('EntityEmailAddress')
->select(['entityType', 'entityId'])
->sth()
->join('EmailAddress', 'ea', ['ea.id:' => 'emailAddressId', 'ea.deleted' => 0])
@@ -262,7 +261,6 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements
['primary', 'DESC'],
]);
if ($entityType) {
$selectBuilder->where('entityType=', $entityType);
}
@@ -290,8 +288,6 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements
public function storeEntityEmailAddressData(Entity $entity)
{
$pdo = $this->getEntityManager()->getPDO();
$emailAddressValue = $entity->get('emailAddress');
if (is_string($emailAddressValue)) {
$emailAddressValue = trim($emailAddressValue);
@@ -410,17 +406,21 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements
foreach ($toRemoveList as $address) {
$emailAddress = $this->getByAddress($address);
if ($emailAddress) {
$sql = $this->getEntityManager()->getQuery()->createDeleteQuery('EntityEmailAddress', [
'whereClause' => [
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
'emailAddressId' => $emailAddress->id,
],
]);
$sth = $pdo->prepare($sql);
$sth->execute();
if (!$emailAddress) {
continue;
}
$delete = $this->getEntityManager()->getQueryBuilder()
->delete()
->from('EntityEmailAddress')
->where([
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
'emailAddressId' => $emailAddress->id,
])
->build();
$this->getEntityManager()->getQueryExecutor()->run($delete);
}
foreach ($toUpdateList as $address) {
@@ -496,8 +496,10 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements
$emailAddress = $this->getByAddress($primary);
if ($emailAddress) {
$updateSelect = $this->getEntityManager()->createSelectBuilder()
$update = $this->getEntityManager()->getQueryBuilder()
->update()
->from('EntityEmailAddress')
->set(['primary' => false])
->where([
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
@@ -506,10 +508,12 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements
])
->build();
$this->getEntityManager()->getQueryExecutor()->update($updateSelect, ['primary' => false]);
$this->getEntityManager()->getQueryExecutor()->run($update);
$updateSelect = $this->getEntityManager()->createSelectBuilder()
$update = $this->getEntityManager()->getQueryBuilder()
->update()
->from('EntityEmailAddress')
->set(['primary' => true])
->where([
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
@@ -518,7 +522,7 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements
])
->build();
$this->getEntityManager()->getQueryExecutor()->update($updateSelect, ['primary' => true]);
$this->getEntityManager()->getQueryExecutor()->run($update);
}
}
@@ -537,8 +541,6 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements
{
if (!$entity->has('emailAddress')) return;
$pdo = $this->getEntityManager()->getPDO();
$emailAddressValue = $entity->get('emailAddress');
if (is_string($emailAddressValue)) {
$emailAddressValue = trim($emailAddressValue);
@@ -574,8 +576,10 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements
$this->markAddressOptedOut($emailAddressValue, !!$entity->get('emailAddressIsOptedOut'));
}
$updateSelect = $this->getEntityManager()->createSelectBuilder()
$update = $this->getEntityManager()->getQueryBuilder()
->update()
->from('EntityEmailAddress')
->set(['primary' => true])
->where([
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
@@ -583,7 +587,7 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements
])
->build();
$this->getEntityManager()->getQueryExecutor()->update($updateSelect, ['primary' => true]);
$this->getEntityManager()->getQueryExecutor()->run($update);
} else {
if (
+13 -8
View File
@@ -29,11 +29,14 @@
namespace Espo\Repositories;
use Espo\ORM\Entity;
use Espo\ORM\{
Entity,
Collection,
};
class Import extends \Espo\Core\Repositories\Database
{
public function findRelated(Entity $entity, string $relationName, array $params = []) : \Traversable
public function findRelated(Entity $entity, string $relationName, ?array $params = [])
{
$entityType = $entity->get('entityType');
@@ -76,7 +79,7 @@ class Import extends \Espo\Core\Repositories\Database
];
}
public function countRelated(Entity $entity, string $relationName, array $params = []) : int
public function countRelated(Entity $entity, string $relationName, ?array $params = null) : int
{
$entityType = $entity->get('entityType');
@@ -94,13 +97,15 @@ class Import extends \Espo\Core\Repositories\Database
}
}
$sql = $this->getEntityManager()->getQuery()->createDeleteQuery('ImportEntity', [
'whereClause' => [
$delete = $this->getEntityManager()->getQueryBuilder()
->delete()
->from('ImportEntity')
->where([
'importId' => $entity->id,
]
]);
])
->build();
$this->getEntityManager()->getPDO()->query($sql);
$this->getEntityManager()->getQueryExecutor()->run($delete);
parent::afterRemove($entity, $options);
}
+29 -23
View File
@@ -92,13 +92,13 @@ class PhoneNumber extends \Espo\Core\Repositories\Database implements
$numberList = $this
->select(['name', 'type', 'invalid', 'optOut', ['en.primary', 'primary']])
->join([[
->join(
'EntityPhoneNumber',
'en',
[
'en.phoneNumberId:' => 'id',
]
]])
)
->where([
'en.entityId' => $entity->id,
'en.entityType' => $entity->getEntityType(),
@@ -211,8 +211,6 @@ class PhoneNumber extends \Espo\Core\Repositories\Database implements
protected function storeEntityPhoneNumberData(Entity $entity)
{
$pdo = $this->getEntityManager()->getPDO();
$phoneNumberValue = $entity->get('phoneNumber');
if (is_string($phoneNumberValue)) {
$phoneNumberValue = trim($phoneNumberValue);
@@ -335,17 +333,21 @@ class PhoneNumber extends \Espo\Core\Repositories\Database implements
foreach ($toRemoveList as $number) {
$phoneNumber = $this->getByNumber($number);
if ($phoneNumber) {
$sql = $this->getEntityManager()->getQuery()->createDeleteQuery('EntityPhoneNumber', [
'whereClause' => [
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
'phoneNumberId' => $phoneNumber->id,
],
]);
$sth = $pdo->prepare($sql);
$sth->execute();
if (!$phoneNumber) {
continue;
}
$delete = $this->getEntityManager()->getQueryBuilder()
->delete()
->from('EntityPhoneNumber')
->where([
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
'phoneNumberId' => $phoneNumber->id,
])
->build();
$this->getEntityManager()->getQueryExecutor()->run($delete);
}
foreach ($toUpdateList as $number) {
@@ -422,10 +424,12 @@ class PhoneNumber extends \Espo\Core\Repositories\Database implements
if ($primary) {
$phoneNumber = $this->getByNumber($primary);
if ($phoneNumber) {
$updateSelect = $this->getEntityManager()->createSelectBuilder()
if ($phoneNumber) {
$update = $this->getEntityManager()->getQueryBuilder()
->update()
->from('EntityPhoneNumber')
->set(['primary' => false])
->where([
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
@@ -434,10 +438,12 @@ class PhoneNumber extends \Espo\Core\Repositories\Database implements
])
->build();
$this->getEntityManager()->getQueryExecutor()->update($updateSelect, ['primary' => false]);
$this->getEntityManager()->getQueryExecutor()->run($update);
$updateSelect = $this->getEntityManager()->createSelectBuilder()
$update = $this->getEntityManager()->getQueryBuilder()
->update()
->from('EntityPhoneNumber')
->set(['primary' => true])
->where([
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
@@ -446,7 +452,7 @@ class PhoneNumber extends \Espo\Core\Repositories\Database implements
])
->build();
$this->getEntityManager()->getQueryExecutor()->update($updateSelect, ['primary' => true]);
$this->getEntityManager()->getQueryExecutor()->run($update);
}
}
@@ -464,8 +470,6 @@ class PhoneNumber extends \Espo\Core\Repositories\Database implements
protected function storeEntityPhoneNumberPrimary(Entity $entity)
{
$pdo = $this->getEntityManager()->getPDO();
if (!$entity->has('phoneNumber')) return;
$phoneNumberValue = trim($entity->get('phoneNumber'));
@@ -502,8 +506,10 @@ class PhoneNumber extends \Espo\Core\Repositories\Database implements
$this->markNumberOptedOut($phoneNumberValue, !!$entity->get('phoneNumberIsOptedOut'));
}
$updateSelect = $this->getEntityManager()->createSelectBuilder()
$update = $this->getEntityManager()->getQueryBuilder()
->update()
->from('EntityPhoneNumber')
->set( ['primary' => true])
->where([
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
@@ -511,7 +517,7 @@ class PhoneNumber extends \Espo\Core\Repositories\Database implements
])
->build();
$this->getEntityManager()->getQueryExecutor()->update($updateSelect, ['primary' => true]);
$this->getEntityManager()->getQueryExecutor()->run($update);
} else {
if (
+35 -38
View File
@@ -30,18 +30,14 @@
namespace Espo\Repositories;
use Espo\ORM\Entity;
use Espo\ORM\Repository;
use Espo\ORM\Repository\Repository;
use Espo\Core\Utils\Json;
use Espo\ORM\Repositories\{
Removable,
};
use PDO;
use Espo\Core\Di;
class Preferences extends Repository implements Removable,
class Preferences extends Repository implements
Di\MetadataAware,
Di\ConfigAware,
Di\EntityManagerAware
@@ -87,18 +83,17 @@ class Preferences extends Repository implements Removable,
{
$data = null;
$pdo = $this->entityManager->getPDO();
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Preferences', [
'select' => ['id', 'data'],
'whereClause' => [
$select = $this->getEntityManager()->getQueryBuilder()
->select()
->from('Preferences')
->select(['id', 'data'])
->where([
'id' => $id,
],
'limit' => 1,
]);
])
->limit(0, 1)
->build();
$sth = $pdo->prepare($sql);
$sth->execute();
$sth = $this->getEntityManager()->getQueryExecutor()->run($select);
while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
$data = Json::decode($row['data']);
@@ -171,13 +166,15 @@ class Preferences extends Repository implements Removable,
$entityTypeList = $entity->get('autoFollowEntityTypeList') ?? [];
$sql = $this->entityManager->getQuery()->createDeleteQuery('Autofollow', [
'whereClause' => [
$delete = $this->entityManager->getQueryBuilder()
->delete()
->from('Autofollow')
->where([
'userId' => $id,
],
]);
])
->build();
$this->entityManager->getPDO()->query($sql);
$this->entityManager->getQueryExecutor()->run($delete);
$entityTypeList = array_filter($entityTypeList, function ($item) {
return (bool) $this->metadata->get(['scopes', $item, 'stream']);
@@ -208,20 +205,20 @@ class Preferences extends Repository implements Removable,
$dataString = Json::encode($data, \JSON_PRETTY_PRINT);
$pdo = $this->entityManager->getPDO();
$sql = $this->entityManager->getQuery()->createInsertQuery('Preferences', [
'columns' => ['id', 'data'],
'values' => [
$insert = $this->getEntityManager()->getQueryBuilder()
->insert()
->into('Preferences')
->columns(['id', 'data'])
->values([
'id' => $entity->id,
'data' => $dataString,
],
'update' => [
])
->updateSet([
'data' => $dataString,
],
]);
])
->build();
$pdo->query($sql);
$this->getEntityManager()->getQueryExecutor()->run($insert);
$user = $this->entityManager->getEntity('User', $entity->id);
if ($user && !$user->isPortal()) {
@@ -233,15 +230,15 @@ class Preferences extends Repository implements Removable,
public function deleteFromDb(string $id)
{
$pdo = $this->entityManager->getPDO();
$sql = $this->entityManager->getQuery()->createDeleteQuery('Preferences', [
'whereClause' => [
$delete = $this->getEntityManager()->getQueryBuilder()
->delete()
->from('Preferences')
->where([
'id' => $id,
],
]);
])
->build();
$pdo->query($sql);
$this->getEntityManager()->getQueryExecutor()->run($delete);
}
public function remove(Entity $entity, array $options = [])
+10 -74
View File
@@ -334,7 +334,9 @@ class App
$this->dataManager->rebuild();
}
// TODO remove in 5.5.0
/**
* @todo Remove in 6.0.
*/
public function jobPopulatePhoneNumberNumeric()
{
$numberList = $this->entityManager->getRepository('PhoneNumber')->find();
@@ -343,7 +345,9 @@ class App
}
}
// TODO remove in 5.5.0
/**
* @todo Remove in 6.0. Move to another place. CLI command.
*/
public function jobPopulateArrayValues()
{
$scopeList = array_keys($this->metadata->get(['scopes']));
@@ -373,7 +377,7 @@ class App
$orGroup[$attribute . '!='] = null;
}
$sql = $this->entityManager->getQuery()->createSelectQuery($scope, [
$sql = $this->entityManager->getQueryComposer()->createSelectQuery($scope, [
'select' => $select,
'whereClause' => [
'OR' => $orGroup
@@ -394,77 +398,9 @@ class App
}
}
// TODO remove in 5.5.0
public function jobPopulateNotesTeamUser()
{
$aclManager = $this->aclManager;
$sql = $this->entityManager->getQuery()->createSelectQuery('Note', [
'whereClause' => [
'parentId!=' => null,
'type=' => ['Relate', 'CreateRelated', 'EmailReceived', 'EmailSent', 'Assign', 'Create'],
],
'limit' => 100000,
'orderBy' => [['number', 'DESC']]
]);
$sth = $this->entityManager->getPdo()->prepare($sql);
$sth->execute();
$i = 0;
while ($dataRow = $sth->fetch(\PDO::FETCH_ASSOC)) {
$i++;
$note = $this->entityManager->getEntityFactory()->create('Note');
$note->set($dataRow);
$note->setAsFetched();
if ($note->get('relatedId') && $note->get('relatedType')) {
$targetType = $note->get('relatedType');
$targetId = $note->get('relatedId');
} else if ($note->get('parentId') && $note->get('parentType')) {
$targetType = $note->get('parentType');
$targetId = $note->get('parentId');
} else {
continue;
}
if (!$this->entityManager->hasRepository($targetType)) continue;
try {
$entity = $this->entityManager->getEntity($targetType, $targetId);
if (!$entity) continue;
$ownerUserIdAttribute = $aclManager->getImplementation($targetType)->getOwnerUserIdAttribute($entity);
$toSave = false;
if ($ownerUserIdAttribute) {
if ($entity->getAttributeParam($ownerUserIdAttribute, 'isLinkMultipleIdList')) {
$link = $entity->getAttributeParam($ownerUserIdAttribute, 'relation');
$userIdList = $entity->getLinkMultipleIdList($link);
} else {
$userId = $entity->get($ownerUserIdAttribute);
if ($userId) {
$userIdList = [$userId];
} else {
$userIdList = [];
}
}
if (!empty($userIdList)) {
$note->set('usersIds', $userIdList);
$toSave = true;
}
}
if ($entity->hasLinkMultipleField('teams')) {
$teamIdList = $entity->getLinkMultipleIdList('teams');
if (!empty($teamIdList)) {
$note->set('teamsIds', $teamIdList);
$toSave = true;
}
}
if ($toSave) {
$this->entityManager->saveEntity($note);
}
} catch (\Exception $e) {}
}
}
/**
* @todo Remove in 6.0. Move to another place. CLI command.
*/
public function jobPopulateOptedOutPhoneNumbers()
{
$entityTypeList = ['Contact', 'Lead'];
@@ -109,7 +109,7 @@ class DashboardTemplate extends Record
$team = $this->getEntityManager()->fetchEntity('Team', $teamId);
if (!$team) throw new NotFound();
$userList = $this->getEntityManager()->getRepository('User')->join(['teams'])->distinct()->where([
$userList = $this->getEntityManager()->getRepository('User')->join('teams')->distinct()->where([
'teams.id' => $teamId,
])->find();
+30 -20
View File
@@ -522,8 +522,9 @@ class Email extends Record implements
{
$userId = $userId ?? $this->getUser()->id;
$select = $this->entityManager->createSelectBuilder()
$update = $this->entityManager->getQueryBuilder()->update()
->from('EmailUser')
->set(['isRead' => true])
->where([
'deleted' => false,
'userId' => $userId,
@@ -531,10 +532,11 @@ class Email extends Record implements
])
->build();
$this->entityManager->getQueryExecutor()->update($select, ['isRead' => true]);
$this->entityManager->getQueryExecutor()->run($update);
$select = $this->entityManager->createSelectBuilder()
$update = $this->entityManager->getQueryBuilder()->update()
->from('Notification')
->set(['read' => true])
->where([
'deleted' => false,
'userId' => $userId,
@@ -544,7 +546,7 @@ class Email extends Record implements
])
->build();
$this->entityManager->getQueryExecutor()->update($select, ['read' => true]);
$this->entityManager->getQueryExecutor()->run($update);
return true;
}
@@ -553,8 +555,9 @@ class Email extends Record implements
{
$userId = $userId ?? $this->getUser()->id;
$select = $this->entityManager->createSelectBuilder()
$update = $this->entityManager->getQueryBuilder()->update()
->from('EmailUser')
->set(['isRead' => true])
->where([
'deleted' => false,
'userId' => $userId,
@@ -562,7 +565,7 @@ class Email extends Record implements
])
->build();
$this->entityManager->getQueryExecutor()->update($select, ['isRead' => true]);
$this->entityManager->getQueryExecutor()->run($update);
$this->markNotificationAsRead($id, $userId);
@@ -573,8 +576,9 @@ class Email extends Record implements
{
$userId = $userId ?? $this->getUser()->id;
$select = $this->entityManager->createSelectBuilder()
$update = $this->entityManager->getQueryBuilder()->update()
->from('EmailUser')
->set(['isRead' => false])
->where([
'deleted' => false,
'userId' => $userId,
@@ -582,7 +586,7 @@ class Email extends Record implements
])
->build();
$this->entityManager->getQueryExecutor()->update($select, ['isRead' => false]);
$this->entityManager->getQueryExecutor()->run($update);
return true;
}
@@ -591,8 +595,9 @@ class Email extends Record implements
{
$userId = $userId ?? $this->getUser()->id;
$select = $this->entityManager->createSelectBuilder()
$update = $this->entityManager->getQueryBuilder()->update()
->from('EmailUser')
->set(['isImportant' => true])
->where([
'deleted' => false,
'userId' => $userId,
@@ -600,7 +605,7 @@ class Email extends Record implements
])
->build();
$this->entityManager->getQueryExecutor()->update($select, ['isImportant' => true]);
$this->entityManager->getQueryExecutor()->run($update);
return true;
}
@@ -609,8 +614,9 @@ class Email extends Record implements
{
$userId = $userId ?? $this->getUser()->id;
$select = $this->entityManager->createSelectBuilder()
$update = $this->entityManager->getQueryBuilder()->update()
->from('EmailUser')
->set(['isImportant' => false])
->where([
'deleted' => false,
'userId' => $userId,
@@ -618,7 +624,7 @@ class Email extends Record implements
])
->build();
$this->entityManager->getQueryExecutor()->update($select, ['isImportant' => false]);
$this->entityManager->getQueryExecutor()->run($update);
return true;
}
@@ -627,8 +633,9 @@ class Email extends Record implements
{
$userId = $userId ?? $this->getUser()->id;
$select = $this->entityManager->createSelectBuilder()
$update = $this->entityManager->getQueryBuilder()->update()
->from('EmailUser')
->set(['inTrash' => true])
->where([
'deleted' => false,
'userId' => $userId,
@@ -636,7 +643,7 @@ class Email extends Record implements
])
->build();
$this->entityManager->getQueryExecutor()->update($select, ['inTrash' => true]);
$this->entityManager->getQueryExecutor()->run($update);
$this->markNotificationAsRead($id, $userId);
@@ -647,8 +654,9 @@ class Email extends Record implements
{
$userId = $userId ?? $this->getUser()->id;
$select = $this->entityManager->createSelectBuilder()
$update = $this->entityManager->getQueryBuilder()->update()
->from('EmailUser')
->set(['inTrash' => false])
->where([
'deleted' => false,
'userId' => $userId,
@@ -656,15 +664,16 @@ class Email extends Record implements
])
->build();
$this->entityManager->getQueryExecutor()->update($select, ['inTrash' => false]);
$this->entityManager->getQueryExecutor()->run($update);
return true;
}
public function markNotificationAsRead(string $id, string $userId)
{
$select = $this->entityManager->createSelectBuilder()
$update = $this->entityManager->getQueryBuilder()->update()
->from('Notification')
->set(['read' => true])
->where([
'deleted' => false,
'userId' => $userId,
@@ -675,7 +684,7 @@ class Email extends Record implements
])
->build();
$this->entityManager->getQueryExecutor()->update($select, ['read' => true]);
$this->entityManager->getQueryExecutor()->run($update);
}
public function moveToFolder(string $id, ?string $folderId, ?string $userId = null)
@@ -686,8 +695,9 @@ class Email extends Record implements
$folderId = null;
}
$select = $this->entityManager->createSelectBuilder()
$update = $this->entityManager->getQueryBuilder()->update()
->from('EmailUser')
->set(['folderId' => $folderId])
->where([
'deleted' => false,
'userId' => $userId,
@@ -695,7 +705,7 @@ class Email extends Record implements
])
->build();
$this->entityManager->getQueryExecutor()->update($select, ['folderId' => $folderId]);
$this->entityManager->getQueryExecutor()->run($update);
return true;
}
+11 -17
View File
@@ -144,26 +144,20 @@ class EmailAddress extends Record
return [];
}
$pdo = $this->getEntityManager()->getPDO();
$list = $this->getEntityManager()->getRepository('InboundEmail')
->select(['id', 'name', 'emailAddress'])
->where([
'emailAddress*' => $query . '%',
])
->order('name')
->find();
$selectParams = [
'select' => ['id', 'name', 'emailAddress'],
'whereClause' => [
'emailAddress*' => $query . '%'
],
'orderBy' => 'name',
];
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('InboundEmail', $selectParams);
$sth = $pdo->prepare($sql);
$sth->execute();
while ($row = $sth->fetch(\PDO::FETCH_ASSOC)) {
foreach ($list as $item) {
$result[] = [
'emailAddress' => $row['emailAddress'],
'entityName' => $row['name'],
'emailAddress' => $item->get('emailAddress'),
'entityName' => $item->get('name'),
'entityType' => 'InboundEmail',
'entityId' => $row['id'],
'entityId' => $item->get('id'),
];
}
}
@@ -222,14 +222,14 @@ class EmailNotification
$selectParams = $this->$methodName();
$selectParams['whereClause'][] = $where;
$sqlArr[] = $this->entityManager->getQuery()->createSelectQuery('Notification', $selectParams);
$sqlArr[] = $this->entityManager->getQueryComposer()->createSelectQuery('Notification', $selectParams);
}
$maxCount = intval(self::PROCESS_MAX_COUNT);
$sql = '' . implode(' UNION ', $sqlArr) . " ORDER BY number LIMIT 0, {$maxCount}";
$notificationList = $this->entityManager->getRepository('Notification')->findByQuery($sql);
$notificationList = $this->entityManager->getRepository('Notification')->findBySql($sql);
foreach ($notificationList as $notification) {
$notification->set('emailIsProcessed', true);
+2 -2
View File
@@ -98,7 +98,7 @@ class GlobalSearch implements
$selectParams['orderBy'] = [['name']];
}
$itemSql = $this->entityManager->getQuery()->createSelectQuery($entityType, $selectParams);
$itemSql = $this->entityManager->getQueryComposer()->createSelectQuery($entityType, $selectParams);
$unionPartList[] = "(\n" . $itemSql . "\n)";
}
@@ -127,7 +127,7 @@ class GlobalSearch implements
$sql .= "\nORDER BY name";
}
$sql = $this->entityManager->getQuery()->limit($sql, $offset, $maxSize + 1);
$sql = $this->entityManager->getQueryComposer()->limit($sql, $offset, $maxSize + 1);
$sth = $pdo->prepare($sql);
$sth->execute();
+3 -2
View File
@@ -643,14 +643,15 @@ class InboundEmail extends \Espo\Services\Record implements
$case->set('accountId', $email->get('accountId'));
}
$contact = $this->getEntityManager()->getRepository('Contact')->join([['emailAddresses', 'emailAddressesMultiple']])->where([
$contact = $this->getEntityManager()->getRepository('Contact')->join('emailAddresses', 'emailAddressesMultiple')->where([
'emailAddressesMultiple.id' => $email->get('fromEmailAddressId')
])->findOne();
if ($contact) {
$case->set('contactId', $contact->id);
} else {
if (!$case->get('accountId')) {
$lead = $this->getEntityManager()->getRepository('Lead')->join([['emailAddresses', 'emailAddressesMultiple']])->where([
$lead = $this->getEntityManager()->getRepository('Lead')
->join('emailAddresses', 'emailAddressesMultiple')->where([
'emailAddressesMultiple.id' => $email->get('fromEmailAddressId')
])->findOne();
if ($lead) {
+7 -5
View File
@@ -80,7 +80,6 @@ class Notification extends \Espo\Services\Record implements
$collection = $this->entityManager->createCollection();
$userList = $this->getEntityManager()->getRepository('User')
->select(['id'])
->where([
@@ -176,15 +175,16 @@ class Notification extends \Espo\Services\Record implements
public function markAllRead(string $userId)
{
$select = $this->entityManager->createSelectBuilder()
$update = $this->entityManager->getQueryBuilder()->update()
->from('Notification')
->set(['read' => true])
->where([
'userId' => $userId,
'read' => false,
])
->build();
$this->entityManager->getQueryExecutor()->update($select, ['read' => true]);
$this->entityManager->getQueryExecutor()->run($update);
return true;
}
@@ -276,13 +276,15 @@ class Notification extends \Espo\Services\Record implements
}
if (!empty($ids)) {
$select = $this->entityManager->createSelectBuilder()
$update = $this->entityManager->getQueryBuilder()
->update()
->from('Notification')
->set(['read' => true])
->where([
'id' => $ids,
])
->build();
$this->entityManager->getQueryExecutor()->update($select, ['read' => true]);
$this->entityManager->getQueryExecutor()->run($update);
}
return new RecordCollection($collection, $count);
+37 -14
View File
@@ -385,22 +385,28 @@ class Record implements Crud,
public function getEntity(?string $id = null) : ?Entity
{
if (!is_null($id)) {
$selectParams = [];
if ($this->getUser()->isAdmin()) {
$selectParams['withDeleted'] = true;
}
$entity = $this->getRepository()->getById($id, $selectParams);
} else {
$entity = $this->getRepository()->getNew();
if ($id === null) {
return $this->getRepository()->getNew();
}
if ($entity && !is_null($id)) {
$this->loadAdditionalFields($entity);
if (!$this->getAcl()->check($entity, 'read')) throw new ForbiddenSilent("No read access.");
$this->prepareEntityForOutput($entity);
$entity = $this->getRepository()->getById($id);
if (!$entity && $this->getUser()->isAdmin()) {
$entity = $this->getEntityEvenDeleted($id);
}
if (!$entity) {
return null;
}
$this->loadAdditionalFields($entity);
if (!$this->getAcl()->check($entity, 'read')) {
throw new ForbiddenSilent("No read access.");
}
$this->prepareEntityForOutput($entity);
return $entity;
}
@@ -1325,11 +1331,25 @@ class Record implements Crud,
];
}
protected function getEntityEvenDeleted(string $id) : ?Entity
{
$query = $this->entityManager->getQueryBuilder()
->select()
->from($this->entityType)
->where([
'id' => $id,
])
->withDeleted()
->build();
return $this->getRepository()->findOne($query);
}
public function restoreDeleted(string $id)
{
if (!$this->getUser()->isAdmin()) throw new Forbidden();
$entity = $this->getRepository()->getById($id, ['withDeleted' => true]);
$entity = $this->getEntityEvenDeleted($id);
if (!$entity) throw new NotFound();
if (!$entity->get('deleted')) throw new Forbidden();
@@ -2095,7 +2115,10 @@ class Record implements Crud,
$this->getEntityManager()->getRepository($this->getEntityType())->handleSelectParams($selectParams);
$collection = $this->getEntityManager()->createSthCollection($this->getEntityType(), $selectParams);
$collection = $this->getEntityManager()->getCollectionFactroy()->createFromQuery(
$this->getEntityType(),
SelectQuery::fromRaw($selectParams)
);
}
$attributeListToSkip = [
+59 -63
View File
@@ -35,6 +35,7 @@ use Espo\Core\Exceptions\NotFound;
use Espo\ORM\{
Entity,
EntityCollection,
QueryParams\Select,
};
use Espo\Entities\User;
@@ -240,17 +241,17 @@ class Stream
return;
}
$pdo = $this->entityManager->getPDO();
$sql = $this->entityManager->getQuery()->createDeleteQuery('Subscription', [
'whereClause' => [
$delete = $this->entityManager->getQueryBuilder()
->delete()
->from('Subscription')
->where([
'userId' => $userIdList,
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
],
]);
])
->build();
$pdo->query($sql);
$this->entityManager->getQueryExecutor()->run($delete);
$collection = new EntityCollection();
@@ -264,7 +265,7 @@ class Stream
$collection[] = $subscription;
}
$this->entityManager->getMapper('RDB')->massInsert($collection);
$this->entityManager->getMapper()->massInsert($collection);
}
public function followEntity(Entity $entity, string $userId, bool $skipAclCheck = false)
@@ -309,17 +310,17 @@ class Stream
return false;
}
$pdo = $this->entityManager->getPDO();
$sql = $this->entityManager->getQuery()->createDeleteQuery('Subscription', [
'whereClause' => [
$delete = $this->entityManager->getQueryBuilder()
->delete()
->from('Subscription')
->where([
'userId' => $userId,
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
],
]);
])
->build();
$pdo->query($sql);
$this->entityManager->getQueryExecutor()->run($delete);
return true;
}
@@ -330,16 +331,16 @@ class Stream
return;
}
$pdo = $this->entityManager->getPDO();
$sql = $this->entityManager->getQuery()->createDeleteQuery('Subscription', [
'whereClause' => [
$delete = $this->entityManager->getQueryBuilder()
->delete()
->from('Subscription')
->where([
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
],
]);
])
->build();
$pdo->query($sql);
$this->entityManager->getQueryExecutor()->run($delete);
}
public function findUserStream($userId, $params = [])
@@ -633,11 +634,7 @@ class Stream
if ($user->isPortal()) {
$portalIdList = $user->getLinkMultipleIdList('portals');
$portalIdQuotedList = [];
foreach ($portalIdList as $portalId) {
$portalIdQuotedList[] = $pdo->quote($portalId);
}
if (!empty($portalIdQuotedList)) {
if (!empty($portalIdList)) {
$selectParamsList[] = [
'select' => $select,
'leftJoins' => ['portals', 'createdBy'],
@@ -730,16 +727,18 @@ class Stream
}
}
$sqlPartList[] = "(\n" . $this->entityManager->getQuery()->createSelectQuery('Note', $selectParams) . "\n)";
$sqlPartList[] = "(\n" . $this->entityManager->getQueryComposer()->createSelectQuery('Note', $selectParams) . "\n)";
}
$sql = implode("\n UNION ALL \n", $sqlPartList) . "
ORDER BY number DESC
";
$sql = $this->entityManager->getQuery()->limit($sql, $offset, $maxSize + 1);
$sql = $this->entityManager->getQueryComposer()->limit($sql, $offset, $maxSize + 1);
$collection = $this->entityManager->getRepository('Note')->findByQuery($sql);
$sthCollection = $this->entityManager->getRepository('Note')->findBySql($sql);
$collection = $this->entityManager->getCollectionFactory()->createFromSthCollection($sthCollection);
foreach ($collection as $e) {
$this->loadNoteAdditionalFields($e);
@@ -1090,9 +1089,15 @@ class Stream
$selectManager->applyLimit($offset, $maxSize + 1, $selectParams);
$sql = $this->entityManager->getQuery()->createSelectQuery('Note', $selectParams);
$selectParams['from'] = 'Note';
$collection = $this->entityManager->getRepository('Note')->findByQuery($sql);
$select = Select::fromRaw($selectParams);
$sql = $this->entityManager->getQueryComposer()->compose($select);
$sthCollection = $this->entityManager->getRepository('Note')->findBySql($sql);
$collection = $this->entityManager->getCollectionFactory()->createFromSthCollection($sthCollection);
foreach ($collection as $e) {
$this->loadNoteAdditionalFields($e);
@@ -1611,9 +1616,8 @@ class Stream
$selectParams['select'] = $selectAttributeList;
}
$query = $this->entityManager->getQuery();
$query = $this->entityManager->getQueryComposer();
$selectParams['t'] = true;
$sql = $query->createSelectQuery('User', $selectParams);
$collection = $this->entityManager->getRepository('User')->find($selectParams);
$total = $this->entityManager->getRepository('User')->count($selectParams);
@@ -1623,49 +1627,41 @@ class Stream
public function getEntityFollowers(Entity $entity, $offset = 0, $limit = false)
{
$query = $this->entityManager->getQuery();
$pdo = $this->entityManager->getPDO();
if (!$limit) {
$limit = 200;
}
$sql = $query->createSelectQuery('User', [
'select' => ['id', 'name'],
'joins' => [
$userList = $this->entityManager->getRepository('User')
->select(['id', 'name'])
->join(
'Subscription',
'subscription',
[
'Subscription',
'subscription',
[
'subscription.userId=:' => 'user.id',
'subscription.entityId' => $entity->id,
'subscription.entityType' => $entity->getEntityType()
]
'subscription.userId=:' => 'user.id',
'subscription.entityId' => $entity->id,
'subscription.entityType' => $entity->getEntityType()
]
],
'offset' => $offset,
'limit' => $limit,
'whereClause' => [
'isActive' => true
],
'orderBy' => [
)
->limit($offset, $limit)
->where([
'isActive' => true,
])
->order([
['LIST:id:' . $this->user->id, 'DESC'],
['name']
]
]);
$sth = $pdo->prepare($sql);
$sth->execute();
['name'],
])
->find();
$data = [
'idList' => [],
'nameMap' => (object) []
];
while ($row = $sth->fetch(\PDO::FETCH_ASSOC)) {
$id = $row['id'];
foreach ($userList as $user) {
$id = $user->id;
$data['idList'][] = $id;
$data['nameMap']->$id = $row['name'];
$data['nameMap']->$id = $user->get('name');
}
return $data;
+5 -3
View File
@@ -27,9 +27,11 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
use Espo\ORM\DB\MysqlMapper;
use Espo\ORM\DB\Query\Mysql as Query;
use Espo\ORM\EntityFactory;
use Espo\ORM\{
DB\MysqlMapper,
DB\Query\Mysql as Query,
EntityFactory,
};
use Espo\Entities\Job;
@@ -27,19 +27,26 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
use Espo\ORM\DB\MysqlMapper;
use Espo\ORM\DB\Query\MysqlQuery as Query;
use Espo\ORM\EntityFactory;
use Espo\ORM\EntityCollection;
use Espo\ORM\{
Metadata,
EntityManager,
EntityFactory,
CollectionFactory,
EntityCollection,
QueryComposer\MysqlQueryComposer as QueryComposer,
Mapper\MysqlMapper,
QueryParams\Select,
};
use Espo\Entities\Post;
use Espo\Entities\Comment;
use Espo\Entities\Tag;
use Espo\Entities\Note;
use Espo\Entities\Job;
use Espo\Entities\{
Post,
Comment,
Tag,
Note,
Job,
};
require_once 'tests/unit/testData/DB/Entities.php';
require_once 'tests/unit/testData/DB/MockPDO.php';
require_once 'tests/unit/testData/DB/MockDBResult.php';
class DBMapperTest extends \PHPUnit\Framework\TestCase
@@ -53,7 +60,7 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
protected function setUp() : void
{
$this->pdo = $this->createMock('MockPDO');
$this->pdo = $this->getMockBuilder(PDO::class)->disableOriginalConstructor()->getMock();
$this->pdo
->expects($this->any())
->method('quote')
@@ -62,33 +69,49 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
return "'" . $args[0] . "'";
}));
$metadata = $this->getMockBuilder('Espo\\ORM\\Metadata')->disableOriginalConstructor()->getMock();
$metadata = $this->getMockBuilder(Metadata::class)->disableOriginalConstructor()->getMock();
$metadata
->method('get')
->will($this->returnValue(false));
$entityManager = $this->getMockBuilder('Espo\\ORM\\EntityManager')->disableOriginalConstructor()->getMock();
$entityManager = $this->getMockBuilder(EntityManager::class)->disableOriginalConstructor()->getMock();
$entityManager
->method('getMetadata')
->will($this->returnValue($metadata));
$this->entityFactory = $this->getMockBuilder('Espo\\ORM\\EntityFactory')->disableOriginalConstructor()->getMock();
$this->entityFactory = $this->getMockBuilder(EntityFactory::class)->disableOriginalConstructor()->getMock();
$this->entityFactory
->expects($this->any())
->method('create')
->will($this->returnCallback(function () use ($entityManager) {
$args = func_get_args();
$className = "\\Espo\\Entities\\" . $args[0];
return new $className($args[0], [], $entityManager);
}));
->will($this->returnCallback(
function () use ($entityManager) {
$args = func_get_args();
$className = "Espo\\Entities\\" . $args[0];
return new $className($args[0], [], $entityManager);
}
));
$entityFactory = $this->entityFactory;
$this->collectionFactory = $this->getMockBuilder(CollectionFactory::class)->disableOriginalConstructor()->getMock();
$this->collectionFactory
->expects($this->any())
->method('create')
->will($this->returnCallback(
function () use ($entityFactory) {
$args = func_get_args();
$entityType = $args[0] ?? null;
$dataList = $args[1] ?? [];
return new EntityCollection($dataList, $entityType, $entityFactory);
}
));
$this->metadata = $this->getMockBuilder('Espo\\ORM\\Metadata')->disableOriginalConstructor()->getMock();
$this->query = new Query($this->pdo, $this->entityFactory, $this->metadata);
$this->query = new QueryComposer($this->pdo, $this->entityFactory, $this->metadata);
$this->db = new MysqlMapper($this->pdo, $this->entityFactory, $this->query, $this->metadata);
$entityFactory = $this->entityFactory;
$this->db = new MysqlMapper($this->pdo, $this->entityFactory, $this->collectionFactory, $this->query, $this->metadata);
$this->post = $entityFactory->create('Post');
$this->comment = $entityFactory->create('Comment');
@@ -120,30 +143,43 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
->will($this->returnValue($return));
}
public function testSelectById()
public function testSelectOne()
{
$query =
"SELECT post.id AS `id`, post.name AS `name`, NULLIF(TRIM(CONCAT(IFNULL(createdBy.salutation_name, ''), IFNULL(createdBy.first_name, ''), ' ', IFNULL(createdBy.last_name, ''))), '') AS `createdByName`, post.created_by_id AS `createdById`, post.deleted AS `deleted` ".
"SELECT post.id AS `id`, post.name AS `name`, NULLIF(TRIM(CONCAT(IFNULL(createdBy.salutation_name, ''), " .
"IFNULL(createdBy.first_name, ''), ' ', IFNULL(createdBy.last_name, ''))), '') AS `createdByName`, ".
"post.created_by_id AS `createdById`, post.deleted AS `deleted` ".
"FROM `post` ".
"LEFT JOIN `user` AS `createdBy` ON post.created_by_id = createdBy.id " .
"WHERE post.id = '1' AND post.deleted = 0";
$return = new MockDBResult(array(
array(
$return = new MockDBResult([
[
'id' => '1',
'name' => 'test',
'deleted' => false,
),
));
],
]);
$this->mockQuery($query, $return);
$this->db->selectById($this->post, '1');
$this->assertEquals($this->post->id, '1');
$select = Select::fromRaw([
'from' => 'Post',
'whereClause' => [
'id' => '1',
],
]);
$post = $this->db->selectOne($select);
$this->assertEquals($post->id, '1');
}
public function testSelect1()
{
$query =
"SELECT post.id AS `id`, post.name AS `name`, NULLIF(TRIM(CONCAT(IFNULL(createdBy.salutation_name, ''), IFNULL(createdBy.first_name, ''), ' ', IFNULL(createdBy.last_name, ''))), '') AS `createdByName`, post.created_by_id AS `createdById`, post.deleted AS `deleted` ".
"SELECT post.id AS `id`, post.name AS `name`, NULLIF(TRIM(CONCAT(IFNULL(createdBy.salutation_name, ''), " .
"IFNULL(createdBy.first_name, ''), ' ', IFNULL(createdBy.last_name, ''))), '') AS `createdByName`, " .
"post.created_by_id AS `createdById`, post.deleted AS `deleted` ".
"FROM `post` ".
"LEFT JOIN `user` AS `createdBy` ON post.created_by_id = createdBy.id " .
"JOIN `post_tag` AS `tagsMiddle` ON post.id = tagsMiddle.post_id AND tagsMiddle.deleted = 0 ".
@@ -152,39 +188,41 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
"WHERE post.name = 'test_1' AND (post.id = '100' OR post.name LIKE 'test_%') AND tags.name = 'yoTag' AND post.deleted = 0 ".
"ORDER BY post.name DESC ".
"LIMIT 0, 10";
$return = new MockDBResult(array(
array(
$return = new MockDBResult([
[
'id' => '2',
'name' => 'test_2',
'deleted' => false,
),
array(
],
[
'id' => '1',
'name' => 'test_1',
'deleted' => false,
),
));
],
]);
$this->mockQuery($query, $return);
$selectParams = array(
'whereClause' => array(
$selectParams = [
'from' => 'Post',
'whereClause' => [
'name' => 'test_1',
'OR' => array(
'OR' => [
'id' => '100',
'name*' => 'test_%',
),
],
'tags.name' => 'yoTag',
),
],
'order' => 'DESC',
'orderBy' => 'name',
'limit' => 10,
'joins' => array(
'joins' => [
'tags',
'comments',
),
);
$list = $this->db->select($this->post, $selectParams);
],
];
$list = $this->db->select(Select::fromRaw($selectParams));
$this->assertTrue($list[0] instanceof Post);
$this->assertTrue(isset($list[0]->id));
@@ -194,51 +232,65 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
public function testSelectWithSpecifiedParams()
{
$query =
"SELECT contact.id AS `id`, TRIM(CONCAT(contact.first_name, ' ', contact.last_name)) AS `name`, contact.first_name AS `firstName`, contact.last_name AS `lastName`, contact.deleted AS `deleted` ".
"SELECT contact.id AS `id`, TRIM(CONCAT(contact.first_name, ' ', contact.last_name)) AS `name`, " .
"contact.first_name AS `firstName`, contact.last_name AS `lastName`, contact.deleted AS `deleted` ".
"FROM `contact` ".
"WHERE (contact.first_name LIKE 'test%' OR contact.last_name LIKE 'test%' OR CONCAT(contact.first_name, ' ', contact.last_name) LIKE 'test%') AND contact.deleted = 0 ".
"WHERE " .
"(contact.first_name LIKE 'test%' OR contact.last_name LIKE 'test%' OR CONCAT(contact.first_name, ' ', contact.last_name) " .
"LIKE 'test%') ".
"AND contact.deleted = 0 ".
"ORDER BY contact.first_name DESC, contact.last_name DESC ".
"LIMIT 0, 10";
$return = new MockDBResult(array(
array(
$return = new MockDBResult([
[
'id' => '1',
'name' => 'test',
'deleted' => false,
),
));
],
]);
$this->mockQuery($query, $return);
$selectParams = array(
'whereClause' => array(
$selectParams = [
'from' => 'Contact',
'whereClause' => [
'name*' => 'test%',
),
],
'order' => 'DESC',
'orderBy' => 'name',
'limit' => 10
);
$list = $this->db->select($this->contact, $selectParams);
'limit' => 10,
];
$list = $this->db->select(Select::fromRaw($selectParams));
}
public function testJoin()
{
$query =
"SELECT comment.id AS `id`, comment.post_id AS `postId`, post.name AS `postName`, comment.name AS `name`, comment.deleted AS `deleted` ".
"SELECT comment.id AS `id`, comment.post_id AS `postId`, post.name AS `postName`, comment.name AS `name`, " .
"comment.deleted AS `deleted` ".
"FROM `comment` ".
"LEFT JOIN `post` AS `post` ON comment.post_id = post.id ".
"WHERE comment.deleted = 0";
$return = new MockDBResult(array(
array(
$return = new MockDBResult([
[
'id' => '11',
'postId' => '1',
'postName' => 'test',
'name' => 'test_comment',
'deleted' => false,
),
));
],
]);
$this->mockQuery($query, $return);
$list = $this->db->select($this->comment);
$selectParams = [
'from' => 'Comment',
];
$list = $this->db->select(Select::fromRaw($selectParams));
$this->assertTrue($list[0] instanceof Comment);
$this->assertTrue($list[0]->has('postName'));
@@ -252,6 +304,7 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
"FROM `tag` ".
"JOIN `post_tag` AS `postTag` ON postTag.tag_id = tag.id AND postTag.post_id = '1' AND postTag.deleted = 0 ".
"WHERE tag.deleted = 0";
$return = new MockDBResult([
[
'id' => '1',
@@ -259,8 +312,10 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
'deleted' => false,
],
]);
$this->mockQuery($query, $return);
$this->post->id = '1';
$list = $this->db->selectRelated($this->post, 'tags');
$this->assertTrue($list[0] instanceof Tag);
@@ -284,11 +339,15 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
$this->mockQuery($query, $return);
$this->account->id = '1';
$list = $this->db->selectRelated($this->account, 'teams', [
'additionalColumns' => [
'teamId' => 'stub',
$select = Select::fromRaw([
'from' => 'Team',
'select' => [
'*',
['entityTeam.teamId', 'stub'],
],
]);
$list = $this->db->selectRelated($this->account, 'teams', $select);
}
public function testSelectRelatedHasChildren()
@@ -298,13 +357,15 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
"note.id AS `id`, note.name AS `name`, note.parent_id AS `parentId`, note.parent_type AS `parentType`, note.deleted AS `deleted` ".
"FROM `note` ".
"WHERE note.parent_id = '1' AND note.parent_type = 'Post' AND note.deleted = 0";
$return = new MockDBResult(array(
array(
$return = new MockDBResult([
[
'id' => '1',
'name' => 'test',
'deleted' => false,
),
));
],
]);
$this->mockQuery($query, $return);
$this->post->id = '1';
$list = $this->db->selectRelated($this->post, 'notes');
@@ -343,7 +404,6 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
$this->assertEquals($post->get('name'), 'test');
}
public function testCountRelated()
{
$query =
@@ -351,14 +411,17 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
"FROM `tag` ".
"JOIN `post_tag` AS `postTag` ON postTag.tag_id = tag.id AND postTag.post_id = '1' AND postTag.deleted = 0 ".
"WHERE tag.deleted = 0";
$return = new MockDBResult([
[
'value' => 1,
],
]);
$this->mockQuery($query, $return);
$this->post->id = '1';
$count = $this->db->countRelated($this->post, 'tags');
$this->assertEquals($count, 1);
@@ -368,6 +431,7 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
{
$query = "INSERT INTO `post` (`id`, `name`) VALUES ('1', 'test')";
$return = true;
$this->mockQuery($query, $return);
$this->post->reset();
@@ -509,6 +573,7 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
$this->note->id = 'noteId';
$this->post->id = 'postId';
$this->db->unrelate($this->note, 'parent', $this->post);
}
@@ -533,6 +598,7 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
$this->mockQuery($query, true);
$this->post->id = 'postId';
$this->db->unrelateById($this->post, 'comments', 'commentId');
}
@@ -545,6 +611,7 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
$this->mockQuery($query, true);
$this->post->id = 'postId';
$this->db->unrelateAll($this->post, 'comments');
}
@@ -571,6 +638,7 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
$this->mockQuery($query, true);
$this->post->id = 'postId';
$this->db->unrelateById($this->post, 'notes', 'noteId');
}
@@ -583,6 +651,7 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
$this->mockQuery($query, true);
$this->post->id = 'postId';
$this->db->unrelateAll($this->post, 'notes');
}
@@ -593,6 +662,7 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
$this->mockQuery($query, $return);
$this->post->id = '1';
$this->db->unrelateById($this->post, 'tags', '100');
}
@@ -603,6 +673,7 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
$this->mockQuery($query, $return);
$this->post->id = '1';
$this->db->unrelateAll($this->post, 'tags');
}
@@ -611,10 +682,11 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
$query =
"UPDATE `entity_team` SET entity_team.deleted = 1 ".
"WHERE entity_team.entity_id = '1' AND entity_team.team_id = '100' AND entity_team.entity_type = 'Account'";
$return = true;
$this->mockQuery($query, $return);
$this->mockQuery($query, true);
$this->account->id = '1';
$this->db->unrelateById($this->account, 'teams', '100');
}
@@ -624,8 +696,7 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
"UPDATE `entity_team` SET entity_team.deleted = 1 ".
"WHERE entity_team.entity_id = '1' AND entity_team.entity_type = 'Account'";
$return = true;
$this->mockQuery($query, $return);
$this->mockQuery($query, true);
$this->account->id = '1';
$this->db->unrelateAll($this->account, 'teams');
@@ -634,8 +705,8 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
public function testUnrelateManyToMany()
{
$query = "UPDATE `post_tag` SET post_tag.deleted = 1 WHERE post_tag.post_id = 'postId' AND post_tag.tag_id = 'tagId'";
$return = true;
$this->mockQuery($query, $return);
$this->mockQuery($query, true);
$this->post->id = 'postId';
$this->tag->id = 'tagId';
@@ -802,7 +873,8 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
"WHERE post_tag.post_id = 'postId' AND post_tag.tag_id = 'tagId'";
$query3 =
"UPDATE `post_tag` SET post_tag.deleted = 0, post_tag.role = 'Test' WHERE post_tag.post_id = 'postId' AND post_tag.tag_id = 'tagId'";
"UPDATE `post_tag` SET post_tag.deleted = 0, post_tag.role = 'Test' " .
"WHERE post_tag.post_id = 'postId' AND post_tag.tag_id = 'tagId'";
$ps = $this->createMock(\PDOStatement::class);
$ps->expects($this->exactly(1))
@@ -896,7 +968,7 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
$this->assertEquals('test', $result);
}
public function testUpdateRelation()
public function testUpdateRelationColumns()
{
$this->post->id = 'postId';
$this->tag->id = 'tagId';
@@ -907,7 +979,7 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
$this->mockQuery($query);
$this->db->updateRelation($this->post, 'tags', 'tagId', [
$this->db->updateRelationColumns($this->post, 'tags', 'tagId', [
'role' => 'test'
]);
}
@@ -922,7 +994,7 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
]);
$this->mockQuery($query, $return);
$value = $this->db->max($this->post, array(), 'id', true);
$value = $this->db->max(Select::fromRaw(['from' => 'Post']), 'id');
$this->assertEquals($value, 10);
}
@@ -938,11 +1010,14 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
$this->post->id = '1';
$this->db->massRelate($this->post, 'tags', [
$select = Select::fromRaw([
'from' => 'Tag',
'whereClause' => [
'name' => 'test',
],
]);
$this->db->massRelate($this->post, 'tags', $select);
}
public function testDeleteFromDb1()
+99
View File
@@ -0,0 +1,99 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
use Espo\ORM\{
Metadata,
};
class MetadataTest extends \PHPUnit\Framework\TestCase
{
protected function setUp() : void
{
}
public function testHas1()
{
$metadata = new Metadata([
'Test' => [],
]);
$this->assertTrue($metadata->has('Test'));
}
public function testHas2()
{
$metadata = new Metadata([
'Test' => [],
]);
$this->assertFalse($metadata->has('Hello'));
}
public function testGet1()
{
$metadata = new Metadata([
'Test' => [
'indexes' => [],
],
]);
$this->assertEquals([], $metadata->get('Test', 'indexes'));
}
public function testGet2()
{
$metadata = new Metadata([
'Test' => [
'relations' => [
'test' => [
'type' => 'hasMany',
],
],
],
]);
$this->assertEquals('hasMany', $metadata->get('Test', 'relations.test.type'));
}
public function testGet3()
{
$metadata = new Metadata([
'Test' => [
'relations' => [
'test' => [
'type' => 'hasMany',
],
],
],
]);
$this->assertEquals('hasMany', $metadata->get('Test', ['relations', 'test', 'type']));
}
}
+126
View File
@@ -0,0 +1,126 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
use Espo\ORM\{
QueryBuilder,
QueryParams\Select,
QueryParams\Insert,
QueryParams\Update,
QueryParams\Delete,
};
class QueryBuilderTest extends \PHPUnit\Framework\TestCase
{
protected function setUp() : void
{
$this->queryBuilder = new QueryBuilder();
}
public function testClone1()
{
$select = $this->queryBuilder
->select()
->from('Test')
->build();
$clonedSelect = $this->queryBuilder
->clone($select)
->distinct()
->build();
$this->assertNotSame($clonedSelect, $select);
$this->assertInstanceOf(Select::class, $clonedSelect);
}
public function testInsert1()
{
$insert = $this->queryBuilder
->insert()
->into('Test')
->columns(['col1'])
->values(['col1' => '1'])
->build();
$this->assertInstanceOf(Insert::class, $insert);
}
public function testInsert2()
{
$select = $this->queryBuilder
->select()
->from('Test')
->build();
$insert = $this->queryBuilder
->insert()
->into('Test')
->columns(['col1'])
->valuesQuery($select)
->build();
$this->assertInstanceOf(Insert::class, $insert);
}
public function testDelete1()
{
$delete = $this->queryBuilder
->delete()
->from('Test')
->where(['col1' => '1'])
->limit(1)
->build();
$this->assertInstanceOf(Delete::class, $delete);
}
public function testUpdate1()
{
$update = $this->queryBuilder
->update()
->from('Test')
->set(['col1' => '2'])
->where(['col1' => '1'])
->limit(1)
->build();
$this->assertInstanceOf(Update::class, $update);
}
public function testUpdateNoSet()
{
$this->expectException(\RuntimeException::class);
$update = $this->queryBuilder
->update()
->from('Test')
->where(['col1' => '1'])
->limit(1)
->build();
}
}
@@ -0,0 +1,241 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\unit\Espo\ORM\QueryParams;
use Espo\ORM\{
EntityManager,
QueryParams\Select,
QueryParams\SelectBuilder,
};
class SelectBuilderTest extends \PHPUnit\Framework\TestCase
{
protected function setUp() : void
{
$this->builder = new SelectBuilder();
}
public function testFrom()
{
$params = $this->builder
->from('Test')
->build()
->getRawParams();
$this->assertEquals('Test', $params['from']);
}
public function testSelect1()
{
$select = $this->builder
->from('Test')
->select(['id', 'name'])
->select('test')
->build();
$this->assertEquals(['id', 'name', 'test'], $select->getSelect());
}
public function testSelect2()
{
$select = $this->builder
->from('Test')
->select('test')
->select(['id', 'name'])
->build();
$this->assertEquals(['id', 'name'], $select->getSelect());
}
public function testSelect3()
{
$select = $this->builder
->from('Test')
->select('test', 'hello')
->build();
$this->assertEquals([['test', 'hello']], $select->getSelect());
}
public function testCloneNotSame()
{
$builder = new SelectBuilder();
$select = $builder
->from('Test')
->build();
$builder = new SelectBuilder();
$selectCloned = $builder
->clone($select)
->build();
$this->assertNotSame($selectCloned, $select);
}
public function testWhereNull1()
{
$select = $this->builder
->from('Test')
->where(['test' => null])
->build();
$raw = $select->getRawParams();
$this->assertEquals(['test' => null], $raw['whereClause']);
}
public function testWhereNull2()
{
$select = $this->builder
->from('Test')
->where('test', null)
->build();
$raw = $select->getRawParams();
$this->assertEquals(['test' => null], $raw['whereClause']);
}
public function testGroupBy()
{
$select = $this->builder
->from('Test')
->having(['test' => null])
->groupBy(['test'])
->build();
$raw = $select->getRawParams();
$this->assertEquals(['test' => null], $raw['havingClause']);
$this->assertEquals(['test'], $raw['groupBy']);
}
public function testClone()
{
$builder = new SelectBuilder();
$select = $builder
->from('Test')
->where('test1', '1')
->build();
$builder = new SelectBuilder();
$selectCloned = $builder
->clone($select)
->distinct()
->where('test2', '2')
->build();
$params = $select->getRawParams();
$paramsCloned = $selectCloned->getRawParams();
$this->assertTrue($paramsCloned['distinct']);
$this->assertFalse($params['distinct'] ?? false);
$this->assertEquals(['test1' =>'1'], $params['whereClause']);
$this->assertEquals(['test1' => '1', 'test2' => '2'], $paramsCloned['whereClause']);
}
public function testCloneException()
{
$builder = new SelectBuilder();
$select = $builder
->from('Test')
->where('test1', '1')
->build();
$builder = new SelectBuilder();
$this->expectException(\RuntimeException::class);
$builder
->from('Test')
->clone($select);
}
public function testWhereSameKeys1()
{
$builder = new SelectBuilder();
$select = $builder
->from('Test')
->where(['test' => '1'])
->where(['test' => '2'])
->build();
$raw = $select->getRawParams();
$expected = [
'test' => '1',
['test' => '2'],
];
$this->assertEquals($expected, $raw['whereClause']);
}
public function testWhereSameKeys2()
{
$builder = new SelectBuilder();
$select = $builder
->from('Test')
->where([
'OR' => [
'test' => '1'
],
])
->where([
'OR' => [
'test' => '2'
],
])
->build();
$raw = $select->getRawParams();
$expected = [
'OR' => [
'test' => '1'
],
[
'OR' => [
'test' => '2'
],
]
];
$this->assertEquals($expected, $raw['whereClause']);
}
}
File diff suppressed because it is too large Load Diff
+170 -136
View File
@@ -12,23 +12,23 @@ class TEntity extends BaseEntity
class Account extends TEntity
{
public $fields = array(
'id' => array(
public $fields = [
'id' => [
'type' => Entity::ID,
),
'name' => array(
],
'name' => [
'type' => Entity::VARCHAR,
'len' => 255,
),
'deleted' => array(
],
'deleted' => [
'type' => Entity::BOOL,
'default' => 0,
),
],
'stub' => [
'type' => 'varchar',
'notStorable' => true,
],
);
];
public $relations = [
'teams' => [
'type' => Entity::MANY_MANY,
@@ -38,26 +38,26 @@ class Account extends TEntity
'entityId',
'teamId',
],
'conditions' => ['entityType' => 'Account']
'conditions' => ['entityType' => 'Account'],
],
];
}
class Team extends TEntity
{
public $fields = array(
'id' => array(
public $fields = [
'id' => [
'type' => Entity::ID,
),
'name' => array(
],
'name' => [
'type' => Entity::VARCHAR,
'len' => 255,
),
'deleted' => array(
],
'deleted' => [
'type' => Entity::BOOL,
'default' => 0,
),
);
],
];
public $relations = [];
}
@@ -90,133 +90,134 @@ class EntityTeam extends TEntity
class Contact extends TEntity
{
public $fields = array(
'id' => array(
public $fields = [
'id' => [
'type' => Entity::ID,
),
'name' => array(
],
'name' => [
'type' => Entity::VARCHAR,
'notStorable' => true,
'select' => "TRIM(CONCAT(contact.first_name, ' ', contact.last_name))",
'where' => array(
'LIKE' => "(contact.first_name LIKE {value} OR contact.last_name LIKE {value} OR CONCAT(contact.first_name, ' ', contact.last_name) LIKE {value})",
),
'where' => [
'LIKE' => "(contact.first_name LIKE {value} OR contact.last_name LIKE {value} OR "
."CONCAT(contact.first_name, ' ', contact.last_name) LIKE {value})",
],
'orderBy' => "contact.first_name {direction}, contact.last_name {direction}",
),
'firstName' => array(
],
'firstName' => [
'type' => Entity::VARCHAR,
),
'lastName' => array(
],
'lastName' => [
'type' => Entity::VARCHAR,
),
'deleted' => array(
],
'deleted' => [
'type' => Entity::BOOL,
'default' => 0,
),
);
public $relations = array();
],
];
public $relations = [];
}
class Post extends TEntity
{
public $fields = array(
'id' => array(
public $fields = [
'id' => [
'type' => Entity::ID,
),
'name' => array(
],
'name' => [
'type' => Entity::VARCHAR,
'len' => 255,
),
'privateField' => array(
],
'privateField' => [
'notStorable' => true,
),
'createdByName' => array(
],
'createdByName' => [
'type' => Entity::FOREIGN,
'relation' => 'createdBy',
'foreign' => array('salutationName', 'firstName', ' ', 'lastName'),
),
'createdById' => array(
'foreign' => ['salutationName', 'firstName', ' ', 'lastName'],
],
'createdById' => [
'type' => Entity::FOREIGN_ID,
),
'deleted' => array(
],
'deleted' => [
'type' => Entity::BOOL,
'default' => 0,
),
);
public $relations = array(
'tags' => array(
],
];
public $relations = [
'tags' => [
'type' => Entity::MANY_MANY,
'entity' => 'Tag',
'relationName' => 'PostTag',
'key' => 'id',
'foreignKey' => 'id',
'midKeys' => array(
'midKeys' => [
'postId',
'tagId',
),
],
'additionalColumns' => [
'role' => [
'type' => Entity::VARCHAR,
],
],
),
'comments' => array(
],
'comments' => [
'type' => Entity::HAS_MANY,
'entity' => 'Comment',
'foreignKey' => 'postId',
),
'createdBy' => array(
],
'createdBy' => [
'type' => Entity::BELONGS_TO,
'entity' => 'User',
'key' => 'createdById',
),
'notes' => array(
],
'notes' => [
'type' => Entity::HAS_CHILDREN,
'entity' => 'Note',
'foreignKey' => 'parentId',
'foreignType' => 'parentType',
),
],
'postData' => [
'type' => Entity::HAS_ONE,
'entity' => 'PostData',
'foreign' => 'post',
'noJoin' => true,
],
);
];
}
class Comment extends TEntity
{
public $fields = array(
'id' => array(
'type' => Entity::ID,
),
'postId' => array(
'type' => Entity::FOREIGN_ID,
),
'postName' => array(
'type' => Entity::FOREIGN,
'relation' => 'post',
'foreign' => 'name',
),
'name' => array(
'type' => Entity::VARCHAR,
'len' => 255,
),
'deleted' => array(
'type' => Entity::BOOL,
'default' => 0,
),
);
public $fields = [
'id' => [
'type' => Entity::ID,
],
'postId' => [
'type' => Entity::FOREIGN_ID,
],
'postName' => [
'type' => Entity::FOREIGN,
'relation' => 'post',
'foreign' => 'name',
],
'name' => [
'type' => Entity::VARCHAR,
'len' => 255,
],
'deleted' => [
'type' => Entity::BOOL,
'default' => 0,
],
];
public $relations = array(
'post' => array(
'type' => Entity::BELONGS_TO,
'entity' => 'Post',
'key' => 'postId',
'foreignKey' => 'id',
),
);
public $relations = [
'post' => [
'type' => Entity::BELONGS_TO,
'entity' => 'Post',
'key' => 'postId',
'foreignKey' => 'id',
],
];
}
class PostData extends TEntity
@@ -247,19 +248,19 @@ class PostData extends TEntity
class Tag extends TEntity
{
public $fields = array(
'id' => array(
public $fields = [
'id' => [
'type' => Entity::ID,
),
'name' => array(
],
'name' => [
'type' => Entity::VARCHAR,
'len' => 50,
),
'deleted' => array(
],
'deleted' => [
'type' => Entity::BOOL,
'default' => 0,
),
);
],
];
}
class PostTag extends TEntity
@@ -290,29 +291,29 @@ class PostTag extends TEntity
class Note extends TEntity
{
public $fields = array(
'id' => array(
public $fields = [
'id' => [
'type' => Entity::ID,
),
'name' => array(
],
'name' => [
'type' => Entity::VARCHAR,
'len' => 50,
),
'parentId' => array(
],
'parentId' => [
'type' => Entity::FOREIGN_ID,
),
'parentType' => array(
],
'parentType' => [
'type' => Entity::FOREIGN_TYPE,
),
'parentName' => array(
],
'parentName' => [
'type' => Entity::VARCHAR,
'notStorable' => true,
),
'deleted' => array(
],
'deleted' => [
'type' => Entity::BOOL,
'default' => 0,
),
);
],
];
public $relations = [
'parent' => [
@@ -326,49 +327,82 @@ class Note extends TEntity
class Article extends TEntity
{
public $fields = array(
'id' => array(
public $fields = [
'id' => [
'type' => Entity::ID
),
'name' => array(
],
'name' => [
'type' => Entity::VARCHAR,
'len' => 50,
),
'description' => array(
],
'description' => [
'type' => Entity::TEXT
),
'deleted' => array(
],
'deleted' => [
'type' => Entity::BOOL,
'default' => 0
)
);
]
];
}
class Job extends TEntity
{
public $fields = array(
'id' => array(
public $fields = [
'id' => [
'type' => Entity::ID
),
'string' => array(
],
'string' => [
'type' => Entity::VARCHAR,
'len' => 50
),
'array' => array(
],
'array' => [
'type' => Entity::JSON_ARRAY
),
'arrayUnordered' => array(
],
'arrayUnordered' => [
'type' => Entity::JSON_ARRAY,
'isUnordered' => true
),
'object' => array(
],
'object' => [
'type' => Entity::JSON_OBJECT
),
'deleted' => array(
],
'deleted' => [
'type' => Entity::BOOL,
'default' => 0
)
);
],
];
}
class Test extends TEntity
{
public $fields = [
'id' => [
'type' => Entity::ID,
],
'name' => [
'type' => Entity::VARCHAR,
'len' => 255,
],
'date' => [
'type' => Entity::DATE
],
'dateTime' => [
'type' => Entity::DATETIME
],
'int' => [
'type' => Entity::INT
],
'float' => [
'type' => Entity::FLOAT
],
'list' => [
'type' => Entity::JSON_ARRAY
],
'object' => [
'type' => Entity::JSON_OBJECT
],
'deleted' => [
'type' => Entity::BOOL,
'default' => 0,
]
];
}