grand orm refactoring
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = [])
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')."'
|
||||
|
||||
@@ -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());
|
||||
|
||||
+1
-1
@@ -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']],
|
||||
|
||||
@@ -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.
|
||||
|
||||
+19
-14
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ namespace Espo\ORM\DB\Query;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
abstract class Base extends BaseQuery
|
||||
abstract class Base extends Espo\ORM\QueryComposer\BaseQueryComposer
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+364
-262
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.
|
||||
+1
-1
@@ -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
|
||||
{
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+309
-193
@@ -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)) {
|
||||
+1
-1
@@ -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
|
||||
{
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+20
-25
@@ -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;
|
||||
}
|
||||
}
|
||||
+7
-6
@@ -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;
|
||||
}
|
||||
}
|
||||
+15
-16
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
-19
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{}
|
||||
}
|
||||
+21
-23
@@ -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;
|
||||
}
|
||||
}
|
||||
+328
-116
@@ -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);
|
||||
}
|
||||
}
|
||||
+12
-7
@@ -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);
|
||||
}
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 = [])
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
@@ -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']));
|
||||
}
|
||||
}
|
||||
+312
-172
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user