From cd25f0c723cfb0ceaef202bbbed50e612e6e7959 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 29 Mar 2021 16:24:53 +0300 Subject: [PATCH] field validation refactoring --- .../FieldValidationManager.php | 95 ++++++++++++++++++ .../Espo/Core/FieldValidation/Params.php | 97 +++++++++++++++++++ application/Espo/Services/Record.php | 87 +++++------------ application/Espo/Services/Settings.php | 81 +++++----------- 4 files changed, 240 insertions(+), 120 deletions(-) create mode 100644 application/Espo/Core/FieldValidation/Params.php diff --git a/application/Espo/Core/FieldValidation/FieldValidationManager.php b/application/Espo/Core/FieldValidation/FieldValidationManager.php index 1137b90472..483e334e0d 100644 --- a/application/Espo/Core/FieldValidation/FieldValidationManager.php +++ b/application/Espo/Core/FieldValidation/FieldValidationManager.php @@ -32,12 +32,16 @@ namespace Espo\Core\FieldValidation; use Espo\ORM\Entity; use Espo\Core\{ + Exceptions\BadRequest, Utils\Metadata, Utils\FieldUtil, }; use StdClass; +/** + * A field validation manager. + */ class FieldValidationManager { private $checkerCache = []; @@ -55,6 +59,52 @@ class FieldValidationManager $this->factory = $factory; } + /** + * Process validation. + * + * @param Entity $entity An entity. + * @param ?StdClass $data Raw request payload data. + * @param ?Params $params Validation additional parameters. + + * + * @throws BadRequest If data is not valid. + */ + public function process(Entity $entity, ?StdClass $data = null, ?Params $params = null) : void + { + $dataIsSet = $data !== null; + + if (!$data) { + $data = $data ?? (object) []; + } + + if (!$params) { + $params = new Params(); + } + + $fieldList = $this->fieldUtil->getEntityTypeFieldList($entity->getEntityType()); + + $skipFieldList = $params->getSkipFieldList(); + + foreach ($fieldList as $field) { + if (in_array($field, $skipFieldList)) { + continue; + } + + if ( + !$entity->isNew() && + $dataIsSet && + !$this->isFieldSetInData($entity->getEntityType(), $field, $data) + ) { + continue; + } + + $this->processField($entity, $field, $params, $data); + } + } + + /** + * Check a specific field for a specific validation type. + */ public function check(Entity $entity, string $field, string $type, ?StdClass $data = null) : bool { if (!$data) { @@ -89,6 +139,34 @@ class FieldValidationManager return true; } + private function processField(Entity $entity, string $field, Params $params, StdClass $data) : void + { + $entityType = $entity->getEntityType(); + + $fieldType = $this->fieldUtil->getEntityTypeFieldParam($entityType, $field, 'type'); + + $validationList = $this->metadata->get(['fields', $fieldType, 'validationList'], []); + $mandatoryValidationList = $this->metadata->get(['fields', $fieldType, 'mandatoryValidationList'], []); + + foreach ($validationList as $type) { + $value = $this->fieldUtil->getEntityTypeFieldParam($entityType, $field, $type); + + if (is_null($value) && !in_array($type, $mandatoryValidationList)) { + continue; + } + + if (in_array($field, $params->getTypeSkipFieldList($type))) { + continue; + } + + $result = $this->check($entity, $field, $type, $data); + + if (!$result) { + throw new BadRequest("Not valid data. Field: {$field}, validation type: {$type}."); + } + } + } + private function processFieldCheck( string $entityType, string $type, Entity $entity, string $field, $validationValue ) : bool { @@ -150,4 +228,21 @@ class FieldValidationManager $this->checkerCache[$key] = $this->factory->create($entityType, $field); } + + private function isFieldSetInData(string $entityType, string $field, StdClass $data) : bool + { + $attributeList = $this->fieldUtil->getActualAttributeList($entityType, $field); + + $isSet = false; + + foreach ($attributeList as $attribute) { + if (property_exists($data, $attribute)) { + $isSet = true; + + break; + } + } + + return $isSet; + } } diff --git a/application/Espo/Core/FieldValidation/Params.php b/application/Espo/Core/FieldValidation/Params.php new file mode 100644 index 0000000000..d363dfda3b --- /dev/null +++ b/application/Espo/Core/FieldValidation/Params.php @@ -0,0 +1,97 @@ + + */ + public function getSkipFieldList() : array + { + return $this->skipFieldList; + } + + /** + * A field list that will be skipped in validation for a specific validation type. + * + * @return array + */ + public function getTypeSkipFieldList(string $type) : array + { + return $this->typeSkipFieldListData[$type] ?? []; + } + + /** + * Clone with a specified field list that will be skipped in validation. + * + * @param array $list + */ + public function withSkipFieldList(array $list) : self + { + $obj = clone $this; + + $obj->skipFieldList = $list; + + return $obj; + } + + /** + * Clone with a specified field list that will be skipped in validation for a specific validation type. + * + * @param array $list + */ + public function withTypeSkipFieldList(string $type, array $list) : self + { + $obj = clone $this; + + $obj->typeSkipFieldListData[$type] = $list; + + return $obj; + } + + /** + * Create an empty instance. + */ + public function fromNothing() : self + { + return new self(); + } +} diff --git a/application/Espo/Services/Record.php b/application/Espo/Services/Record.php index 6901992fbb..b4d235be4f 100644 --- a/application/Espo/Services/Record.php +++ b/application/Espo/Services/Record.php @@ -61,6 +61,7 @@ use Espo\Core\{ Action\Params as ActionParams, Action\Data as ActionData, Action\ActionFactory, + FieldValidation\Params as FieldValidationParams, }; use Espo\Tools\{ @@ -179,6 +180,8 @@ class Record implements Crud, protected $validateSkipFieldList = []; + protected $validateRequiredSkipFieldList = []; + protected $findDuplicatesSelectAttributeList = ['id', 'name']; protected $duplicateIgnoreFieldList = []; @@ -295,6 +298,9 @@ class Record implements Crud, return $this->serviceFactory; } + /** + * @deprecated Since v6.2.0. + */ protected function getSelectManagerFactory() { return $this->selectManagerFactory; @@ -709,6 +715,9 @@ class Record implements Crud, } } + /** + * @deprecated + */ protected function getSelectManager($entityType = null) { if (!$entityType) { @@ -723,74 +732,26 @@ class Record implements Crud, $this->getRepository()->save($entity); } + /** + * @return void + * + * @throws BadRequest + */ public function processValidation(Entity $entity, $data) { - $fieldList = $this->fieldUtil->getEntityTypeFieldList($this->entityType); + $params = FieldValidationParams + ::fromNothing() + ->withSkipFieldList($this->validateSkipFieldList) + ->withTypeSkipFieldList('required', $this->validateRequiredSkipFieldList); - foreach ($fieldList as $field) { - if (in_array($field, $this->validateSkipFieldList)) { - continue; - } - - if (!$entity->isNew()) { - if (!$this->isFieldSetInData($data, $field)) { - continue; - } - } - - $this->processValidationField($entity, $field, $data); - } - } - - protected function processValidationField(Entity $entity, string $field, $data) - { - $fieldType = $this->fieldUtil->getEntityTypeFieldParam($this->entityType, $field, 'type'); - - $validationList = $this->getMetadata()->get(['fields', $fieldType, 'validationList'], []); - $mandatoryValidationList = $this->getMetadata()->get(['fields', $fieldType, 'mandatoryValidationList'], []); - - foreach ($validationList as $type) { - $value = $this->fieldUtil->getEntityTypeFieldParam($this->entityType, $field, $type); - - if (is_null($value)) { - if (!in_array($type, $mandatoryValidationList)) { - continue; - } - } - - $skipPropertyName = 'validate' . ucfirst($type) . 'SkipFieldList'; - - if (property_exists($this, $skipPropertyName)) { - $skipList = $this->$skipPropertyName; - - if (in_array($field, $skipList)) { - continue; - } - } - - if (!$this->fieldValidationManager->check($entity, $field, $type, $data)) { - throw new BadRequest("Not valid data. Field: '{$field}', type: {$type}."); - } - } - } - - protected function isFieldSetInData(StdClass $data, string $field) : bool - { - $attributeList = $this->fieldUtil->getActualAttributeList($this->entityType, $field); - - $isSet = false; - - foreach ($attributeList as $attribute) { - if (property_exists($data, $attribute)) { - $isSet = true; - - break; - } - } - - return $isSet; + $this->fieldValidationManager->process($entity, $data, $params); } + /** + * @return void + * + * @throws Forbidden + */ public function processAssignmentCheck(Entity $entity) { if (!$this->checkAssignment($entity)) { diff --git a/application/Espo/Services/Settings.php b/application/Espo/Services/Settings.php index 279c7b0a9f..0b8ea6f955 100644 --- a/application/Espo/Services/Settings.php +++ b/application/Espo/Services/Settings.php @@ -29,12 +29,10 @@ namespace Espo\Services; -use Espo\Core\Exceptions\Forbidden; -use Espo\Core\Exceptions\BadRequest; - use Espo\ORM\Entity; use Espo\Core\{ + Exceptions\Forbidden, ApplicationState, Acl, InjectableFactory, @@ -48,17 +46,28 @@ use Espo\Core\{ Currency\DatabasePopulator as CurrencyDatabasePopulator, }; +use StdClass; + class Settings { protected $applicationState; + protected $config; + protected $configWriter; + protected $fieldUtil; + protected $metadata; + protected $acl; + protected $entityManager; + protected $dataManager; + protected $fieldValidationManager; + protected $injectableFactory; public function __construct( @@ -85,7 +94,7 @@ class Settings $this->injectableFactory = $injectableFactory; } - public function getConfigData() + public function getConfigData() : StdClass { $data = $this->config->getAllData(); @@ -200,7 +209,7 @@ class Settings return $data; } - public function setConfigData($data) + public function setConfigData(StdClass $data) : void { $user = $this->applicationState->getUser(); @@ -232,6 +241,8 @@ class Settings $entity->set($data); + $entity->setAsNotNew(); + $this->processValidation($entity, $data); if ( @@ -255,12 +266,12 @@ class Settings } } - protected function populateDatabaseWithCurrencyRates() + protected function populateDatabaseWithCurrencyRates() : void { $this->injectableFactory->create(CurrencyDatabasePopulator::class)->process(); } - protected function filterData($data) + protected function filterData(StdCLass $data) : void { $user = $this->applicationState->getUser(); @@ -287,7 +298,7 @@ class Settings } } - public function getAdminOnlyItemList() + public function getAdminOnlyItemList() : array { $itemList = $this->config->getAdminOnlyItemList(); @@ -303,7 +314,7 @@ class Settings return $itemList; } - public function getUserOnlyItemList() + public function getUserOnlyItemList() : array { $itemList = $this->config->getUserOnlyItemList(); @@ -320,7 +331,7 @@ class Settings return $itemList; } - public function getSystemOnlyItemList() + public function getSystemOnlyItemList() : array { $itemList = $this->config->getSystemOnlyItemList(); @@ -337,7 +348,7 @@ class Settings return $itemList; } - public function getGlobalItemList() + public function getGlobalItemList() : array { $itemList = $this->config->get('globalItems', []); @@ -354,52 +365,8 @@ class Settings return $itemList; } - protected function processValidation(Entity $entity, $data) + protected function processValidation(Entity $entity, StdClass $data) : void { - $fieldList = $this->fieldUtil->getEntityTypeFieldList('Settings'); - - foreach ($fieldList as $field) { - if (!$this->isFieldSetInData($data, $field)) { - continue; - } - - $this->processValidationField($entity, $field, $data); - } - } - - protected function processValidationField(Entity $entity, string $field, $data) - { - $fieldType = $this->fieldUtil->getEntityTypeFieldParam('Settings', $field, 'type'); - $validationList = $this->metadata->get(['fields', $fieldType, 'validationList'], []); - $mandatoryValidationList = $this->metadata->get(['fields', $fieldType, 'mandatoryValidationList'], []); - - foreach ($validationList as $type) { - $value = $this->fieldUtil->getEntityTypeFieldParam('Settings', $field, $type); - - if (is_null($value) && !in_array($type, $mandatoryValidationList)) { - continue; - } - - if (!$this->fieldValidationManager->check($entity, $field, $type, $data)) { - throw new BadRequest("Not valid data. Field: '{$field}', type: {$type}."); - } - } - } - - protected function isFieldSetInData($data, $field) - { - $attributeList = $this->fieldUtil->getActualAttributeList('Settings', $field); - - $isSet = false; - - foreach ($attributeList as $attribute) { - if (property_exists($data, $attribute)) { - $isSet = true; - - break; - } - } - - return $isSet; + $this->fieldValidationManager->process($entity, $data); } }