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."
+ }
+ }
+ }
+}