Walidator
Walidator
Walidacja danych jest nie odłączną częścią platformy Read_. Dane wprowadzone przez Dialogi (formularze), czy też za pomocą API wymagają weryfikacji poprawności. Dane muszą być sprawdzone przed zapisaniem ich w bazie lub przekazaniem do innej usługi.
Za pomocą biblioteki symfony/validator stworzyliśmy komponent \Ready\Component\System\Validator
, dzięki któremu możemy w prosty sposób stworzyć dedykowaną klasę walidatora do obsługi dedykowanego obiektu.
Tworzenie klasy walidatora
Aby móc użyć tego mechanizmu, musimy najpierw utworzyć klasę, która jeśli operuje na obiektach typu Bean
to rozszerza się ją o \Ready\Component\System\Validator\BeanValidator
w przeciwnym wypadku o \Ready\Component\System\Validator\ArrayValidator
.
Klasy te dostarczają już cały mechanizm walidacji. Po stronie developera pozostaje tylko skonfigurowanie reguł, które później będą sprawdzane.
Zacznijmy od przykładu, który w dalszej części tego artykułu omówimy.
use Ready\Component\System\Validator\BeanValidator;
use Ready\Component\System\Validator\Metadata\MetadataInterface;
use Ready\Component\System\Validator\Metadata\ArrayMetadata;
use LoggedUser;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;
use Ready\Component\System\Validator\Constraint\ContainsAlphanumeric;
use Ready\Component\System\Validator\Constraint\SqlRecord;
use Symfony\Component\Validator\Constraints\Type;
use Ready\Component\System\Validator\Constraint\EmployeeExist;
use Ready\Component\System\Validator\Constraint\RecordFromUsefulDatasetsExist;
use Ready\Component\System\Validator\Constraint\RecordFromSearchEngineExist;
use Symfony\Component\Validator\Constraints\Date;
use Symfony\Component\Validator\Constraints\GreaterThan;
use Symfony\Component\Validator\Constraints\CssColor;
use Symfony\Component\Validator\Constraints\Regex;
class Validator extends BeanValidator {
/**
* @inheritDoc
*/
public function configure(array $contextData): MetadataInterface {
$primaryKeyValue = $contextData['projid'] ?? NULL;
$conditions = 'is_del IS NOT TRUE AND ent_id = '.LoggedUser::getEntityID();
if ($primaryKeyValue) {
$conditions .= ' AND projid != '.(int)$primaryKeyValue;
}
/** @var string $start_ */
$start_ = $contextData['start_'] ?? SYSDATE;
$metadata = new ArrayMetadata();
$metadata->addConstraintsToField('projnm', TRUE, FALSE, [
new NotBlank(),
new NotNull(),
new ContainsAlphanumeric(),
new SqlRecord([
'tableName' => 'projects',
'conditionFieldName' => 'projnm',
'conditionFieldType' => SqlRecord::FIELD_TYPE_STRING_REGEX,
'additionalConditions' => $conditions,
'recordExist' => FALSE,
'message' => \Translator::translate('Projekt o takiej nazwie {{ value }} już istnieje.'),
]),
]);
$metadata->addConstraintsToField('number', TRUE, FALSE, [
new NotBlank(),
new NotNull(),
new Regex([
'pattern' => '/^\d[\d-]*\d$/',
'match' => TRUE,
'message' => \Translator::translate('Numer projektu zawiera nie dozwolone znaki (0-9)')
]),
new SqlRecord([
'tableName' => 'projects',
'conditionFieldName' => 'number',
'conditionFieldType' => SqlRecord::FIELD_TYPE_STRING_REGEX,
'additionalConditions' => $conditions,
'recordExist' => FALSE,
'message' => \Translator::translate('Projekt o takim numerze {{ value }} już istnieje.'),
]),
]);
$metadata->addConstraintsToField('coorid', FALSE, FALSE, [
new NotNull(),
new Type(['type' => 'int']),
new EmployeeExist(),
]);
$metadata->addConstraintsToField('cadfid', FALSE, FALSE, [
new NotNull(),
new Type(['type' => 'int']),
new RecordFromUsefulDatasetsExist(['className' => \CalendarsDefDataSet::class])
]);
$metadata->addConstraintsToField('is_fix', FALSE, FALSE, [
new NotNull(),
new Type(['type' => 'bool']),
]);
$metadata->addConstraintsToField('dscrpt', FALSE, FALSE, [
new ContainsAlphanumeric(),
]);
$metadata->addConstraintsToField('contid', FALSE, FALSE, [
new Type(['type' => 'int']),
new RecordFromSearchEngineExist(['className' => \ContactSearchEngine::class, 'fieldName' => 'contid']),
]);
$metadata->addConstraintsToField('prior_', FALSE, FALSE, [
new Type(['type' => 'int']),
]);
$metadata->addConstraintsToField('start_', TRUE, FALSE, [
new Type(['type' => 'string']),
new Date(),
]);
$metadata->addConstraintsToField('end___', FALSE, FALSE, [
new Type(['type' => 'string']),
new Date(),
new GreaterThan(['value' => $start_]),
]);
$metadata->addConstraintsToField('colour', FALSE, FALSE, [
new Type(['type' => 'string']),
new CssColor(['formats' => [CssColor::HEX_LONG_WITH_ALPHA, CssColor::HEX_SHORT_WITH_ALPHA]]),
]);
return $metadata;
}
}
Zadaniem developera jest zaimplementowanie metody configure
public function configure(array $contextData): MetadataInterface;
Metoda ta jako argument przyjmuje argument $contextData
, który zawiera w sobie dane do zwalidowania. Na tym etapie możemy te dane wykorzystać, aby zwalidować pola od siebie zależne. Na przykład, aby data początkowa była wcześniejsza niż data końcowa.
$metadata->addConstraintsToField('end___', FALSE, FALSE, [
new Type(['type' => 'string']),
new Date(),
new GreaterThan(['value' => $start_]),
]);
Zadaniem metody configure
jest zwrócenie obiektu implementującego interfejs \Ready\Component\System\Validator\Metadata\MetadataInterface
, który zawiera w sobie zestaw reguł dla poszczególnych pól. W tym celu posłużymy się klasą \Ready\Component\System\Validator\Metadata\ArrayMetadata
, która ten interfejs implementuje oraz dostarcza nam następujące metody:
addConstraintsToField(string $fieldName, bool $createObjectMandatory, bool $modifyObjectMandatory, array $constraints): void
- metoda ta pozwala nam dodać reguły do pola (tworzenie i modyfikacja obiektu). Do metody możemy przekazać następujące argumenty:string $fieldName
- Nazwa pola, do którego chcemy dodać reguły.bool $createObjectMandatory
- Czy pole wymagane jest podczas tworzenia obiektubool $modifyObjectMandatory
- Czy pole wymagane jest podczas modyfikacji obiektuarray $constraints
- Lista reguł, które mają zostać sprawdzone podczas walidacji obiektu
addFieldConstraintsForCreateObject(string $fieldName, bool $mandatory, array $constraints): void
- metoda ta pozwala nam dodać reguły do pola (tylko tworzenie obiektu). Do metody możemy przekazać następujące argumenty:string $fieldName
- Nazwa pola, do którego chcemy dodać reguły.bool $mandatory
- Czy pole jest wymaganearray $constraints
- Lista reguł, które mają zostać sprawdzone podczas walidacji obiektu
addFieldConstraintsForModifyObject(string $fieldName, bool $mandatory, array $constraints): void
- metoda ta pozwala nam dodać reguły do pola (tylko modyfikacji obiektu). Do metody możemy przekazać następujące argumenty:string $fieldName
- Nazwa pola, do którego chcemy dodać reguły.bool $mandatory
- Czy pole jest wymaganearray $constraints
- Lista reguł, które mają zostać sprawdzone podczas walidacji obiektu
createFieldAsNestedArray(string $fieldName, bool $createObjectMandatory = false, bool $modifyObjectMandatory = false): NestedArray
- metoda ta pozwala dodać prostą tablicę podrzędną, która zwraca obiektNestedArray
. Obiekt ten pozwala na dodawanie do siebie pól oraz kolejnych tablic (wszystkie metody opisane w tym rozdziale). Po dodaniu wszystkich pól na koniec pracy z tym obiektem trzeba wywołać metodęaddField(): void
createFieldAsCollectionArray(string $fieldName, bool $createObjectMandatory = false, bool $modifyObjectMandatory = false): NestedArray
- metoda ta pozwala dodać kolekcję tablic podrzędnych, która zwraca obiektArrayCollection
. Obiekt ten pozwala na dodawanie do siebie pól oraz kolejnych tablic (wszystkie metody opisane w tym rozdziale). Po dodaniu wszystkich pól na koniec pracy z tym obiektem trzeba wywołać metodęaddField(int $minimalAmount = 1): void
Lista dostępnych reguł
Dostarczone przez bibliotekę symfony/validator
Biblioteki symfony dostarcza następujące reguły:
Podstawowe ograniczenia
Ograniczenia ciągów tekstowych
- ExpressionLanguageSyntax
- Length
- Url
- Regex
- Hostname
- Ip
- Cidr
- Json
- Uuid
- Ulid
- UserPassword
- NotCompromisedPassword
- CssColor
Ograniczenia porównań
- EqualTo
- NotEqualTo
- IdenticalTo
- NotIdenticalTo
- LessThan
- LessThanOrEqual
- GreaterThan
- GreaterThanOrEqual
- Range
- DivisibleBy
- Unique
Ograniczenia liczbowe
Ograniczenia daty/czasu
Ograniczenia wyboru
Ograniczenia plikowe
Ograniczenia finansowe i inne ograniczenia liczbowe
Inne ograniczenia
- AtLeastOneOf
- Sequentially
- Compound
- Callback
- Expression
- All
- Valid
- Cascade
- Traverse
- Collection
- Count
- UniqueEntity
- EnableAutoMapping
- DisableAutoMapping
Dostarczone przez platformę Ready_
- ContainsAlphanumeric
- EmployeeExist
- Nip
- RecordFromSearchEngineExist
- RecordFromUsefulDatasetsExist
- SqlRecord
Tworzenie własnych reguł
Aby stworzyć swoją własną regułę, musimy utworzyć we wspólnym katalogu dwie klasy ContainsAlphanumeric
oraz ContainsAlphanumericValidator
. Całość w przestrzeni nazw prezentowałą by się następująco:
- \ReadyApp\MyOwnApp\Model\Validator\Constraint\ContainsAlphanumeric
- \ReadyApp\MyOwnApp\Model\Validator\Constraint\ContainsAlphanumericValidator
Pierwsza klasa jest definicją reguły, która dziedziczy po \Symfony\Component\Validator\Constraint
, natomiast druga to walidator tej reguły, który z koleji dziedziczy po \Symfony\Component\Validator\ConstraintValidator
.
Definicja klasy reguły
Zacznijmy od przykładu
use Symfony\Component\Validator\Constraint;
class ContainsAlphanumeric extends Constraint {
public string $message;
public bool $allowPolishLetters = TRUE;
public bool $allowWhitespace = TRUE;
/**
* @inheritDoc
*/
public function __construct($options = NULL, array $groups = NULL, $payload = NULL) {
$this->message = \Translator::translate('Ciąg znaków "{{ string }}" zawiera nie dozwolone znaki. Możesz tylko używać znaków alfanumerycznych.');
parent::__construct($options, $groups, $payload);
}
}
Wszystkie opcje, jakie chcemy udostępnić do konfiguracji reguły, muszą być publicznymi właściwościami klasy.
Definicja klasy reguły walidatora
Zacznijmy od przykładu.
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class ContainsAlphanumericValidator extends ConstraintValidator {
public function validate($value, Constraint $constraint): void
{
if (!$constraint instanceof ContainsAlphanumeric) {
throw new UnexpectedTypeException($constraint, ContainsAlphanumeric::class);
}
// custom constraints should ignore null and empty values to allow
// other constraints (NotBlank, NotNull, etc.) to take care of that
if (null === $value || '' === $value) {
return;
}
if (!is_string($value)) {
// throw this exception if your validator cannot handle the passed type so that it can be marked as invalid
throw new UnexpectedValueException($value, 'string');
}
$pattern = '/^[a-zA-Z0-9';
if ($constraint->allowWhitespace) {
$pattern .= '\s';
}
if ($constraint->allowPolishLetters) {
$pattern .= 'ąćęłńóśżź';
}
$pattern .= ']+$/';
if (preg_match($pattern, $value)) {
return;
}
$violation = $this->context->buildViolation($constraint->message);
$violation->setParameter('{{ string }}', $value);
$violation->addViolation();
}
}
Podsumowanie
W ten oto sposób w bardzo szybki sposób napisaliśmy własną regułę, która będzie nam sprawdzać, czy w ciągu znaków znajdują się znaki alfanumeryczne.
Należy również zauważyć, że rzucamy wyjątki tylko w sytuacji, kiedy jest ewidentny błąd konfiguracji.