From c635b23b5627f2a68aec3385cbaadd562eea57fa Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sun, 5 Nov 2023 10:08:23 +0200 Subject: [PATCH] metadata ref --- application/Espo/Core/Utils/Metadata.php | 150 ++---------------- .../Espo/Core/Utils/Metadata/Builder.php | 148 +++++++++++++++++ .../Core/Utils/Metadata/BuilderHelper.php | 97 +++++++++++ .../Espo/Core/Utils/Metadata/Helper.php | 69 +------- tests/unit/Espo/Core/Utils/MetadataTest.php | 13 +- 5 files changed, 268 insertions(+), 209 deletions(-) create mode 100644 application/Espo/Core/Utils/Metadata/Builder.php create mode 100644 application/Espo/Core/Utils/Metadata/BuilderHelper.php diff --git a/application/Espo/Core/Utils/Metadata.php b/application/Espo/Core/Utils/Metadata.php index b997afcd86..4851cee7a0 100644 --- a/application/Espo/Core/Utils/Metadata.php +++ b/application/Espo/Core/Utils/Metadata.php @@ -30,9 +30,8 @@ namespace Espo\Core\Utils; use Espo\Core\Utils\File\Manager as FileManager; -use Espo\Core\Utils\Metadata\Helper; -use Espo\Core\Utils\Resource\Reader as ResourceReader; -use Espo\Core\Utils\Resource\Reader\Params as ResourceReaderParams; +use Espo\Core\Utils\Metadata\Builder; +use Espo\Core\Utils\Metadata\BuilderHelper; use stdClass; use LogicException; @@ -56,54 +55,14 @@ class Metadata /** @var array> */ private $changedData = []; - /** @var array */ - private $forceAppendPathList = [ - ['app', 'rebuild', 'actionClassNameList'], - ['app', 'fieldProcessing', 'readLoaderClassNameList'], - ['app', 'fieldProcessing', 'listLoaderClassNameList'], - ['app', 'fieldProcessing', 'saverClassNameList'], - ['app', 'hook', 'suppressClassNameList'], - ['app', 'api', 'globalMiddlewareClassNameList'], - ['app', 'api', 'routeMiddlewareClassNameListMap', self::ANY_KEY], - ['app', 'api', 'controllerMiddlewareClassNameListMap', self::ANY_KEY], - ['app', 'api', 'controllerActionMiddlewareClassNameListMap', self::ANY_KEY], - ['app', 'entityManager', 'createHookClassNameList'], - ['app', 'entityManager', 'deleteHookClassNameList'], - ['app', 'entityManager', 'updateHookClassNameList'], - ['app', 'linkManager', 'createHookClassNameList'], - ['app', 'linkManager', 'deleteHookClassNameList'], - ['recordDefs', self::ANY_KEY, 'readLoaderClassNameList'], - ['recordDefs', self::ANY_KEY, 'listLoaderClassNameList'], - ['recordDefs', self::ANY_KEY, 'saverClassNameList'], - ['recordDefs', self::ANY_KEY, 'selectApplierClassNameList'], - ['recordDefs', self::ANY_KEY, 'beforeReadHookClassNameList'], - ['recordDefs', self::ANY_KEY, 'beforeCreateHookClassNameList'], - ['recordDefs', self::ANY_KEY, 'beforeUpdateHookClassNameList'], - ['recordDefs', self::ANY_KEY, 'beforeDeleteHookClassNameList'], - ['recordDefs', self::ANY_KEY, 'beforeLinkHookClassNameList'], - ['recordDefs', self::ANY_KEY, 'beforeUnlinkHookClassNameList'], - ]; - - private const ANY_KEY = '__ANY__'; - - private Helper $metadataHelper; - public function __construct( private FileManager $fileManager, private DataCache $dataCache, - private ResourceReader $resourceReader, private Module $module, + private Builder $builder, + private BuilderHelper $builderHelper, private bool $useCache = false - ){} - - private function getMetadataHelper(): Helper - { - if (!isset($this->metadataHelper)) { - $this->metadataHelper = new Helper($this); - } - - return $this->metadataHelper; - } + ) {} /** * Init metadata. @@ -162,28 +121,6 @@ class Metadata return Util::getValueByKey($this->getData(), $key, $default); } - /** - * Get all metadata. - * - * @/param bool $isJSON - * @/param bool $reload - * @/return array|string - */ - /*public function getAll(bool $isJSON = false, bool $reload = false) - { - if ($reload) { - $this->init($reload); - } - - assert($this->data !== null); - - if ($isJSON) { - return Json::encode($this->data); - } - - return $this->data; - }*/ - private function objInit(bool $reload = false): void { if (!$this->useCache) { @@ -199,12 +136,7 @@ class Metadata return; } - $readerParams = ResourceReaderParams::create() - ->withForceAppendPathList($this->forceAppendPathList); - - $this->objData = $this->resourceReader->read('metadata', $readerParams); - - $this->objData = $this->addAdditionalFieldsObj($this->objData); + $this->objData = $this->builder->build(); if ($this->useCache) { $this->dataCache->store($this->objCacheKey, $this->objData); @@ -241,71 +173,7 @@ class Metadata return $this->getObjData(); } - /** - * @param stdClass $data - * @return stdClass - */ - private function addAdditionalFieldsObj($data) - { - if (!isset($data->entityDefs)) { - return $data; - } - $fieldDefinitionList = Util::objectToArray($data->fields); - - foreach (get_object_vars($data->entityDefs) as $entityType => $entityDefsItem) { - if (isset($data->entityDefs->$entityType->collection)) { - /** @var stdClass $collectionItem */ - $collectionItem = $data->entityDefs->$entityType->collection; - - if (isset($collectionItem->orderBy)) { - $collectionItem->sortBy = $collectionItem->orderBy; - } - else if (isset($collectionItem->sortBy)) { - $collectionItem->orderBy = $collectionItem->sortBy; - } - - if (isset($collectionItem->order)) { - $collectionItem->asc = $collectionItem->order === 'asc'; - } - else if (isset($collectionItem->asc)) { - $collectionItem->order = $collectionItem->asc === true ? 'asc' : 'desc'; - } - } - - if (!isset($entityDefsItem->fields)) { - continue; - } - - foreach (get_object_vars($entityDefsItem->fields) as $field => $fieldDefsItem) { - $additionalFields = $this->getMetadataHelper()->getAdditionalFieldList( - $field, - Util::objectToArray($fieldDefsItem), $fieldDefinitionList - ); - - if (!$additionalFields) { - continue; - } - - foreach ($additionalFields as $subFieldName => $subFieldParams) { - $item = Util::arrayToObject($subFieldParams); - - if (isset($entityDefsItem->fields->$subFieldName)) { - $data->entityDefs->$entityType->fields->$subFieldName = DataUtil::merge( - $item, - $entityDefsItem->fields->$subFieldName - ); - - continue; - } - - $data->entityDefs->$entityType->fields->$subFieldName = $item; - } - } - } - - return $data; - } /** * Get metadata definition in custom directory. @@ -401,7 +269,7 @@ class Metadata switch ($key1) { case 'entityDefs': - // unset related additional fields, e.g. a field with "address" type + // Unset related additional fields, e.g. a field with an 'address' type. $fieldDefinitionList = $this->get('fields'); $unsetList = $unsets; @@ -411,7 +279,8 @@ class Metadata $fieldName = $matches[1]; $fieldPath = [$key1, $key2, 'fields', $fieldName]; - $additionalFields = $this->getMetadataHelper()->getAdditionalFieldList( + // @todo Revise the need. Additional fields are supposed to exist only in the build? + $additionalFields = $this->builderHelper->getAdditionalFieldList( $fieldName, $this->get($fieldPath, []), $fieldDefinitionList @@ -449,6 +318,7 @@ class Metadata $this->deletedData = $mergedDeletedData; /** @var array> $unsetDeletedData */ + /** @noinspection PhpRedundantOptionalArgumentInspection */ $unsetDeletedData = Util::unsetInArrayByValue('__APPEND__', $this->deletedData, true); $this->deletedData = $unsetDeletedData; diff --git a/application/Espo/Core/Utils/Metadata/Builder.php b/application/Espo/Core/Utils/Metadata/Builder.php new file mode 100644 index 0000000000..e40626d80d --- /dev/null +++ b/application/Espo/Core/Utils/Metadata/Builder.php @@ -0,0 +1,148 @@ + */ + private $forceAppendPathList = [ + ['app', 'rebuild', 'actionClassNameList'], + ['app', 'fieldProcessing', 'readLoaderClassNameList'], + ['app', 'fieldProcessing', 'listLoaderClassNameList'], + ['app', 'fieldProcessing', 'saverClassNameList'], + ['app', 'hook', 'suppressClassNameList'], + ['app', 'api', 'globalMiddlewareClassNameList'], + ['app', 'api', 'routeMiddlewareClassNameListMap', self::ANY_KEY], + ['app', 'api', 'controllerMiddlewareClassNameListMap', self::ANY_KEY], + ['app', 'api', 'controllerActionMiddlewareClassNameListMap', self::ANY_KEY], + ['app', 'entityManager', 'createHookClassNameList'], + ['app', 'entityManager', 'deleteHookClassNameList'], + ['app', 'entityManager', 'updateHookClassNameList'], + ['app', 'linkManager', 'createHookClassNameList'], + ['app', 'linkManager', 'deleteHookClassNameList'], + ['recordDefs', self::ANY_KEY, 'readLoaderClassNameList'], + ['recordDefs', self::ANY_KEY, 'listLoaderClassNameList'], + ['recordDefs', self::ANY_KEY, 'saverClassNameList'], + ['recordDefs', self::ANY_KEY, 'selectApplierClassNameList'], + ['recordDefs', self::ANY_KEY, 'beforeReadHookClassNameList'], + ['recordDefs', self::ANY_KEY, 'beforeCreateHookClassNameList'], + ['recordDefs', self::ANY_KEY, 'beforeUpdateHookClassNameList'], + ['recordDefs', self::ANY_KEY, 'beforeDeleteHookClassNameList'], + ['recordDefs', self::ANY_KEY, 'beforeLinkHookClassNameList'], + ['recordDefs', self::ANY_KEY, 'beforeUnlinkHookClassNameList'], + ]; + + private const ANY_KEY = '__ANY__'; + + public function __construct( + private ResourceReader $resourceReader, + private BuilderHelper $builderHelper + ) {} + + public function build(): stdClass + { + $readerParams = ResourceReaderParams::create() + ->withForceAppendPathList($this->forceAppendPathList); + + $data = $this->resourceReader->read('metadata', $readerParams); + + $this->addAdditionalField($data); + + return $data; + } + + private function addAdditionalField(stdClass $data): void + { + if (!isset($data->entityDefs)) { + return; + } + + $fieldDefinitionList = Util::objectToArray($data->fields); + + foreach (get_object_vars($data->entityDefs) as $entityType => $entityDefsItem) { + if (isset($data->entityDefs->$entityType->collection)) { + /** @var stdClass $collectionItem */ + $collectionItem = $data->entityDefs->$entityType->collection; + + if (isset($collectionItem->orderBy)) { + $collectionItem->sortBy = $collectionItem->orderBy; + } + else if (isset($collectionItem->sortBy)) { + $collectionItem->orderBy = $collectionItem->sortBy; + } + + if (isset($collectionItem->order)) { + $collectionItem->asc = $collectionItem->order === 'asc'; + } + else if (isset($collectionItem->asc)) { + $collectionItem->order = $collectionItem->asc === true ? 'asc' : 'desc'; + } + } + + if (!isset($entityDefsItem->fields)) { + continue; + } + + foreach (get_object_vars($entityDefsItem->fields) as $field => $fieldDefsItem) { + $additionalFields = $this->builderHelper->getAdditionalFieldList( + $field, + Util::objectToArray($fieldDefsItem), + $fieldDefinitionList + ); + + if (!$additionalFields) { + continue; + } + + foreach ($additionalFields as $subFieldName => $subFieldParams) { + $item = Util::arrayToObject($subFieldParams); + + if (isset($entityDefsItem->fields->$subFieldName)) { + $data->entityDefs->$entityType->fields->$subFieldName = + DataUtil::merge( + $item, + $entityDefsItem->fields->$subFieldName + ); + + continue; + } + + $data->entityDefs->$entityType->fields->$subFieldName = $item; + } + } + } + } +} diff --git a/application/Espo/Core/Utils/Metadata/BuilderHelper.php b/application/Espo/Core/Utils/Metadata/BuilderHelper.php new file mode 100644 index 0000000000..b32170653b --- /dev/null +++ b/application/Espo/Core/Utils/Metadata/BuilderHelper.php @@ -0,0 +1,97 @@ + fields. + * + * @var string[] + */ + private array $copiedDefParams = [ + 'readOnly', + 'disabled', + 'notStorable', + 'layoutListDisabled', + 'layoutDetailDisabled', + 'layoutMassUpdateDisabled', + 'layoutFiltersDisabled', + 'directAccessDisabled', + 'directUpdateDisabled', + 'customizationDisabled', + 'importDisabled', + 'exportDisabled', + ]; + + private string $defaultFieldNaming = 'postfix'; + + /** + * Get additional field list based on field definition in metadata 'fields'. + * + * @param string $fieldName + * @param array $fieldParams + * @param array $definitionList + * @return ?array + */ + public function getAdditionalFieldList(string $fieldName, array $fieldParams, array $definitionList): ?array + { + if (empty($fieldParams['type']) || empty($definitionList)) { + return null; + } + + $fieldType = $fieldParams['type']; + $fieldDefinition = $definitionList[$fieldType] ?? null; + + if ( + isset($fieldDefinition) && + !empty($fieldDefinition['fields']) && + is_array($fieldDefinition['fields']) + ) { + $copiedParams = array_intersect_key($fieldParams, array_flip($this->copiedDefParams)); + + $additionalFields = []; + + foreach ($fieldDefinition['fields'] as $subFieldName => $subFieldParams) { + $namingType = $fieldDefinition['naming'] ?? $this->defaultFieldNaming; + + $subFieldNaming = Util::getNaming($fieldName, $subFieldName, $namingType); + + $additionalFields[$subFieldNaming] = array_merge($copiedParams, $subFieldParams); + } + + return $additionalFields; + } + + return null; + } +} diff --git a/application/Espo/Core/Utils/Metadata/Helper.php b/application/Espo/Core/Utils/Metadata/Helper.php index d4ad0b4fa9..a2415b36d4 100644 --- a/application/Espo/Core/Utils/Metadata/Helper.php +++ b/application/Espo/Core/Utils/Metadata/Helper.php @@ -29,36 +29,10 @@ namespace Espo\Core\Utils\Metadata; -use Espo\Core\Utils\Util; use Espo\Core\Utils\Metadata; -/** - * Warning: Instantiated explicitly. - */ class Helper { - protected string $defaultNaming = 'postfix'; - - /** - * List of copied params for metadata -> 'fields' from parent items. - * - * @var string[] - */ - private array $copiedDefParams = [ - 'readOnly', - 'disabled', - 'notStorable', - 'layoutListDisabled', - 'layoutDetailDisabled', - 'layoutMassUpdateDisabled', - 'layoutFiltersDisabled', - 'directAccessDisabled', - 'directUpdateDisabled', - 'customizationDisabled', - 'importDisabled', - 'exportDisabled', - ]; - public function __construct(private Metadata $metadata) {} @@ -94,7 +68,7 @@ class Helper /** * Get link definition defined in 'fields' metadata. - * In linkDefs can be used as value (e.g. "type": "hasChildren") and/or variables (e.g. "entityName":"{entity}"). + * In linkDefs can be used as value (e.g. "type": "hasChildren") and/or variables (e.g. "entityName": "{entity}"). * Variables should be defined into fieldDefs (in 'entityDefs' metadata). * * @param string $entityType @@ -128,45 +102,4 @@ class Helper return $linkFieldDefsByType; } - - /** - * Get additional field list based on field definition in metadata 'fields'. - * - * @param string $fieldName - * @param array $fieldParams - * @param array $definitionList - * @return ?array - */ - public function getAdditionalFieldList($fieldName, array $fieldParams, array $definitionList) - { - if (empty($fieldParams['type']) || empty($definitionList)) { - return null; - } - - $fieldType = $fieldParams['type']; - $fieldDefinition = $definitionList[$fieldType] ?? null; - - if ( - isset($fieldDefinition) && - !empty($fieldDefinition['fields']) && - is_array($fieldDefinition['fields']) - ) { - $copiedParams = array_intersect_key($fieldParams, array_flip($this->copiedDefParams)); - - $additionalFields = []; - - // add additional fields - foreach ($fieldDefinition['fields'] as $subFieldName => $subFieldParams) { - $namingType = $fieldDefinition['naming'] ?? $this->defaultNaming; - - $subFieldNaming = Util::getNaming($fieldName, $subFieldName, $namingType); - - $additionalFields[$subFieldNaming] = array_merge($copiedParams, $subFieldParams); - } - - return $additionalFields; - } - - return null; - } } diff --git a/tests/unit/Espo/Core/Utils/MetadataTest.php b/tests/unit/Espo/Core/Utils/MetadataTest.php index 6aff0ce40e..63bcfb04e8 100644 --- a/tests/unit/Espo/Core/Utils/MetadataTest.php +++ b/tests/unit/Espo/Core/Utils/MetadataTest.php @@ -67,7 +67,18 @@ class MetadataTest extends \PHPUnit\Framework\TestCase $reader = new Reader($unifier, $unifierObj); - $this->object = new Metadata($this->fileManager, $this->dataCache, $reader, $module, true); + $builderHelper = new Metadata\BuilderHelper(); + + $builder = new Metadata\Builder($reader, $builderHelper); + + $this->object = new Metadata( + $this->fileManager, + $this->dataCache, + $module, + $builder, + $builderHelper, + true + ); $this->reflection = new ReflectionHelper($this->object);