field validation refactoring

This commit is contained in:
Yuri Kuznetsov
2021-03-29 16:24:53 +03:00
parent fd6554658b
commit cd25f0c723
4 changed files with 240 additions and 120 deletions
@@ -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;
}
}
@@ -0,0 +1,97 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 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\FieldValidation;
class Params
{
private $skipFieldList = [];
private $typeSkipFieldListData = [];
public function __construct()
{
}
/**
* A field list that will be skipped in validation.
*
* @return array<string>
*/
public function getSkipFieldList() : array
{
return $this->skipFieldList;
}
/**
* A field list that will be skipped in validation for a specific validation type.
*
* @return array<string>
*/
public function getTypeSkipFieldList(string $type) : array
{
return $this->typeSkipFieldListData[$type] ?? [];
}
/**
* Clone with a specified field list that will be skipped in validation.
*
* @param array<string> $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<string> $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();
}
}
+24 -63
View File
@@ -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)) {
+24 -57
View File
@@ -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);
}
}