diff --git a/.idea/jsonSchemas.xml b/.idea/jsonSchemas.xml index 806c8a166e..2318c1f133 100644 --- a/.idea/jsonSchemas.xml +++ b/.idea/jsonSchemas.xml @@ -22,15 +22,15 @@ @@ -588,6 +588,25 @@ + + + + + + + diff --git a/.vscode/settings.json b/.vscode/settings.json index 38e9f4a221..669ac82cbf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -268,6 +268,12 @@ ], "url": "./schema/metadata/app/entityTemplateList.json" }, + { + "fileMatch": [ + "*/Resources/metadata/app/entityTemplates.json" + ], + "url": "./schema/metadata/app/entityTemplates.json" + }, { "fileMatch": [ "*/Resources/metadata/app/export.json" diff --git a/application/Espo/Core/ORM/ClassNameProvider.php b/application/Espo/Core/ORM/ClassNameProvider.php new file mode 100644 index 0000000000..6b17f7e872 --- /dev/null +++ b/application/Espo/Core/ORM/ClassNameProvider.php @@ -0,0 +1,138 @@ + */ + private const DEFAULT_ENTITY_CLASS_NAME = BaseEntity::class; + /** @var class-string> */ + private const DEFAULT_REPOSITORY_CLASS_NAME = DatabaseRepository::class; + + /** @var array> */ + private array $entityCache = []; + + /** @var array>> */ + private array $repositoryCache = []; + + public function __construct( + private Metadata $metadata, + private ClassFinder $classFinder + ) {} + + /** + * @param string $entityType + * @return class-string + */ + 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> + */ + 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 + */ + private function findEntityClassName(string $entityType): string + { + /** @var ?class-string $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 $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> + */ + private function findRepositoryClassName(string $entityType): string + { + /** @var ?class-string> $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> $className */ + $className = $this->metadata->get(['app', 'entityTemplates', $template, 'repositoryClassName']); + } + + if ($className) { + return $className; + } + + return self::DEFAULT_REPOSITORY_CLASS_NAME; + } +} diff --git a/application/Espo/Core/ORM/EntityFactory.php b/application/Espo/Core/ORM/EntityFactory.php index 0447d23c9c..f9bfa4a66f 100644 --- a/application/Espo/Core/ORM/EntityFactory.php +++ b/application/Espo/Core/ORM/EntityFactory.php @@ -33,9 +33,6 @@ use Espo\Core\Binding\Binder; use Espo\Core\Binding\BindingContainer; use Espo\Core\Binding\BindingData; use Espo\Core\InjectableFactory; -use Espo\Core\ORM\Entity as BaseEntity; -use Espo\Core\Utils\ClassFinder; - use Espo\ORM\Entity; use Espo\ORM\EntityFactory as EntityFactoryInterface; use Espo\ORM\EntityManager; @@ -49,20 +46,11 @@ class EntityFactory implements EntityFactoryInterface private ?ValueAccessorFactory $valueAccessorFactory = null; public function __construct( - private ClassFinder $classFinder, + private ClassNameProvider $classNameProvider, private Helper $helper, private InjectableFactory $injectableFactory ) {} - /** - * @return ?class-string - */ - private function getClassName(string $entityType): ?string - { - /** @var ?class-string */ - return $this->classFinder->find('Entities', $entityType); - } - public function setEntityManager(EntityManager $entityManager): void { if ($this->entityManager) { @@ -85,10 +73,6 @@ class EntityFactory implements EntityFactoryInterface { $className = $this->getClassName($entityType); - if (!$className) { - $className = BaseEntity::class; - } - if (!$this->entityManager) { throw new RuntimeException(); } @@ -96,7 +80,7 @@ class EntityFactory implements EntityFactoryInterface $defs = $this->entityManager->getMetadata()->get($entityType); if (is_null($defs)) { - throw new RuntimeException("Entity '{$entityType}' is not defined in metadata."); + throw new RuntimeException("Entity '$entityType' is not defined in metadata."); } $bindingContainer = $this->getBindingContainer($className, $entityType, $defs); @@ -104,6 +88,15 @@ class EntityFactory implements EntityFactoryInterface return $this->injectableFactory->createWithBinding($className, $bindingContainer); } + /** + * @return class-string + */ + private function getClassName(string $entityType): string + { + /** @var class-string */ + return $this->classNameProvider->getEntityClassName($entityType); + } + /** * @param class-string $className * @param array $defs diff --git a/application/Espo/Core/ORM/RepositoryFactory.php b/application/Espo/Core/ORM/RepositoryFactory.php index 1e65a7fa92..930cd50404 100644 --- a/application/Espo/Core/ORM/RepositoryFactory.php +++ b/application/Espo/Core/ORM/RepositoryFactory.php @@ -32,41 +32,23 @@ namespace Espo\Core\ORM; use Espo\Core\Binding\BindingContainerBuilder; use Espo\Core\Binding\ContextualBinder; use Espo\Core\InjectableFactory; -use Espo\Core\Repositories\Database as DatabaseRepository; -use Espo\Core\Utils\ClassFinder; -use Espo\ORM\Entity as OrmEntity; +use Espo\ORM\Entity as Entity; use Espo\ORM\EntityFactory as EntityFactoryInterface; use Espo\ORM\Repository\Repository; use Espo\ORM\Repository\RepositoryFactory as RepositoryFactoryInterface; class RepositoryFactory implements RepositoryFactoryInterface { - /** @var class-string> */ - protected $defaultClassName = DatabaseRepository::class; - public function __construct( - protected EntityFactoryInterface $entityFactory, - protected InjectableFactory $injectableFactory, - protected ClassFinder $classFinder + private EntityFactoryInterface $entityFactory, + private InjectableFactory $injectableFactory, + private ClassNameProvider $classNameProvider ) {} - /** - * @return ?class-string> - */ - protected function getClassName(string $entityType): ?string - { - /** @var ?class-string> */ - return $this->classFinder->find('Repositories', $entityType); - } - public function create(string $entityType): Repository { $className = $this->getClassName($entityType); - if (!$className || !class_exists($className)) { - $className = $this->defaultClassName; - } - return $this->injectableFactory->createWithBinding( $className, BindingContainerBuilder::create() @@ -81,4 +63,13 @@ class RepositoryFactory implements RepositoryFactoryInterface ->build() ); } + + /** + * @return class-string> + */ + private function getClassName(string $entityType): string + { + /** @var class-string> */ + return $this->classNameProvider->getRepositoryClassName($entityType); + } } diff --git a/application/Espo/Resources/metadata/app/entityTemplates.json b/application/Espo/Resources/metadata/app/entityTemplates.json new file mode 100644 index 0000000000..a7990d4e3b --- /dev/null +++ b/application/Espo/Resources/metadata/app/entityTemplates.json @@ -0,0 +1,22 @@ +{ + "Base": { + "entityClassName": "Espo\\Core\\Templates\\Entities\\Base", + "repositoryClassName": "Espo\\Core\\Templates\\Repositories\\Base" + }, + "BasePlus": { + "entityClassName": "Espo\\Core\\Templates\\Entities\\BasePlus", + "repositoryClassName": "Espo\\Core\\Templates\\Repositories\\BasePlus" + }, + "Event": { + "entityClassName": "Espo\\Core\\Templates\\Entities\\Event", + "repositoryClassName": "Espo\\Core\\Templates\\Repositories\\Event" + }, + "Company": { + "entityClassName": "Espo\\Core\\Templates\\Entities\\Company", + "repositoryClassName": "Espo\\Core\\Templates\\Repositories\\Company" + }, + "Person": { + "entityClassName": "Espo\\Core\\Templates\\Entities\\Person", + "repositoryClassName": "Espo\\Core\\Templates\\Repositories\\Person" + } +} diff --git a/application/Espo/Resources/metadata/app/fieldProcessing.json b/application/Espo/Resources/metadata/app/fieldProcessing.json index c1dac143ed..0db3279e44 100644 --- a/application/Espo/Resources/metadata/app/fieldProcessing.json +++ b/application/Espo/Resources/metadata/app/fieldProcessing.json @@ -1,23 +1,3 @@ { - "readLoaderClassNameList": [ - "Espo\\Core\\FieldProcessing\\Link\\HasOneLoader", - "Espo\\Core\\FieldProcessing\\Link\\NotJoinedLoader", - "Espo\\Core\\FieldProcessing\\LinkMultiple\\Loader", - "Espo\\Core\\FieldProcessing\\LinkParent\\Loader", - "Espo\\Core\\FieldProcessing\\EmailAddress\\Loader", - "Espo\\Core\\FieldProcessing\\PhoneNumber\\Loader", - "Espo\\Core\\FieldProcessing\\Stream\\FollowersLoader" - ], - "listLoaderClassNameList": [ - "Espo\\Core\\FieldProcessing\\LinkParent\\Loader", - "Espo\\Core\\FieldProcessing\\LinkMultiple\\ListLoader" - ], - "saverClassNameList": [ - "Espo\\Core\\FieldProcessing\\EmailAddress\\Saver", - "Espo\\Core\\FieldProcessing\\PhoneNumber\\Saver", - "Espo\\Core\\FieldProcessing\\Relation\\Saver", - "Espo\\Core\\FieldProcessing\\MultiEnum\\Saver", - "Espo\\Core\\FieldProcessing\\File\\Saver", - "Espo\\Core\\FieldProcessing\\Wysiwyg\\Saver" - ] + } diff --git a/application/Espo/Tools/EntityManager/EntityManager.php b/application/Espo/Tools/EntityManager/EntityManager.php index 8bf947c05b..fb62502ed7 100644 --- a/application/Espo/Tools/EntityManager/EntityManager.php +++ b/application/Espo/Tools/EntityManager/EntityManager.php @@ -133,18 +133,6 @@ class EntityManager $templatePath = "application/Espo/Modules/$normalizedTemplateModuleName/Core/Templates"; } - $contents = "<" . "?" . "php\n\n". - "namespace Espo\Custom\Entities;\n\n". - "class $normalizedName extends $templateNamespace\Entities\\$type\n". - "{\n". - " public const ENTITY_TYPE = '$name';\n\n". - " protected \$entityType = '$name';\n". - "}\n"; - - $filePath = "custom/Espo/Custom/Entities/$normalizedName.php"; - - $this->fileManager->putContents($filePath, $contents); - $contents = "<" . "?" . "php\n\n". "namespace Espo\Custom\Controllers;\n\n". "class $normalizedName extends $templateNamespace\Controllers\\$type\n". @@ -165,16 +153,6 @@ class EntityManager $this->fileManager->putContents($filePath, $contents); - $contents = "<" . "?" . "php\n\n". - "namespace Espo\Custom\Repositories;\n\n". - "class $normalizedName extends $templateNamespace\Repositories\\$type\n". - "{\n". - "}\n"; - - $filePath = "custom/Espo/Custom/Repositories/$normalizedName.php"; - - $this->fileManager->putContents($filePath, $contents); - $stream = false; if (!empty($params['stream'])) { diff --git a/schema/metadata/app/entityTemplates.json b/schema/metadata/app/entityTemplates.json new file mode 100644 index 0000000000..ee1524bd71 --- /dev/null +++ b/schema/metadata/app/entityTemplates.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.espocrm.com/schema/metadata/app/entityTemplates.json", + "title": "app/entityTemplates", + "description": "Definitions for entity templates.", + "type": "object", + "additionalProperties": { + "type": "object", + "description": "A template name.", + "properties": { + "entityClassName": { + "type": "string", + "description": "An entity class name. Should implement Espo\\ORM\\Entity." + }, + "repositoryClassName": { + "type": "string", + "description": "An repository class name. Should implement Espo\\ORM\\Repository\\Repository." + } + } + } +}