Initial commit
This commit is contained in:
89
application/Espo/Core/ORM/AttributeExtractorFactory.php
Normal file
89
application/Espo/Core/ORM/AttributeExtractorFactory.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
|
||||
use Espo\ORM\Metadata as OrmMetadata;
|
||||
use Espo\ORM\Value\AttributeExtractor;
|
||||
use Espo\ORM\Value\AttributeExtractorFactory as AttributeExtractorFactoryInterface;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @template T of object
|
||||
* @implements AttributeExtractorFactoryInterface<T>
|
||||
*/
|
||||
class AttributeExtractorFactory implements AttributeExtractorFactoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private OrmMetadata $ormMetadata,
|
||||
private InjectableFactory $injectableFactory
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return AttributeExtractor<T>
|
||||
*/
|
||||
public function create(string $entityType, string $field): AttributeExtractor
|
||||
{
|
||||
$className = $this->getClassName($entityType, $field);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("Could not get AttributeExtractor for '{$entityType}.{$field}'.");
|
||||
}
|
||||
|
||||
return $this->injectableFactory->createWith($className, ['entityType' => $entityType]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?class-string<AttributeExtractor<T>>
|
||||
*/
|
||||
private function getClassName(string $entityType, string $field): ?string
|
||||
{
|
||||
$fieldDefs = $this->ormMetadata
|
||||
->getDefs()
|
||||
->getEntity($entityType)
|
||||
->getField($field);
|
||||
|
||||
$className = $fieldDefs->getParam('attributeExtractorClassName');
|
||||
|
||||
if ($className) {
|
||||
/** @var class-string<AttributeExtractor<T>> */
|
||||
return $className;
|
||||
}
|
||||
|
||||
$type = $fieldDefs->getType();
|
||||
|
||||
/** @var ?class-string<AttributeExtractor<T>> */
|
||||
return $this->metadata->get(['fields', $type, 'attributeExtractorClassName']);
|
||||
}
|
||||
}
|
||||
161
application/Espo/Core/ORM/ClassNameProvider.php
Normal file
161
application/Espo/Core/ORM/ClassNameProvider.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM;
|
||||
|
||||
use Espo\Core\ORM\Entity as BaseEntity;
|
||||
use Espo\Core\Repositories\Database as DatabaseRepository;
|
||||
use Espo\Core\Utils\ClassFinder;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\Entity as Entity;
|
||||
use Espo\ORM\EventDispatcher;
|
||||
use Espo\ORM\Repository\Repository as Repository;
|
||||
|
||||
class ClassNameProvider
|
||||
{
|
||||
/** @var class-string<Entity> */
|
||||
private const DEFAULT_ENTITY_CLASS_NAME = BaseEntity::class;
|
||||
/** @var class-string<Repository<Entity>> */
|
||||
private const DEFAULT_REPOSITORY_CLASS_NAME = DatabaseRepository::class;
|
||||
|
||||
/** @var array<string, class-string<Entity>> */
|
||||
private array $entityCache = [];
|
||||
|
||||
/** @var array<string, class-string<Repository<Entity>>> */
|
||||
private array $repositoryCache = [];
|
||||
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private ClassFinder $classFinder,
|
||||
EventDispatcher $eventDispatcher,
|
||||
) {
|
||||
$eventDispatcher->subscribeToMetadataUpdate(function () {
|
||||
$this->entityCache = [];
|
||||
$this->repositoryCache = [];
|
||||
|
||||
$this->classFinder->resetRuntimeCache();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entityType
|
||||
* @return class-string<Entity>
|
||||
*/
|
||||
public function getEntityClassName(string $entityType): string
|
||||
{
|
||||
if (!array_key_exists($entityType, $this->entityCache)) {
|
||||
$this->entityCache[$entityType] = $this->findEntityClassName($entityType);
|
||||
}
|
||||
|
||||
return $this->entityCache[$entityType];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entityType
|
||||
* @return class-string<Repository<Entity>>
|
||||
*/
|
||||
public function getRepositoryClassName(string $entityType): string
|
||||
{
|
||||
if (!array_key_exists($entityType, $this->entityCache)) {
|
||||
$this->repositoryCache[$entityType] = $this->findRepositoryClassName($entityType);
|
||||
}
|
||||
|
||||
return $this->repositoryCache[$entityType];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entityType
|
||||
* @return class-string<Entity>
|
||||
*/
|
||||
private function findEntityClassName(string $entityType): string
|
||||
{
|
||||
/** @var ?class-string<Entity> $className */
|
||||
$className = $this->metadata->get("entityDefs.$entityType.entityClassName");
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
/** @var ?class-string<Entity> $className */
|
||||
$className = $this->classFinder->find('Entities', $entityType);
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
/** @var ?string $template */
|
||||
$template = $this->metadata->get(['scopes', $entityType, 'type']);
|
||||
|
||||
if ($template) {
|
||||
/** @var ?class-string<Entity> $className */
|
||||
$className = $this->metadata->get(['app', 'entityTemplates', $template, 'entityClassName']);
|
||||
}
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
return self::DEFAULT_ENTITY_CLASS_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entityType
|
||||
* @return class-string<Repository<Entity>>
|
||||
*/
|
||||
private function findRepositoryClassName(string $entityType): string
|
||||
{
|
||||
/** @var ?class-string<Repository<Entity>> $className */
|
||||
$className = $this->metadata->get("entityDefs.$entityType.repositoryClassName");
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
/** @var ?class-string<Repository<Entity>> $className */
|
||||
$className = $this->classFinder->find('Repositories', $entityType);
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
/** @var ?string $template */
|
||||
$template = $this->metadata->get(['scopes', $entityType, 'type']);
|
||||
|
||||
if ($template) {
|
||||
/** @var ?class-string<Repository<Entity>> $className */
|
||||
$className = $this->metadata->get(['app', 'entityTemplates', $template, 'repositoryClassName']);
|
||||
}
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
return self::DEFAULT_REPOSITORY_CLASS_NAME;
|
||||
}
|
||||
}
|
||||
48
application/Espo/Core/ORM/ConfigDataProvider.php
Normal file
48
application/Espo/Core/ORM/ConfigDataProvider.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
|
||||
class ConfigDataProvider
|
||||
{
|
||||
public function __construct(private Config $config)
|
||||
{}
|
||||
|
||||
public function logSql(): bool
|
||||
{
|
||||
return (bool) $this->config->get('logger.sql');
|
||||
}
|
||||
|
||||
public function logSqlFailed(): bool
|
||||
{
|
||||
return (bool) $this->config->get('logger.sqlFailed');
|
||||
}
|
||||
}
|
||||
95
application/Espo/Core/ORM/DatabaseParamsFactory.php
Normal file
95
application/Espo/Core/ORM/DatabaseParamsFactory.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\ORM\DatabaseParams;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class DatabaseParamsFactory
|
||||
{
|
||||
private const DEFAULT_PLATFORM = 'Mysql';
|
||||
|
||||
public function __construct(private Config $config) {}
|
||||
|
||||
public function create(): DatabaseParams
|
||||
{
|
||||
$config = $this->config;
|
||||
|
||||
if (!$config->get('database')) {
|
||||
throw new RuntimeException('No database params in config.');
|
||||
}
|
||||
|
||||
$databaseParams = DatabaseParams::create()
|
||||
->withHost($config->get('database.host'))
|
||||
->withPort($config->get('database.port') ? (int) $config->get('database.port') : null)
|
||||
->withName($config->get('database.dbname'))
|
||||
->withUsername($config->get('database.user'))
|
||||
->withPassword($config->get('database.password'))
|
||||
->withCharset($config->get('database.charset'))
|
||||
->withPlatform($config->get('database.platform'))
|
||||
->withSslCa($config->get('database.sslCA'))
|
||||
->withSslCert($config->get('database.sslCert'))
|
||||
->withSslKey($config->get('database.sslKey'))
|
||||
->withSslCaPath($config->get('database.sslCAPath'))
|
||||
->withSslCipher($config->get('database.sslCipher'))
|
||||
->withSslVerifyDisabled($config->get('database.sslVerifyDisabled') ?? false);
|
||||
|
||||
if (!$databaseParams->getPlatform()) {
|
||||
$databaseParams = $databaseParams->withPlatform(self::DEFAULT_PLATFORM);
|
||||
}
|
||||
|
||||
return $databaseParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public function createWithMergedAssoc(array $params): DatabaseParams
|
||||
{
|
||||
$configParams = $this->create();
|
||||
|
||||
return DatabaseParams::create()
|
||||
->withHost($params['host'] ?? $configParams->getHost())
|
||||
->withPort(isset($params['port']) ? (int) $params['port'] : $configParams->getPort())
|
||||
->withName($params['dbname'] ?? $configParams->getName())
|
||||
->withUsername($params['user'] ?? $configParams->getUsername())
|
||||
->withPassword($params['password'] ?? $configParams->getPassword())
|
||||
->withCharset($params['charset'] ?? $configParams->getCharset())
|
||||
->withPlatform($params['platform'] ?? $configParams->getPlatform())
|
||||
->withSslCa($params['sslCA'] ?? $configParams->getSslCa())
|
||||
->withSslCert($params['sslCert'] ?? $configParams->getSslCert())
|
||||
->withSslKey($params['sslKey'] ?? $configParams->getSslKey())
|
||||
->withSslCaPath($params['sslCAPath'] ?? $configParams->getSslCaPath())
|
||||
->withSslCipher($params['sslCipher'] ?? $configParams->getSslCipher())
|
||||
->withSslVerifyDisabled($params['sslVerifyDisabled'] ?? $configParams->isSslVerifyDisabled());
|
||||
}
|
||||
}
|
||||
43
application/Espo/Core/ORM/Defs/AttributeParam.php
Normal file
43
application/Espo/Core/ORM/Defs/AttributeParam.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM\Defs;
|
||||
|
||||
class AttributeParam
|
||||
{
|
||||
/** @internal */
|
||||
public const IS_LINK_MULTIPLE_NAME_MAP = 'isLinkMultipleNameMap';
|
||||
/** @internal */
|
||||
public const IS_LINK_MULTIPLE_ID_LIST = 'isLinkMultipleIdList';
|
||||
/** @internal */
|
||||
public const NOT_EXPORTABLE = 'notExportable';
|
||||
/** @internal */
|
||||
public const IS_LINK_STUB = 'isLinkStub';
|
||||
|
||||
}
|
||||
729
application/Espo/Core/ORM/Entity.php
Normal file
729
application/Espo/Core/ORM/Entity.php
Normal file
@@ -0,0 +1,729 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM;
|
||||
|
||||
use Espo\Core\Field\Link;
|
||||
use Espo\Core\Field\LinkParent;
|
||||
use Espo\Core\Name\Field;
|
||||
use Espo\Core\ORM\Defs\AttributeParam;
|
||||
use Espo\ORM\BaseEntity;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Entity as OrmEntity;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
|
||||
use LogicException;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* An entity.
|
||||
*/
|
||||
class Entity extends BaseEntity
|
||||
{
|
||||
/**
|
||||
* Has a link-multiple field.
|
||||
*/
|
||||
public function hasLinkMultipleField(string $field): bool
|
||||
{
|
||||
return
|
||||
$this->hasRelation($field) &&
|
||||
$this->getAttributeParam($field . 'Ids', AttributeParam::IS_LINK_MULTIPLE_ID_LIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Has a link field.
|
||||
*/
|
||||
public function hasLinkField(string $field): bool
|
||||
{
|
||||
return $this->hasAttribute($field . 'Id') && $this->hasRelation($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Has a link-parent field.
|
||||
*/
|
||||
public function hasLinkParentField(string $field): bool
|
||||
{
|
||||
return $this->getAttributeType($field . 'Type') === AttributeType::FOREIGN_TYPE &&
|
||||
$this->hasAttribute($field . 'Id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a parent-name field.
|
||||
*/
|
||||
public function loadParentNameField(string $field): void
|
||||
{
|
||||
if (!$this->hasLinkParentField($field)) {
|
||||
throw new LogicException("Called `loadParentNameField` on non-link-parent field `$field`.");
|
||||
}
|
||||
|
||||
$idAttribute = $field . 'Id';
|
||||
$nameAttribute = $field . 'Name';
|
||||
|
||||
$parentId = $this->get($idAttribute);
|
||||
$parentType = $this->get($field . 'Type');
|
||||
|
||||
if (!$this->entityManager) {
|
||||
throw new LogicException("No entity-manager.");
|
||||
}
|
||||
|
||||
$toSetFetched = !$this->isNew() && !$this->isAttributeChanged($idAttribute);
|
||||
|
||||
if (!$parentId || !$parentType) {
|
||||
/** @noinspection PhpRedundantOptionalArgumentInspection */
|
||||
$this->set($nameAttribute, null);
|
||||
|
||||
if ($toSetFetched) {
|
||||
$this->setFetched($nameAttribute, null);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->entityManager->hasRepository($parentType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$repository = $this->entityManager->getRDBRepository($parentType);
|
||||
|
||||
$select = [Attribute::ID, Field::NAME];
|
||||
|
||||
$foreignEntity = $repository
|
||||
->select($select)
|
||||
->where([Attribute::ID => $parentId])
|
||||
->findOne();
|
||||
|
||||
$entityName = $foreignEntity ? $foreignEntity->get(Field::NAME) : null;
|
||||
|
||||
$this->set($nameAttribute, $entityName);
|
||||
|
||||
if ($toSetFetched) {
|
||||
$this->setFetched($nameAttribute, $entityName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $link
|
||||
* @return ?array{
|
||||
* orderBy: string|array<int, array{string, string}>|null,
|
||||
* order: ?string,
|
||||
* }
|
||||
*/
|
||||
protected function getRelationOrderParams(string $link): ?array
|
||||
{
|
||||
$field = $link;
|
||||
|
||||
$idsAttribute = $field . 'Ids';
|
||||
|
||||
$foreignEntityType = $this->getRelationParam($field, RelationParam::ENTITY);
|
||||
|
||||
if ($this->getAttributeParam($idsAttribute, 'orderBy')) {
|
||||
$defs = [
|
||||
'orderBy' => $this->getAttributeParam($idsAttribute, 'orderBy'),
|
||||
'order' => Order::ASC,
|
||||
];
|
||||
|
||||
if ($this->getAttributeParam($idsAttribute, 'orderDirection')) {
|
||||
$defs['order'] = $this->getAttributeParam($idsAttribute, 'orderDirection');
|
||||
}
|
||||
|
||||
return $defs;
|
||||
}
|
||||
|
||||
if ($this->getRelationParam($link, 'orderBy')) {
|
||||
$defs = [
|
||||
'orderBy' => $this->getRelationParam($link, 'orderBy'),
|
||||
'order' => Order::ASC,
|
||||
];
|
||||
|
||||
if ($this->getRelationParam($link, 'order')) {
|
||||
$defs['order'] = strtoupper($this->getRelationParam($link, 'order'));
|
||||
}
|
||||
|
||||
return $defs;
|
||||
}
|
||||
|
||||
if (!$foreignEntityType || !$this->entityManager) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ormDefs = $this->entityManager->getMetadata()->getDefs();
|
||||
|
||||
if (!$ormDefs->hasEntity($foreignEntityType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$entityDefs = $ormDefs->getEntity($foreignEntityType);
|
||||
|
||||
$collectionDefs = $entityDefs->getParam('collection') ?? [];
|
||||
|
||||
$orderBy = $collectionDefs['orderBy'] ?? null;
|
||||
$order = $collectionDefs['order'] ?? 'ASC';
|
||||
|
||||
if (!$orderBy) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$entityDefs->hasAttribute($orderBy)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'orderBy' => $orderBy,
|
||||
'order' => $order,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a link-multiple field. Should be used wisely. Consider using `getLinkMultipleIdList` instead.
|
||||
*
|
||||
* @param ?array<string, string> $columns Deprecated as of v9.0.
|
||||
* @todo Add a method to load and set only fetched values?
|
||||
* @internal
|
||||
*/
|
||||
public function loadLinkMultipleField(string $field, ?array $columns = null): void
|
||||
{
|
||||
if (!$this->hasLinkMultipleField($field)) {
|
||||
throw new LogicException("Called `loadLinkMultipleField` on non-link-multiple field `$field`.");
|
||||
}
|
||||
|
||||
if (!$this->entityManager) {
|
||||
throw new LogicException("No entity-manager.");
|
||||
}
|
||||
|
||||
$select = [Attribute::ID, Field::NAME];
|
||||
|
||||
$hasType = $this->hasAttribute($field . 'Types');
|
||||
|
||||
if ($hasType) {
|
||||
$select[] = 'type';
|
||||
}
|
||||
|
||||
$columns ??= $this->getLinkMultipleColumnsFromDefs($field);
|
||||
|
||||
if ($columns) {
|
||||
foreach ($columns as $it) {
|
||||
$select[] = $it;
|
||||
}
|
||||
}
|
||||
|
||||
$selectBuilder = $this->entityManager
|
||||
->getRDBRepository($this->getEntityType())
|
||||
->getRelation($this, $field)
|
||||
->select($select);
|
||||
|
||||
$orderBy = null;
|
||||
$order = null;
|
||||
|
||||
$orderParams = $this->getRelationOrderParams($field);
|
||||
|
||||
if ($orderParams) {
|
||||
$orderBy = $orderParams['orderBy'] ?? null;
|
||||
/** @var string|bool|null $order */
|
||||
$order = $orderParams['order'] ?? null;
|
||||
}
|
||||
|
||||
if ($orderBy) {
|
||||
if (is_string($orderBy) && !in_array($orderBy, $select)) {
|
||||
$selectBuilder->select($orderBy);
|
||||
}
|
||||
|
||||
if (is_string($order)) {
|
||||
$order = strtoupper($order);
|
||||
|
||||
if ($order !== Order::ASC && $order !== Order::DESC) {
|
||||
$order = Order::ASC;
|
||||
}
|
||||
}
|
||||
|
||||
$selectBuilder->order($orderBy, $order);
|
||||
}
|
||||
|
||||
$collection = $selectBuilder->find();
|
||||
|
||||
$ids = [];
|
||||
$names = (object) [];
|
||||
$types = (object) [];
|
||||
$columnsData = (object) [];
|
||||
|
||||
foreach ($collection as $e) {
|
||||
$id = $e->getId();
|
||||
|
||||
$ids[] = $id;
|
||||
|
||||
$names->$id = $e->get(Field::NAME);
|
||||
|
||||
if ($hasType) {
|
||||
$types->$id = $e->get('type');
|
||||
}
|
||||
|
||||
if (!$columns) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columnsData->$id = (object) [];
|
||||
|
||||
foreach ($columns as $column => $foreignAttribute) {
|
||||
$columnsData->$id->$column = $e->get($foreignAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
$idsAttribute = $field . 'Ids';
|
||||
$namesAttribute = $field . 'Names';
|
||||
$typesAttribute = $field . 'Types';
|
||||
$columnsAttribute = $field . 'Columns';
|
||||
|
||||
$toSetFetched = !$this->isNew() && !$this->hasFetched($idsAttribute);
|
||||
|
||||
$this->setInContainerNotWritten($idsAttribute, $ids);
|
||||
$this->setInContainerNotWritten($namesAttribute, $names);
|
||||
|
||||
if ($toSetFetched) {
|
||||
$this->setFetched($idsAttribute, $ids);
|
||||
$this->setFetched($namesAttribute, $names);
|
||||
}
|
||||
|
||||
if ($hasType) {
|
||||
$this->set($typesAttribute, $types);
|
||||
|
||||
if ($toSetFetched) {
|
||||
$this->setFetched($typesAttribute, $types);
|
||||
}
|
||||
}
|
||||
|
||||
if ($columns) {
|
||||
$this->setInContainerNotWritten($columnsAttribute, $columnsData);
|
||||
|
||||
if ($toSetFetched) {
|
||||
$this->setFetched($columnsAttribute, $columnsData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a link field. If a value is already set, it will set only a fetched value.
|
||||
*/
|
||||
public function loadLinkField(string $field): void
|
||||
{
|
||||
if (!$this->hasLinkField($field)) {
|
||||
throw new LogicException("Called `loadLinkField` on non-link field '$field'.");
|
||||
}
|
||||
|
||||
if (
|
||||
$this->getRelationType($field) !== RelationType::HAS_ONE &&
|
||||
$this->getRelationType($field) !== RelationType::BELONGS_TO
|
||||
) {
|
||||
throw new LogicException("Can't load link '$field'.");
|
||||
}
|
||||
|
||||
if (!$this->entityManager) {
|
||||
throw new LogicException("No entity-manager.");
|
||||
}
|
||||
|
||||
$select = [Attribute::ID, Field::NAME];
|
||||
|
||||
$entity = $this->entityManager
|
||||
->getRelation($this, $field)
|
||||
->select($select)
|
||||
->findOne();
|
||||
|
||||
$entityId = null;
|
||||
$entityName = null;
|
||||
|
||||
if ($entity) {
|
||||
$entityId = $entity->getId();
|
||||
$entityName = $entity->get(Field::NAME);
|
||||
}
|
||||
|
||||
$idAttribute = $field . 'Id';
|
||||
$nameAttribute = $field . 'Name';
|
||||
|
||||
if (!$this->isNew() && !$this->hasFetched($idAttribute)) {
|
||||
$this->setFetched($idAttribute, $entityId);
|
||||
$this->setFetched($nameAttribute, $entityName);
|
||||
}
|
||||
|
||||
if ($this->has($idAttribute)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setInContainerNotWritten($idAttribute, $entityId);
|
||||
$this->setInContainerNotWritten($nameAttribute, $entityName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a link-multiple name.
|
||||
*/
|
||||
public function getLinkMultipleName(string $field, string $id): ?string
|
||||
{
|
||||
$namesAttribute = $field . 'Names';
|
||||
|
||||
if (!$this->hasAttribute($namesAttribute)) {
|
||||
throw new LogicException("Called `getLinkMultipleName` on non-link-multiple field `$field.");
|
||||
}
|
||||
|
||||
if (!$this->has($namesAttribute) && !$this->isNew()) {
|
||||
$this->loadLinkMultipleField($field);
|
||||
}
|
||||
|
||||
$object = $this->get($namesAttribute) ?? (object) [];
|
||||
|
||||
if (!$object instanceof stdClass) {
|
||||
throw new LogicException("Non-object value in `$namesAttribute`.");
|
||||
}
|
||||
|
||||
return $object->$id ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a link-multiple name.
|
||||
*
|
||||
* @return static As of v9.2.
|
||||
*/
|
||||
public function setLinkMultipleName(string $field, string $id, ?string $value): static
|
||||
{
|
||||
$namesAttribute = $field . 'Names';
|
||||
|
||||
if (!$this->hasAttribute($namesAttribute)) {
|
||||
throw new LogicException("Called `setLinkMultipleName` on non-link-multiple field `$field.");
|
||||
}
|
||||
|
||||
if (!$this->has($namesAttribute)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$object = $this->get($namesAttribute) ?? (object) [];
|
||||
|
||||
if (!$object instanceof stdClass) {
|
||||
throw new LogicException("Non-object value in `$namesAttribute`.");
|
||||
}
|
||||
|
||||
$object->$id = $value;
|
||||
|
||||
$this->set($namesAttribute, $object);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a link-multiple column value.
|
||||
*/
|
||||
public function getLinkMultipleColumn(string $field, string $column, string $id): mixed
|
||||
{
|
||||
$columnsAttribute = $field . 'Columns';
|
||||
|
||||
if (!$this->hasAttribute($columnsAttribute)) {
|
||||
throw new LogicException("Called `getLinkMultipleColumn` on not supported field `$field.");
|
||||
}
|
||||
|
||||
if (!$this->has($columnsAttribute) && !$this->isNew()) {
|
||||
$this->loadLinkMultipleField($field);
|
||||
}
|
||||
|
||||
$object = $this->get($columnsAttribute) ?? (object) [];
|
||||
|
||||
if (!$object instanceof stdClass) {
|
||||
throw new LogicException("Non-object value in `$columnsAttribute`.");
|
||||
}
|
||||
|
||||
return $object->$id->$column ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a link-multiple column value.
|
||||
*
|
||||
* @return static As of v9.2.
|
||||
*/
|
||||
public function setLinkMultipleColumn(string $field, string $column, string $id, mixed $value): static
|
||||
{
|
||||
$columnsAttribute = $field . 'Columns';
|
||||
|
||||
if (!$this->hasAttribute($columnsAttribute)) {
|
||||
throw new LogicException("Called `setLinkMultipleColumn` on non-link-multiple field `$field.");
|
||||
}
|
||||
|
||||
if (!$this->has($columnsAttribute) && !$this->isNew()) {
|
||||
$this->loadLinkMultipleField($field);
|
||||
}
|
||||
|
||||
$object = $this->get($columnsAttribute) ?? (object) [];
|
||||
|
||||
if (!$object instanceof stdClass) {
|
||||
throw new LogicException("Non-object value in `$columnsAttribute`.");
|
||||
}
|
||||
|
||||
$object->$id ??= (object) [];
|
||||
$object->$id->$column = $value;
|
||||
|
||||
$this->set($columnsAttribute, $object);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set link-multiple IDs.
|
||||
*
|
||||
* @param string[] $idList
|
||||
* @return static As of v9.2.
|
||||
*/
|
||||
public function setLinkMultipleIdList(string $field, array $idList): static
|
||||
{
|
||||
$idsAttribute = $field . 'Ids';
|
||||
|
||||
if (!$this->hasAttribute($idsAttribute)) {
|
||||
throw new LogicException("Called `setLinkMultipleIdList` on non-link-multiple field `$field.");
|
||||
}
|
||||
|
||||
$this->set($idsAttribute, $idList);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an ID to a link-multiple field.
|
||||
*
|
||||
* @return static As of v9.2.
|
||||
*/
|
||||
public function addLinkMultipleId(string $field, string $id): static
|
||||
{
|
||||
$idsAttribute = $field . 'Ids';
|
||||
|
||||
if (!$this->hasAttribute($idsAttribute)) {
|
||||
throw new LogicException("Called `addLinkMultipleId` on non-link-multiple field `$field.");
|
||||
}
|
||||
|
||||
if (!$this->has($idsAttribute)) {
|
||||
if (!$this->isNew()) {
|
||||
$this->loadLinkMultipleField($field);
|
||||
} else {
|
||||
$this->set($idsAttribute, []);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->has($idsAttribute)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$idList = $this->get($idsAttribute);
|
||||
|
||||
if ($idList === null) {
|
||||
throw new LogicException("Null value set in `$idsAttribute`.");
|
||||
}
|
||||
|
||||
if (!is_array($idList)) {
|
||||
throw new LogicException("Non-array value set in `$idsAttribute`.");
|
||||
}
|
||||
|
||||
if (in_array($id, $idList)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$idList[] = $id;
|
||||
|
||||
$this->set($idsAttribute, $idList);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an ID from link-multiple field.
|
||||
*
|
||||
* @return static As of v9.2.
|
||||
*/
|
||||
public function removeLinkMultipleId(string $field, string $id): static
|
||||
{
|
||||
if (!$this->hasLinkMultipleId($field, $id)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$list = $this->getLinkMultipleIdList($field);
|
||||
|
||||
$index = array_search($id, $list);
|
||||
|
||||
if ($index !== false) {
|
||||
unset($list[$index]);
|
||||
|
||||
$list = array_values($list);
|
||||
}
|
||||
|
||||
$this->setLinkMultipleIdList($field, $list);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get link-multiple field IDs.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getLinkMultipleIdList(string $field): array
|
||||
{
|
||||
$idsAttribute = $field . 'Ids';
|
||||
|
||||
if (!$this->hasAttribute($idsAttribute)) {
|
||||
throw new LogicException("Called `getLinkMultipleIdList` for non-link-multiple field `$field.");
|
||||
}
|
||||
|
||||
if (!$this->has($idsAttribute) && !$this->isNew()) {
|
||||
$this->loadLinkMultipleField($field);
|
||||
}
|
||||
|
||||
/** @var string[] */
|
||||
return $this->get($idsAttribute) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get previous link-multiple field IDs.
|
||||
*
|
||||
* @return string[]
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function getFetchedLinkMultipleIdList(string $field): array
|
||||
{
|
||||
$idsAttribute = $field . 'Ids';
|
||||
|
||||
if (!$this->hasAttribute($idsAttribute)) {
|
||||
throw new LogicException("Called `getFetchedLinkMultipleIdList` for non-link-multiple field `$field.");
|
||||
}
|
||||
|
||||
if (!$this->isNew()) {
|
||||
if (!$this->has($idsAttribute)) {
|
||||
$this->loadLinkMultipleField($field);
|
||||
} else if (!$this->hasFetched($field)) {
|
||||
// Set but not loaded.
|
||||
|
||||
$attributes = [
|
||||
$field . 'Ids',
|
||||
$field . 'Names',
|
||||
$field . 'Types',
|
||||
$field . 'Columns',
|
||||
];
|
||||
|
||||
$map = array_reduce($attributes, function ($p, $item) {
|
||||
if (!$this->has($item)) {
|
||||
return $p;
|
||||
}
|
||||
|
||||
$p[$item] = $this->get($item);
|
||||
|
||||
return $p;
|
||||
}, []);
|
||||
|
||||
$this->loadLinkMultipleField($field);
|
||||
|
||||
// Restore set values.
|
||||
$this->setMultiple($map);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->has($idsAttribute) && !$this->isNew()) {
|
||||
$this->loadLinkMultipleField($field);
|
||||
}
|
||||
|
||||
/** @var string[] */
|
||||
return $this->getFetched($idsAttribute) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Has an ID in a link-multiple field.
|
||||
*/
|
||||
public function hasLinkMultipleId(string $field, string $id): bool
|
||||
{
|
||||
$idsAttribute = $field . 'Ids';
|
||||
|
||||
if (!$this->hasAttribute($idsAttribute)) {
|
||||
throw new LogicException("Called `hasLinkMultipleId` for non-link-multiple field `$field.");
|
||||
}
|
||||
|
||||
if (!$this->has($idsAttribute) && !$this->isNew()) {
|
||||
$this->loadLinkMultipleField($field);
|
||||
}
|
||||
|
||||
if (!$this->has($idsAttribute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var string[] $idList */
|
||||
$idList = $this->get($idsAttribute) ?? [];
|
||||
|
||||
return in_array($id, $idList);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]|null
|
||||
*/
|
||||
private function getLinkMultipleColumnsFromDefs(string $field): ?array
|
||||
{
|
||||
if (!$this->entityManager) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$entityDefs = $this->entityManager->getDefs()->getEntity($this->entityType);
|
||||
|
||||
/** @var ?array<string, string> $columns */
|
||||
$columns = $entityDefs->tryGetField($field)?->getParam('columns');
|
||||
|
||||
if (!$columns) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
$foreignEntityType = $entityDefs->tryGetRelation($field)?->tryGetForeignEntityType();
|
||||
|
||||
if ($foreignEntityType) {
|
||||
$foreignEntityDefs = $this->entityManager->getDefs()->getEntity($foreignEntityType);
|
||||
|
||||
foreach ($columns as $column => $attribute) {
|
||||
if (!$foreignEntityDefs->hasAttribute($attribute)) {
|
||||
// For backward compatibility. If foreign attributes defined in the field do not exist.
|
||||
unset($columns[$column]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 9.0.0
|
||||
*/
|
||||
protected function setRelatedLinkOrEntity(string $relation, Link|LinkParent|OrmEntity|null $related): static
|
||||
{
|
||||
if ($related instanceof Entity || $related === null) {
|
||||
$this->relations->set($relation, $related);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->setValueObject($relation, $related);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
188
application/Espo/Core/ORM/EntityFactory.php
Normal file
188
application/Espo/Core/ORM/EntityFactory.php
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM;
|
||||
|
||||
use Espo\Core\Binding\Binder;
|
||||
use Espo\Core\Binding\BindingContainer;
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\Binding\BindingData;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\ORM\DataLoader\Loader;
|
||||
use Espo\ORM\DataLoader\RDBLoader;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityFactory as EntityFactoryInterface;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Relation\RDBRelations;
|
||||
use Espo\ORM\Relation\Relations;
|
||||
use Espo\ORM\Relation\RelationsMap;
|
||||
use Espo\ORM\Value\ValueAccessorFactory;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class EntityFactory implements EntityFactoryInterface
|
||||
{
|
||||
private ?EntityManager $entityManager = null;
|
||||
private ?ValueAccessorFactory $valueAccessorFactory = null;
|
||||
|
||||
public function __construct(
|
||||
private ClassNameProvider $classNameProvider,
|
||||
private Helper $helper,
|
||||
private InjectableFactory $injectableFactory,
|
||||
private RelationsMap $relationsMap,
|
||||
) {}
|
||||
|
||||
public function setEntityManager(EntityManager $entityManager): void
|
||||
{
|
||||
if ($this->entityManager) {
|
||||
throw new RuntimeException("EntityManager can be set only once.");
|
||||
}
|
||||
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function setValueAccessorFactory(ValueAccessorFactory $valueAccessorFactory): void
|
||||
{
|
||||
if ($this->valueAccessorFactory) {
|
||||
throw new RuntimeException("ValueAccessorFactory can be set only once.");
|
||||
}
|
||||
|
||||
$this->valueAccessorFactory = $valueAccessorFactory;
|
||||
}
|
||||
|
||||
public function create(string $entityType): Entity
|
||||
{
|
||||
return $this->createInternal($entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?array<string, mixed> $attributeDefs
|
||||
*/
|
||||
private function createInternal(string $entityType, ?array $attributeDefs = null): Entity
|
||||
{
|
||||
$className = $this->getClassName($entityType);
|
||||
|
||||
if (!$this->entityManager) {
|
||||
throw new RuntimeException("No entityManager.");
|
||||
}
|
||||
|
||||
$defs = $this->entityManager->getMetadata()->get($entityType);
|
||||
|
||||
if (is_null($defs)) {
|
||||
throw new RuntimeException("Entity '$entityType' is not defined in metadata.");
|
||||
}
|
||||
|
||||
if ($attributeDefs) {
|
||||
$defs['attributes'] = array_merge($defs['attributes'] ?? [], $attributeDefs);
|
||||
}
|
||||
|
||||
$relations = $this->injectableFactory->createWithBinding(
|
||||
RDBRelations::class,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(EntityManager::class, $this->entityManager)
|
||||
->build()
|
||||
);
|
||||
|
||||
$loader = $this->injectableFactory->createWithBinding(
|
||||
RDBLoader::class,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(EntityManager::class, $this->entityManager)
|
||||
->build()
|
||||
);
|
||||
|
||||
$bindingContainer = $this->getBindingContainer(
|
||||
$className,
|
||||
$entityType,
|
||||
$defs,
|
||||
$relations,
|
||||
$loader,
|
||||
);
|
||||
|
||||
$entity = $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
|
||||
if ($relations instanceof RDBRelations) {
|
||||
$relations->setEntity($entity);
|
||||
}
|
||||
|
||||
$this->relationsMap->set($entity, $relations);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attributeDefs
|
||||
* @internal
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function createWithAdditionalAttributes(string $entityType, array $attributeDefs): Entity
|
||||
{
|
||||
return $this->createInternal($entityType, $attributeDefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<Entity>
|
||||
*/
|
||||
private function getClassName(string $entityType): string
|
||||
{
|
||||
/** @var class-string<Entity> */
|
||||
return $this->classNameProvider->getEntityClassName($entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<Entity> $className
|
||||
* @param array<string, mixed> $defs
|
||||
*/
|
||||
private function getBindingContainer(
|
||||
string $className,
|
||||
string $entityType,
|
||||
array $defs,
|
||||
Relations $relations,
|
||||
Loader $loader,
|
||||
): BindingContainer {
|
||||
|
||||
if (!$this->entityManager || !$this->valueAccessorFactory) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$data = new BindingData();
|
||||
$binder = new Binder($data);
|
||||
|
||||
$binder
|
||||
->for($className)
|
||||
->bindValue('$entityType', $entityType)
|
||||
->bindValue('$defs', $defs)
|
||||
->bindInstance(EntityManager::class, $this->entityManager)
|
||||
->bindInstance(ValueAccessorFactory::class, $this->valueAccessorFactory)
|
||||
->bindInstance(Helper::class, $this->helper)
|
||||
->bindInstance(Relations::class, $relations)
|
||||
->bindInstance(Loader::class, $loader);
|
||||
|
||||
return new BindingContainer($data);
|
||||
}
|
||||
}
|
||||
35
application/Espo/Core/ORM/EntityManager.php
Normal file
35
application/Espo/Core/ORM/EntityManager.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM;
|
||||
|
||||
use Espo\ORM\EntityManager as BaseEntityManager;
|
||||
|
||||
class EntityManager extends BaseEntityManager
|
||||
{}
|
||||
170
application/Espo/Core/ORM/EntityManagerFactory.php
Normal file
170
application/Espo/Core/ORM/EntityManagerFactory.php
Normal file
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM;
|
||||
|
||||
use Espo\Core\ORM\PDO\PDOFactoryFactory;
|
||||
use Espo\Core\ORM\QueryComposer\QueryComposerFactory;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\ORM\QueryComposer\Part\FunctionConverterFactory;
|
||||
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\ORM\Executor\DefaultSqlExecutor;
|
||||
use Espo\ORM\Metadata;
|
||||
use Espo\ORM\EventDispatcher;
|
||||
use Espo\ORM\DatabaseParams;
|
||||
use Espo\ORM\PDO\PDOFactory;
|
||||
use Espo\ORM\QueryComposer\QueryComposerFactory as QueryComposerFactoryInterface;
|
||||
use Espo\ORM\Relation\RelationsMap;
|
||||
use Espo\ORM\Repository\RepositoryFactory as RepositoryFactoryInterface;
|
||||
use Espo\ORM\EntityFactory as EntityFactoryInterface;
|
||||
use Espo\ORM\Executor\SqlExecutor;
|
||||
use Espo\ORM\Value\ValueFactoryFactory as ValueFactoryFactoryInterface;
|
||||
use Espo\ORM\Value\AttributeExtractorFactory as AttributeExtractorFactoryInterface;
|
||||
use Espo\ORM\PDO\PDOProvider;
|
||||
use Espo\ORM\QueryComposer\Part\FunctionConverterFactory as FunctionConverterFactoryInterface;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class EntityManagerFactory
|
||||
{
|
||||
public function __construct(
|
||||
private InjectableFactory $injectableFactory,
|
||||
private MetadataDataProvider $metadataDataProvider,
|
||||
private EventDispatcher $eventDispatcher,
|
||||
private PDOFactoryFactory $pdoFactoryFactory,
|
||||
private DatabaseParamsFactory $databaseParamsFactory,
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
private Log $log,
|
||||
) {}
|
||||
|
||||
public function create(): EntityManager
|
||||
{
|
||||
$relationsMap = new RelationsMap();
|
||||
|
||||
$entityFactory = $this->injectableFactory->createWithBinding(
|
||||
EntityFactory::class,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(EventDispatcher::class, $this->eventDispatcher)
|
||||
->bindInstance(RelationsMap::class, $relationsMap)
|
||||
->build()
|
||||
);
|
||||
|
||||
$repositoryFactory = $this->injectableFactory->createWithBinding(
|
||||
RepositoryFactory::class,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(EntityFactoryInterface::class, $entityFactory)
|
||||
->bindInstance(EventDispatcher::class, $this->eventDispatcher)
|
||||
->bindInstance(RelationsMap::class, $relationsMap)
|
||||
->build()
|
||||
);
|
||||
|
||||
$databaseParams = $this->createDatabaseParams();
|
||||
|
||||
$metadata = new Metadata($this->metadataDataProvider, $this->eventDispatcher);
|
||||
|
||||
$valueFactoryFactory = $this->injectableFactory->createWithBinding(
|
||||
ValueFactoryFactory::class,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(Metadata::class, $metadata)
|
||||
->build()
|
||||
);
|
||||
|
||||
$attributeExtractorFactory = $this->injectableFactory->createWithBinding(
|
||||
AttributeExtractorFactory::class,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(Metadata::class, $metadata)
|
||||
->build()
|
||||
);
|
||||
|
||||
$functionConverterFactory = $this->injectableFactory->createWithBinding(
|
||||
FunctionConverterFactory::class,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(DatabaseParams::class, $databaseParams)
|
||||
->build()
|
||||
);
|
||||
|
||||
$pdoFactory = $this->pdoFactoryFactory->create($databaseParams->getPlatform() ?? '');
|
||||
|
||||
$pdoProvider = $this->injectableFactory->createResolved(
|
||||
PDOProvider::class,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(DatabaseParams::class, $databaseParams)
|
||||
->bindInstance(PDOFactory::class, $pdoFactory)
|
||||
->build()
|
||||
);
|
||||
|
||||
$queryComposerFactory = $this->injectableFactory->createWithBinding(
|
||||
QueryComposerFactory::class,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(PDOProvider::class, $pdoProvider)
|
||||
->bindInstance(Metadata::class, $metadata)
|
||||
->bindInstance(EventDispatcher::class, $this->eventDispatcher)
|
||||
->bindInstance(EntityFactoryInterface::class, $entityFactory)
|
||||
->bindInstance(FunctionConverterFactoryInterface::class, $functionConverterFactory)
|
||||
->build()
|
||||
);
|
||||
|
||||
$sqlExecutor = new DefaultSqlExecutor(
|
||||
$pdoProvider,
|
||||
$this->log,
|
||||
$this->configDataProvider->logSql(),
|
||||
$this->configDataProvider->logSqlFailed()
|
||||
);
|
||||
|
||||
$binding = BindingContainerBuilder::create()
|
||||
->bindInstance(DatabaseParams::class, $databaseParams)
|
||||
->bindInstance(Metadata::class, $metadata)
|
||||
->bindInstance(QueryComposerFactoryInterface::class, $queryComposerFactory)
|
||||
->bindInstance(RepositoryFactoryInterface::class, $repositoryFactory)
|
||||
->bindInstance(EntityFactoryInterface::class, $entityFactory)
|
||||
->bindInstance(ValueFactoryFactoryInterface::class, $valueFactoryFactory)
|
||||
->bindInstance(AttributeExtractorFactoryInterface::class, $attributeExtractorFactory)
|
||||
->bindInstance(EventDispatcher::class, $this->eventDispatcher)
|
||||
->bindInstance(PDOProvider::class, $pdoProvider)
|
||||
->bindInstance(FunctionConverterFactoryInterface::class, $functionConverterFactory)
|
||||
->bindInstance(SqlExecutor::class, $sqlExecutor)
|
||||
->bindInstance(RelationsMap::class, $relationsMap)
|
||||
->build();
|
||||
|
||||
return $this->injectableFactory->createWithBinding(EntityManager::class, $binding);
|
||||
}
|
||||
|
||||
private function createDatabaseParams(): DatabaseParams
|
||||
{
|
||||
$databaseParams = $this->databaseParamsFactory->create();
|
||||
|
||||
if (!$databaseParams->getName()) {
|
||||
throw new RuntimeException('No database name specified in config.');
|
||||
}
|
||||
|
||||
return $databaseParams;
|
||||
}
|
||||
}
|
||||
132
application/Espo/Core/ORM/EntityManagerProxy.php
Normal file
132
application/Espo/Core/ORM/EntityManagerProxy.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Metadata;
|
||||
use Espo\ORM\Repository\RDBRepository;
|
||||
use Espo\ORM\Repository\Repository;
|
||||
use Espo\ORM\Executor\SqlExecutor;
|
||||
use Espo\Core\Container;
|
||||
|
||||
class EntityManagerProxy
|
||||
{
|
||||
private ?EntityManager $entityManager = null;
|
||||
|
||||
public function __construct(private Container $container)
|
||||
{}
|
||||
|
||||
private function getEntityManager(): EntityManager
|
||||
{
|
||||
if (!$this->entityManager) {
|
||||
$this->entityManager = $this->container->getByClass(EntityManager::class);
|
||||
}
|
||||
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
public function getNewEntity(string $entityType): Entity
|
||||
{
|
||||
return $this->getEntityManager()->getNewEntity($entityType);
|
||||
}
|
||||
|
||||
public function getEntityById(string $entityType, string $id): ?Entity
|
||||
{
|
||||
return $this->getEntityManager()->getEntityById($entityType, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v9.0.
|
||||
* @todo Remove in v11.0.
|
||||
*/
|
||||
public function getEntity(string $entityType, ?string $id = null): ?Entity
|
||||
{
|
||||
/** @noinspection PhpDeprecationInspection */
|
||||
return $this->getEntityManager()->getEntity($entityType, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function saveEntity(Entity $entity, array $options = []): void
|
||||
{
|
||||
$this->getEntityManager()->saveEntity($entity, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Repository<Entity>
|
||||
*/
|
||||
public function getRepository(string $entityType): Repository
|
||||
{
|
||||
return $this->getEntityManager()->getRepository($entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RDBRepository<Entity>
|
||||
*/
|
||||
public function getRDBRepository(string $entityType): RDBRepository
|
||||
{
|
||||
return $this->getEntityManager()->getRDBRepository($entityType);
|
||||
}
|
||||
|
||||
public function getMetadata(): Metadata
|
||||
{
|
||||
return $this->getEntityManager()->getMetadata();
|
||||
}
|
||||
|
||||
public function getSqlExecutor(): SqlExecutor
|
||||
{
|
||||
return $this->getEntityManager()->getSqlExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an RDB repository by an entity class name.
|
||||
*
|
||||
* @template T of Entity
|
||||
* @param class-string<T> $className An entity class name.
|
||||
* @return RDBRepository<T>
|
||||
*/
|
||||
public function getRDBRepositoryByClass(string $className): RDBRepository
|
||||
{
|
||||
return $this->getEntityManager()->getRDBRepositoryByClass($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a repository by an entity class name.
|
||||
*
|
||||
* @template T of Entity
|
||||
* @param class-string<T> $className An entity class name.
|
||||
* @return Repository<T>
|
||||
*/
|
||||
public function getRepositoryByClass(string $className): Repository
|
||||
{
|
||||
return $this->getEntityManager()->getRepositoryByClass($className);
|
||||
}
|
||||
}
|
||||
160
application/Espo/Core/ORM/Helper.php
Normal file
160
application/Espo/Core/ORM/Helper.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM;
|
||||
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class Helper
|
||||
{
|
||||
private const FORMAT_LAST_FIRST = 'lastFirst';
|
||||
private const FORMAT_LAST_FIRST_MIDDLE = 'lastFirstMiddle';
|
||||
private const FORMAT_FIRST_MIDDLE_LAST = 'firstMiddleLast';
|
||||
|
||||
public function __construct(private Config $config)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function hasAllPersonNameAttributes(Entity $entity, string $field): bool
|
||||
{
|
||||
$format = $this->config->get('personNameFormat');
|
||||
|
||||
$firstName = 'first' . ucfirst($field);
|
||||
$lastName = 'last' . ucfirst($field);
|
||||
$middleName = 'middle' . ucfirst($field);
|
||||
|
||||
$attributes = [
|
||||
$firstName,
|
||||
$lastName,
|
||||
];
|
||||
|
||||
if (
|
||||
$format === self::FORMAT_LAST_FIRST_MIDDLE ||
|
||||
$format === self::FORMAT_FIRST_MIDDLE_LAST
|
||||
) {
|
||||
$attributes[] = $middleName;
|
||||
}
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
if (!$entity->has($attribute)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function formatPersonName(Entity $entity, string $field): ?string
|
||||
{
|
||||
$format = $this->config->get('personNameFormat');
|
||||
|
||||
$first = $entity->get('first' . ucfirst($field));
|
||||
$last = $entity->get('last' . ucfirst($field));
|
||||
$middle = $entity->get('middle' . ucfirst($field));
|
||||
|
||||
switch ($format) {
|
||||
case self::FORMAT_LAST_FIRST:
|
||||
if ($first === null && $last === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($first === null) {
|
||||
return $last;
|
||||
}
|
||||
|
||||
if ($last === null) {
|
||||
return $first;
|
||||
}
|
||||
|
||||
return $last . ' ' . $first;
|
||||
|
||||
case self::FORMAT_LAST_FIRST_MIDDLE:
|
||||
if ($first === null && $last === null && $middle === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$arr = [];
|
||||
|
||||
if ($last !== null) {
|
||||
$arr[] = $last;
|
||||
}
|
||||
|
||||
if ($first !== null) {
|
||||
$arr[] = $first;
|
||||
}
|
||||
|
||||
if ($middle !== null) {
|
||||
$arr[] = $middle;
|
||||
}
|
||||
|
||||
return implode(' ', $arr);
|
||||
|
||||
case self::FORMAT_FIRST_MIDDLE_LAST:
|
||||
if (!$first && !$last && !$middle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$arr = [];
|
||||
|
||||
if ($first !== null) {
|
||||
$arr[] = $first;
|
||||
}
|
||||
|
||||
if ($middle !== null) {
|
||||
$arr[] = $middle;
|
||||
}
|
||||
|
||||
if ($last !== null) {
|
||||
$arr[] = $last;
|
||||
}
|
||||
|
||||
return implode(' ', $arr);
|
||||
}
|
||||
|
||||
if ($first === null && $last === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($first === null) {
|
||||
return $last;
|
||||
}
|
||||
|
||||
if ($last === null) {
|
||||
return $first;
|
||||
}
|
||||
|
||||
return $first . ' ' . $last;
|
||||
}
|
||||
}
|
||||
53
application/Espo/Core/ORM/MetadataDataProvider.php
Normal file
53
application/Espo/Core/ORM/MetadataDataProvider.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM;
|
||||
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Utils\Metadata\OrmMetadataData;
|
||||
use Espo\ORM\MetadataDataProvider as MetadataDataProviderInterface;
|
||||
|
||||
class MetadataDataProvider implements MetadataDataProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private OrmMetadataData $ormMetadataData,
|
||||
private Metadata $metadata
|
||||
) {}
|
||||
|
||||
public function get(): array
|
||||
{
|
||||
$data = $this->ormMetadataData->getData();
|
||||
|
||||
foreach (array_keys($data) as $entityType) {
|
||||
$data[$entityType]['fields'] = $this->metadata->get(['entityDefs', $entityType, 'fields']) ?? [];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
57
application/Espo/Core/ORM/PDO/PDOFactoryFactory.php
Normal file
57
application/Espo/Core/ORM/PDO/PDOFactoryFactory.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM\PDO;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\PDO\PDOFactory;
|
||||
use RuntimeException;
|
||||
|
||||
class PDOFactoryFactory
|
||||
{
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private InjectableFactory $injectableFactory
|
||||
) {}
|
||||
|
||||
public function create(string $platform): PDOFactory
|
||||
{
|
||||
/** @var ?class-string<PDOFactory> $className */
|
||||
$className =
|
||||
$this->metadata->get(['app', 'orm', 'platforms', $platform, 'pdoFactoryClassName']) ??
|
||||
$this->metadata->get(['app', 'orm', 'pdoFactoryClassNameMap', $platform]);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("Could not create PDOFactory.");
|
||||
}
|
||||
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM\QueryComposer\Part;
|
||||
|
||||
use Espo\ORM\QueryComposer\Part\FunctionConverterFactory as FunctionConverterFactoryInterface;
|
||||
use Espo\ORM\QueryComposer\Part\FunctionConverter;
|
||||
use Espo\ORM\DatabaseParams;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\InjectableFactory;
|
||||
|
||||
use LogicException;
|
||||
|
||||
class FunctionConverterFactory implements FunctionConverterFactoryInterface
|
||||
{
|
||||
/** @var array<string, FunctionConverter> */
|
||||
private $hash = [];
|
||||
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private InjectableFactory $injectableFactory,
|
||||
private DatabaseParams $databaseParams
|
||||
) {}
|
||||
|
||||
public function create(string $name): FunctionConverter
|
||||
{
|
||||
$className = $this->getClassName($name);
|
||||
|
||||
if ($className === null) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
|
||||
public function isCreatable(string $name): bool
|
||||
{
|
||||
if ($this->getClassName($name) === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?class-string<FunctionConverter>
|
||||
*/
|
||||
private function getClassName(string $name): ?string
|
||||
{
|
||||
if (!array_key_exists($name, $this->hash)) {
|
||||
/** @var string $platform */
|
||||
$platform = $this->databaseParams->getPlatform();
|
||||
|
||||
$this->hash[$name] =
|
||||
$this->metadata->get(['app', 'orm', 'platforms', $platform, 'functionConverterClassNameMap', $name]) ??
|
||||
$this->metadata->get(['app', 'orm', 'functionConverterClassNameMap_' . $platform, $name]);
|
||||
|
||||
}
|
||||
|
||||
/** @var ?class-string<FunctionConverter> */
|
||||
return $this->hash[$name];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM\QueryComposer\Part\FunctionConverters;
|
||||
|
||||
use Espo\ORM\QueryComposer\Part\FunctionConverter;
|
||||
|
||||
class Abs implements FunctionConverter
|
||||
{
|
||||
public function convert(string ...$argumentList): string
|
||||
{
|
||||
return 'ABS(' . implode(', ', $argumentList) . ')';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM\QueryComposer;
|
||||
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\ORM\EventDispatcher;
|
||||
use Espo\ORM\QueryComposer\Part\FunctionConverterFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\PDO\PDOProvider;
|
||||
use Espo\ORM\QueryComposer\QueryComposer;
|
||||
use Espo\ORM\Metadata as OrmMetadata;
|
||||
use Espo\ORM\EntityFactory;
|
||||
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
class QueryComposerFactory implements \Espo\ORM\QueryComposer\QueryComposerFactory
|
||||
{
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private InjectableFactory $injectableFactory,
|
||||
private PDOProvider $pdoProvider,
|
||||
private OrmMetadata $ormMetadata,
|
||||
private EntityFactory $entityFactory,
|
||||
private FunctionConverterFactory $functionConverterFactory,
|
||||
private EventDispatcher $eventDispatcher
|
||||
) {}
|
||||
|
||||
public function create(string $platform): QueryComposer
|
||||
{
|
||||
/** @var ?class-string<QueryComposer> $className */
|
||||
$className =
|
||||
$this->metadata->get(['app', 'orm', 'platforms', $platform, 'queryComposerClassName']) ??
|
||||
$this->metadata->get(['app', 'orm', 'queryComposerClassNameMap', $platform]);
|
||||
|
||||
if (!$className) {
|
||||
/** @var class-string<QueryComposer> $className */
|
||||
$className = "Espo\\ORM\\QueryComposer\\{$platform}QueryComposer";
|
||||
}
|
||||
|
||||
if (!class_exists($className)) {
|
||||
throw new RuntimeException("Query composer for '{$platform}' platform does not exist.");
|
||||
}
|
||||
|
||||
$bindingContainer = BindingContainerBuilder::create()
|
||||
->bindInstance(PDO::class, $this->pdoProvider->get())
|
||||
->bindInstance(OrmMetadata::class, $this->ormMetadata)
|
||||
->bindInstance(EntityFactory::class, $this->entityFactory)
|
||||
->bindInstance(FunctionConverterFactory::class, $this->functionConverterFactory)
|
||||
->bindInstance(EventDispatcher::class, $this->eventDispatcher)
|
||||
->build();
|
||||
|
||||
return $this->injectableFactory->createWithBinding($className, $bindingContainer);
|
||||
}
|
||||
}
|
||||
121
application/Espo/Core/ORM/Repository/HookMediator.php
Normal file
121
application/Espo/Core/ORM/Repository/HookMediator.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero 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;
|
||||
use Espo\ORM\Query\Select;
|
||||
use Espo\ORM\Repository\EmptyHookMediator;
|
||||
use Espo\Core\HookManager;
|
||||
use Espo\Core\ORM\Repository\Option\SaveOption;
|
||||
|
||||
class HookMediator extends EmptyHookMediator
|
||||
{
|
||||
public function __construct(protected HookManager $hookManager)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @param ?array<string, mixed> $columnData
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function afterRelate(
|
||||
Entity $entity,
|
||||
string $relationName,
|
||||
Entity $foreignEntity,
|
||||
?array $columnData,
|
||||
array $options
|
||||
): void {
|
||||
|
||||
if (!empty($options[SaveOption::SKIP_HOOKS])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hookData = [
|
||||
'relationName' => $relationName,
|
||||
'relationData' => $columnData,
|
||||
'foreignEntity' => $foreignEntity,
|
||||
'foreignId' => $foreignEntity->getId(),
|
||||
];
|
||||
|
||||
$this->hookManager->process(
|
||||
$entity->getEntityType(),
|
||||
'afterRelate',
|
||||
$entity,
|
||||
$options,
|
||||
$hookData
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function afterUnrelate(Entity $entity, string $relationName, Entity $foreignEntity, array $options): void
|
||||
{
|
||||
if (!empty($options[Option\SaveOption::SKIP_HOOKS])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hookData = [
|
||||
'relationName' => $relationName,
|
||||
'foreignEntity' => $foreignEntity,
|
||||
'foreignId' => $foreignEntity->getId(),
|
||||
];
|
||||
|
||||
$this->hookManager->process(
|
||||
$entity->getEntityType(),
|
||||
'afterUnrelate',
|
||||
$entity,
|
||||
$options,
|
||||
$hookData
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function afterMassRelate(Entity $entity, string $relationName, Select $query, array $options): void
|
||||
{
|
||||
if (!empty($options[SaveOption::SKIP_HOOKS])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hookData = [
|
||||
'relationName' => $relationName,
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$this->hookManager->process(
|
||||
$entity->getEntityType(),
|
||||
'afterMassRelate',
|
||||
$entity,
|
||||
$options,
|
||||
$hookData
|
||||
);
|
||||
}
|
||||
}
|
||||
59
application/Espo/Core/ORM/Repository/Option/RemoveOption.php
Normal file
59
application/Espo/Core/ORM/Repository/Option/RemoveOption.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM\Repository\Option;
|
||||
|
||||
/**
|
||||
* Save options.
|
||||
*
|
||||
* @since 9.2.0
|
||||
*/
|
||||
class RemoveOption
|
||||
{
|
||||
/**
|
||||
* Silent. Boolean.
|
||||
* Skip stream notes, notifications, webhooks.
|
||||
*/
|
||||
public const SILENT = 'silent';
|
||||
|
||||
/**
|
||||
* Called from a Record service.
|
||||
*/
|
||||
public const API = 'api';
|
||||
|
||||
/**
|
||||
* When saved in Mass-Remove.
|
||||
*/
|
||||
public const MASS_REMOVE = 'massRemove';
|
||||
|
||||
/**
|
||||
* Override modified-by. String.
|
||||
*/
|
||||
public const MODIFIED_BY_ID = 'modifiedById';
|
||||
}
|
||||
162
application/Espo/Core/ORM/Repository/Option/SaveContext.php
Normal file
162
application/Espo/Core/ORM/Repository/Option/SaveContext.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM\Repository\Option;
|
||||
|
||||
use Closure;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\Repository\Option\SaveOptions;
|
||||
|
||||
/**
|
||||
* A save context.
|
||||
*
|
||||
* If a save invokes another save, the context instance should not be re-used.
|
||||
* If a save invokes a relate action, the context can be passed to that action.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*/
|
||||
class SaveContext
|
||||
{
|
||||
public const NAME = 'context';
|
||||
|
||||
private string $actionId;
|
||||
private bool $linkUpdated = false;
|
||||
|
||||
/** @var Closure[] */
|
||||
private array $deferredActions = [];
|
||||
|
||||
/**
|
||||
* @param ?string $actionId An action ID.
|
||||
*/
|
||||
public function __construct(
|
||||
?string $actionId = null,
|
||||
) {
|
||||
$this->actionId = $actionId ?? Util::generateId();
|
||||
}
|
||||
|
||||
/**
|
||||
* An action ID. Used to group notifications. If a save invokes another save, the same ID can be re-used,
|
||||
* but the context instance should not be re-used. Create a derived context for this.
|
||||
*
|
||||
* @since 9.2.0
|
||||
*/
|
||||
public function getActionId(): string
|
||||
{
|
||||
return $this->actionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Since v9.2.0. Use `getActionId`.
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->getActionId();
|
||||
}
|
||||
|
||||
public function setLinkUpdated(): self
|
||||
{
|
||||
$this->linkUpdated = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isLinkUpdated(): bool
|
||||
{
|
||||
return $this->linkUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain from save options.
|
||||
*
|
||||
* @return ?self
|
||||
* @since 9.2.0.
|
||||
*/
|
||||
public static function obtainFromOptions(SaveOptions $options): ?self
|
||||
{
|
||||
$saveContext = $options->get(self::NAME);
|
||||
|
||||
if (!$saveContext instanceof self) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $saveContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain from raw save options.
|
||||
*
|
||||
* @param array<string, mixed> $options
|
||||
* @return ?self
|
||||
* @since 9.2.0.
|
||||
*/
|
||||
public static function obtainFromRawOptions(array $options): ?self
|
||||
{
|
||||
$saveContext = $options[self::NAME] ?? null;
|
||||
|
||||
if (!$saveContext instanceof self) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $saveContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a deferred action.
|
||||
*
|
||||
* @param Closure $callback A callback.
|
||||
* @since 9.2.0.
|
||||
*/
|
||||
public function addDeferredAction(Closure $callback): void
|
||||
{
|
||||
$this->deferredActions[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @since 9.2.0.
|
||||
*/
|
||||
public function callDeferredActions(): void
|
||||
{
|
||||
foreach ($this->deferredActions as $callback) {
|
||||
$callback();
|
||||
}
|
||||
|
||||
$this->deferredActions = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a derived context. To be used for nested saves.
|
||||
*
|
||||
* @since 9.2.0
|
||||
*/
|
||||
public function createDerived(): self
|
||||
{
|
||||
return new self($this->actionId);
|
||||
}
|
||||
}
|
||||
117
application/Espo/Core/ORM/Repository/Option/SaveOption.php
Normal file
117
application/Espo/Core/ORM/Repository/Option/SaveOption.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM\Repository\Option;
|
||||
|
||||
use Espo\ORM\Repository\Option\SaveOption as BaseSaveOption;
|
||||
|
||||
/**
|
||||
* Save options.
|
||||
*/
|
||||
class SaveOption
|
||||
{
|
||||
/**
|
||||
* Silent. Boolean.
|
||||
* Skip stream notes, notifications, webhooks.
|
||||
*/
|
||||
public const SILENT = 'silent';
|
||||
/**
|
||||
* Import. Boolean.
|
||||
*/
|
||||
public const IMPORT = 'import';
|
||||
/**
|
||||
* Called from a Record service.
|
||||
* @since 8.0.1
|
||||
*/
|
||||
public const API = 'api';
|
||||
/**
|
||||
* Skip all additional processing. Boolean.
|
||||
*/
|
||||
public const SKIP_ALL = BaseSaveOption::SKIP_ALL;
|
||||
/**
|
||||
* Keep new. Boolean.
|
||||
*/
|
||||
public const KEEP_NEW = BaseSaveOption::KEEP_NEW;
|
||||
/**
|
||||
* Keep dirty. Boolean.
|
||||
*/
|
||||
public const KEEP_DIRTY = BaseSaveOption::KEEP_DIRTY;
|
||||
/**
|
||||
* Keep an entity relations map. Boolean.
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public const KEEP_RELATIONS = BaseSaveOption::KEEP_RELATIONS;
|
||||
/**
|
||||
* Skip hooks. Boolean.
|
||||
*/
|
||||
public const SKIP_HOOKS = 'skipHooks';
|
||||
/**
|
||||
* Skip setting created-by. Boolean.
|
||||
*/
|
||||
public const SKIP_CREATED_BY = 'skipCreatedBy';
|
||||
/**
|
||||
* Skip setting modified-by. Boolean.
|
||||
*/
|
||||
public const SKIP_MODIFIED_BY = 'skipModifiedBy';
|
||||
/**
|
||||
* Override created-by. String.
|
||||
*/
|
||||
public const CREATED_BY_ID = 'createdById';
|
||||
/**
|
||||
* Override modified-by. String.
|
||||
*/
|
||||
public const MODIFIED_BY_ID = 'modifiedById';
|
||||
/**
|
||||
* A duplicate source ID. A record that is being duplicated.
|
||||
* @since 8.4.0
|
||||
*/
|
||||
public const DUPLICATE_SOURCE_ID = 'duplicateSourceId';
|
||||
|
||||
/**
|
||||
* When saved in Mass-Update.
|
||||
* @since 8.4.0
|
||||
*/
|
||||
public const MASS_UPDATE = 'massUpdate';
|
||||
/**
|
||||
* Skip stream notes. Boolean.
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public const NO_STREAM = 'noStream';
|
||||
/**
|
||||
* Skip notification. Boolean.
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public const NO_NOTIFICATIONS = 'noNotifications';
|
||||
|
||||
/**
|
||||
* Skip audit log records.
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public const SKIP_AUDITED = 'skipAudited';
|
||||
}
|
||||
78
application/Espo/Core/ORM/RepositoryFactory.php
Normal file
78
application/Espo/Core/ORM/RepositoryFactory.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM;
|
||||
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\Binding\ContextualBinder;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\ORM\Entity as Entity;
|
||||
use Espo\ORM\EntityFactory as EntityFactoryInterface;
|
||||
use Espo\ORM\Relation\RelationsMap;
|
||||
use Espo\ORM\Repository\Repository;
|
||||
use Espo\ORM\Repository\RepositoryFactory as RepositoryFactoryInterface;
|
||||
|
||||
class RepositoryFactory implements RepositoryFactoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityFactoryInterface $entityFactory,
|
||||
private InjectableFactory $injectableFactory,
|
||||
private ClassNameProvider $classNameProvider,
|
||||
private RelationsMap $relationsMap,
|
||||
) {}
|
||||
|
||||
public function create(string $entityType): Repository
|
||||
{
|
||||
$className = $this->getClassName($entityType);
|
||||
|
||||
return $this->injectableFactory->createWithBinding(
|
||||
$className,
|
||||
BindingContainerBuilder::create()
|
||||
->bindInstance(EntityFactoryInterface::class, $this->entityFactory)
|
||||
->bindInstance(EntityFactory::class, $this->entityFactory)
|
||||
->bindInstance(RelationsMap::class, $this->relationsMap)
|
||||
->inContext(
|
||||
$className,
|
||||
function (ContextualBinder $binder) use ($entityType) {
|
||||
$binder->bindValue('$entityType', $entityType);
|
||||
}
|
||||
)
|
||||
->build()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<Repository<Entity>>
|
||||
*/
|
||||
private function getClassName(string $entityType): string
|
||||
{
|
||||
/** @var class-string<Repository<Entity>> */
|
||||
return $this->classNameProvider->getRepositoryClassName($entityType);
|
||||
}
|
||||
}
|
||||
67
application/Espo/Core/ORM/Type/FieldType.php
Normal file
67
application/Espo/Core/ORM/Type/FieldType.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM\Type;
|
||||
|
||||
class FieldType
|
||||
{
|
||||
public const VARCHAR = 'varchar';
|
||||
public const BOOL = 'bool';
|
||||
public const TEXT = 'text';
|
||||
public const INT = 'int';
|
||||
public const FLOAT = 'float';
|
||||
public const DATE = 'date';
|
||||
public const DATETIME = 'datetime';
|
||||
public const DATETIME_OPTIONAL = 'datetimeOptional';
|
||||
public const ENUM = 'enum';
|
||||
public const MULTI_ENUM = 'multiEnum';
|
||||
public const ARRAY = 'array';
|
||||
public const CHECKLIST = 'checklist';
|
||||
public const CURRENCY = 'currency';
|
||||
public const CURRENCY_CONVERTED = 'currencyConverted';
|
||||
public const PERSON_NAME = 'personName';
|
||||
public const ADDRESS = 'address';
|
||||
public const EMAIL = 'email';
|
||||
public const PHONE = 'phone';
|
||||
public const AUTOINCREMENT = 'autoincrement';
|
||||
public const URL = 'url';
|
||||
public const NUMBER = 'number';
|
||||
public const LINK = 'link';
|
||||
public const LINK_ONE = 'linkOne';
|
||||
public const LINK_PARENT = 'linkParent';
|
||||
public const FILE = 'file';
|
||||
public const IMAGE = 'image';
|
||||
public const LINK_MULTIPLE = 'linkMultiple';
|
||||
public const ATTACHMENT_MULTIPLE = 'attachmentMultiple';
|
||||
public const FOREIGN = 'foreign';
|
||||
public const WYSIWYG = 'wysiwyg';
|
||||
public const JSON_ARRAY = 'jsonArray';
|
||||
public const JSON_OBJECT = 'jsonObject';
|
||||
public const PASSWORD = 'password';
|
||||
}
|
||||
86
application/Espo/Core/ORM/ValueFactoryFactory.php
Normal file
86
application/Espo/Core/ORM/ValueFactoryFactory.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://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 Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\ORM;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\Metadata as OrmMetadata;
|
||||
use Espo\ORM\Value\ValueFactory;
|
||||
use Espo\ORM\Value\ValueFactoryFactory as ValueFactoryFactoryInteface;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ValueFactoryFactory implements ValueFactoryFactoryInteface
|
||||
{
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
private OrmMetadata $ormMetadata,
|
||||
private InjectableFactory $injectableFactory
|
||||
) {}
|
||||
|
||||
public function isCreatable(string $entityType, string $field): bool
|
||||
{
|
||||
return $this->getClassName($entityType, $field) !== null;
|
||||
}
|
||||
|
||||
public function create(string $entityType, string $field): ValueFactory
|
||||
{
|
||||
$className = $this->getClassName($entityType, $field);
|
||||
|
||||
if (!$className) {
|
||||
throw new RuntimeException("Could not get ValueFactory for '{$entityType}.{$field}'.");
|
||||
}
|
||||
|
||||
return $this->injectableFactory->create($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?class-string<ValueFactory>
|
||||
*/
|
||||
private function getClassName(string $entityType, string $field): ?string
|
||||
{
|
||||
$fieldDefs = $this->ormMetadata
|
||||
->getDefs()
|
||||
->getEntity($entityType)
|
||||
->getField($field);
|
||||
|
||||
/** @var ?class-string<ValueFactory> $className */
|
||||
$className = $fieldDefs->getParam('valueFactoryClassName');
|
||||
|
||||
if ($className) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
$type = $fieldDefs->getType();
|
||||
|
||||
/** @var ?class-string<ValueFactory> */
|
||||
return $this->metadata->get(['fields', $type, 'valueFactoryClassName']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user