template entities repositories use existing classes

This commit is contained in:
Yuri Kuznetsov
2023-07-14 10:01:26 +03:00
parent 60c8502d27
commit ef8c58e3b7
9 changed files with 234 additions and 86 deletions
+22 -3
View File
@@ -22,15 +22,15 @@
<option name="path" value="schema/metadata" />
<option name="mappingKind" value="Directory" />
</Item>
<Item>
<option name="path" value="schema/autoload.json" />
</Item>
<Item>
<option name="path" value="schema/module.json" />
</Item>
<Item>
<option name="path" value="schema/routes.json" />
</Item>
<Item>
<option name="path" value="schema/autoload.json" />
</Item>
</list>
</option>
</SchemaInfo>
@@ -588,6 +588,25 @@
</SchemaInfo>
</value>
</entry>
<entry key="metadata/app/entityTemplates">
<value>
<SchemaInfo>
<option name="generatedName" value="New Schema" />
<option name="name" value="metadata/app/entityTemplates" />
<option name="relativePathToSchema" value="schema/metadata/app/entityTemplates.json" />
<option name="schemaVersion" value="JSON Schema version 7" />
<option name="patterns">
<list>
<Item>
<option name="pattern" value="true" />
<option name="path" value="*/Resources/metadata/app/entityTemplates.json" />
<option name="mappingKind" value="Pattern" />
</Item>
</list>
</option>
</SchemaInfo>
</value>
</entry>
<entry key="metadata/app/export">
<value>
<SchemaInfo>
+6
View File
@@ -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"
@@ -0,0 +1,138 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\ORM;
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\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
) {}
/**
* @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->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->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;
}
}
+11 -18
View File
@@ -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<Entity>
*/
private function getClassName(string $entityType): ?string
{
/** @var ?class-string<Entity> */
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<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
+13 -22
View File
@@ -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<Repository<OrmEntity>> */
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<Repository<OrmEntity>>
*/
protected function getClassName(string $entityType): ?string
{
/** @var ?class-string<Repository<OrmEntity>> */
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<Repository<Entity>>
*/
private function getClassName(string $entityType): string
{
/** @var class-string<Repository<Entity>> */
return $this->classNameProvider->getRepositoryClassName($entityType);
}
}
@@ -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"
}
}
@@ -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"
]
}
@@ -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'])) {
+21
View File
@@ -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."
}
}
}
}