Symfony PropertyAccess komponent
Prv než prejdeme k validácii, predstavíme si Symfony PropertyAccess
. Ide o komponent, ktorý unifikuje prístup k atribútom objektov a polí. PHP umožňuje prístup k atribútom objektov aj pomocou operátorov, ktoré sa používajú pri poliach.
K atribútom objektov a polí pristupujeme pomocou property paths. Napríklad [index]
je ekvivalentom $data['index']
a prop.sub
je ekvivalentom $data->getProp()->getSub()
.
$ composer req symfony/property-access
Pomocou composera si nainštalujeme balík symfony/property-access
.
<?php // array_access.php require('vendor/autoload.php'); use Symfony\Component\PropertyAccess\PropertyAccess; $accessor = PropertyAccess::createPropertyAccessor(); $bag = [ 'new' => [ 'items' => ['coins' => 6, 'pens' => 3, 'keys' => 2], 'colours' => ['red', 'green', 'yellow'] ], 'borrowed' => [ 'items' => ['books' => 23, 'computers' => 2] ], ]; echo $accessor->getValue($bag, '[new][items][pens]') . "\n"; echo $accessor->getValue($bag, '[new][colours][0]') . "\n"; echo $accessor->getValue($bag, '[borrowed][items][books]') . "\n";
V tomto príklade máme viacnásobne vnorené polia. Pomocou PropertyAccess
komponentu pristupujeme k atribútom týchto vnorených polí. V prípate polí majú property paths hranaté zátvorky []
.
<?php // obj_access.php require('vendor/autoload.php'); use Symfony\Component\PropertyAccess\PropertyAccess; class User { public $name = ''; public $occupation = ''; } $user1 = new User(); $user1->name = 'John Doe'; $user1->occupation = 'gardener'; $user2 = new User(); $user2->name = 'Peter Novak'; $user2->occupation = 'programmer'; $users = ['user1' => $user1, 'user2' => $user2]; $accessor = PropertyAccess::createPropertyAccessor(); echo $accessor->getValue($users, '[user1].name') . "\n"; echo $accessor->getValue($users, '[user2].occupation') . "\n";
V druhom príklade máme pole objektov. K atribútom objektov pristupujeme pomocou operátora bodky.
Symfony Validator komponent
Symfony Validator komponent je pokročilý nástroj na validáciu dát. Bol inšpirovaný Java Bean Validation špecifikáciou.
Vstupným bodom do procesu validácie je Validator\Validation
. Pomocou neho získame objekt validátoru, ktorý vykonáva validáciu pomocou metódy validate()
.
$validator = Validation::createValidatorBuilder()->getValidator(); $violations = $validator->validate($name, $constraint);
Dáta sa validujú pomocou Validator\Constraints
pravidiel. Štandardne sa Validator\Constraints
dáva v Symfony alias Assert
:
use Symfony\Component\Validator\Constraints as Assert;
Pravidlá sa kategorizujú do skupín. Poznáme základné, reťazcové, dátumové, alebo finančné pravidlá. Môžeme si vytvoriť aj vlastné pravidlá. Pravidlá aplikujeme jednotlivo alebo častejšie v kolekciách Constraints\Collection
.
Pravidlá sa môžu špecifikovať a) PHP kódom, b) pomocou anotácií, alebo c) v externých YAML alebo XML súboroch.
Ak validačný proces neprejde, docháza k tzv. porušeniam pravidiel (violations). Porušenie pravidla reprezentuje ConstraintViolation
, ktorý obsahuje chybovú hlášku. Tú dostaneme pomocou metódy getMessage()
.
Metóda validate()
vracia ConstraintViolationList
. Ak je tento zoznam prázdny, validácia dát bola úspešná. Počet porušení môžeme zistiť pomocou funkcie count()
.
Príprava
Symfony Validator komponent si budeme demonštrovať na CLI príkladoch. Potrebujeme si nainštalovať viacero balíčkov a pripraviť autoloading.
{ "autoload": { "psr-4": { "App\\": "src/" } }, "require": { "symfony/validator": "^4.2", "symfony/var-dumper": "^4.2", "symfony/property-access": "^4.2", "doctrine/annotations": "^1.6", "doctrine/cache": "^1.8", "symfony/config": "^4.2", "symfony/translation": "^4.2" } }
Takto vyzerá composer.json
súbor.
Validácia PHP kódom
V nasledujúcich príkladoch budeme validovať dáta PHP kódom.
Validácia jednej premennej
V prvom príklade budeme validovať jednoduchú premennú.
<?php // single_val.php require('vendor/autoload.php'); use Symfony\Component\Validator\Validation; use Symfony\Component\Validator\Constraints as Assert; $name = ''; $constraint = new Assert\NotBlank; $validator = Validation::createValidatorBuilder()->getValidator(); $violations = $validator->validate($name, $constraint); // dump($violations); if (0 === count($violations)) { echo 'validation passed'; } else { echo $violations->get(0)->getMessage(); }
V príklade máme premennú $name
na ktorú aplikujeme pravidlo Assert\NotBlank
.
$validator = Validation::createValidatorBuilder()->getValidator();
Pomocou ValidatorBuilder
objektu vytvoríme validátor.
$violations = $validator->validate($name, $constraint);
Premennú validujeme pomocou metódy validate()
, ktorej zadáme názov premennej a pravidlo. Metóda vráti zoznam porušení pravidiel.
if (0 === count($violations)) { echo 'validation passed'; } else { echo $violations->get(0)->getMessage(); }
Pomocou count()
metódy zistíme počet porušení. Eventuálnu chybovú hlášku vypíšeme pomocou getMessage()
.
$ php single_val.php This value should not be blank.
Toto je výpis nášho programu.
Validácia pomocou viacerých pravidiel
Ak máme viaceré validačné pravidlá, použijeme Assert\Collection
.
<?php // multiple_val.php require('vendor/autoload.php'); use Symfony\Component\Validator\Validation; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\Validator\Constraints as Assert; $name = 'p'; $email = 'peter@gmailcom'; $vals = ['name' => $name, 'email' => $email]; $constraints = new Assert\Collection([ 'name' => [new Assert\Length(['min' => 2]), new Assert\Regex('/^[a-zA-Z1-9]+$/')], 'email' => [new Assert\Email, new Assert\notBlank], ]); $validator = Validation::createValidatorBuilder()->getValidator(); $violations = $validator->validate($vals, $constraints); $accessor = PropertyAccess::createPropertyAccessor(); if (count($violations) > 0) { $messages = []; foreach ($violations as $violation) { $accessor->setValue($messages, $violation->getPropertyPath(), $violation->getMessage()); } dump($messages); } else { echo 'validation passed'; }
V príklade máme dve premenné a viacero validačných pravidiel.
$constraints = new Assert\Collection([ 'name' => [new Assert\Length(['min' => 2]), new Assert\Regex('/^[a-zA-Z1-9]+$/')], 'email' => [new Assert\Email, new Assert\notBlank], ]);
Užívateľské meno sa musí skladať z alfanumerických znakov a mať aspoň dva znaky. Email nesmie byť prázdny a musí zodpovedať požiadavkám na emailovú adresu.
$accessor = PropertyAccess::createPropertyAccessor();
V tomto príklade tiež použijeme PropertyAccess
komponent na extrakciu názvov premenných a chybových hlášok do poľa. Použijeme ho z dôvodu, aby nám v názvoch premenných nezostali hranaté zátvorky (napr. [name]
).
foreach ($violations as $violation) { $accessor->setValue($messages, $violation->getPropertyPath(), $violation->getMessage()); } dump($messages);
Z ConstraintViolationList
získame premenné a zodpovedajúce chybové hlášky. Zapíšeme ich do poľa $messages
a vypíšeme na konzolu pomocou dump()
metódy. Metódu máme zo symfony/var-dumper
. Metóda nám poskytuje farebne vyladený a prívetivejší informačný výstup. Je to lepšia alternatíva k zabudovanej var_dump()
metóde.
$ php multiple_val.php array:2 [ "name" => "This value is too short. It should have 2 characters or more." "email" => "This value is not a valid email address." ]
Tentoraz máme dve chyby.
Vytvorenie vlastného pravidla
Pre vlastné pravidlo potrebujeme vytvoriť pravidlo a k nemu prislúchajúci validátor. V nasledujúcom príklade si vytvoríme pravidlo, ktoré bude prijímať len alfanumerické znaky.
<?php // src/Validator/Constraints/AlphaNumeric.php namespace App\Validator\Constraints; use Symfony\Component\Validator\Constraint; class AlphaNumeric extends Constraint { public $message = 'The string "{{ string }}" contains an illegal character: it can only contain letters or numbers.'; }
Máme AlphaNumeric
pravidlo. Trieda obsahuje chybovú hlášku.
<?php // src/Validator/Constraints/AlphaNumericValidator.php namespace App\Validator\Constraints; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\UnexpectedValueException; class AlphaNumericValidator extends ConstraintValidator { public function validate($value, Constraint $constraint) { if (!$constraint instanceof AlphaNumeric) { throw new UnexpectedTypeException($constraint, AlphaNumeric::class); } // custom constraints should ignore null and empty values to allow // other constraints (NotBlank, NotNull, etc.) 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'); } if (!preg_match('/^[a-zA-Z0-9]+$/', $value, $matches)) { $this->context->buildViolation($constraint->message) ->setParameter('{{ string }}', $value) ->addViolation(); } } }
Validačný proces sa vykoná v metóde validate()
triedy AlphaNumericValidator
.
if (!preg_match('/^[a-zA-Z0-9]+$/', $value, $matches)) { $this->context->buildViolation($constraint->message) ->setParameter('{{ string }}', $value) ->addViolation(); }
V tejto podmienke máme regulárny výraz, ktorý očakáva buď písmená alebo znaky. Ak hodnota nesplní regulárny výraz, vytvorí sa ConstraintViolation
.
<?php // custom_constraint.php require('vendor/autoload.php'); use Symfony\Component\Validator\Validation; use App\Validator\Constraints as MyAssert; use App\Validator\Constraints\AlphaNumeric; $name = 'Peter^'; $constraint = new MyAssert\AlphaNumeric; $validator = Validation::createValidatorBuilder()->getValidator(); $violations = $validator->validate($name, $constraint); // dump($violations); if (0 === count($violations)) { echo 'validation passed'; } else { echo $violations->get(0)->getMessage(); }
Príklad ukazuje použitie nášho nového pravidla.
$ php custom_constraint.php The string "Peter^" contains an illegal character: it can only contain letters or numbers.
Po spustení programu dostaneme takúto chybovú hlášku.
Preklad chybových hlášok
Pre preklad chybových hlášok do iných jazykov použijeme Translator
, ktorý máme v balíčku symfony/translation
.
Preklady sa nachádzajú v súboroch, ktoré môžu mať rôzny typ. Odporúčaný je XLIFF súbor. Jedná sa o špecializovaný XML formát. Súbory sa umiestňujú do preddefinovaných adresárov. Adresár translations
, ktorý použijeme v našom príklade, má z nich najvyššiu prioritu.
<?xml version="1.0"?> <!-- translations/validators.en.xlf --> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" target-language="en" datatype="plaintext" original="file.ext"> <body> <trans-unit id="name.not_blank"> <source>name.not_blank</source> <target>User name should not be blank</target> </trans-unit> </body> </file> </xliff>
Tento súbor je pre angličtinu. K reťazcom sa dostaneme pomocou identifikátorov. V našom prípade máme name.not_blank
.
<?xml version="1.0"?> <!-- translations/validators.sk.xlf --> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" target-language="sk" datatype="plaintext" original="file.ext"> <body> <trans-unit id="name.not_blank"> <source>name.not_blank</source> <target>Užívateľské meno nesmie byť prázdne</target> </trans-unit> </body> </file> </xliff>
Tento súbor je pre slovenčinu.
<?php // translate_val.php require('vendor/autoload.php'); use Symfony\Component\Validator\Validation; use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\MessageSelector; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Translation\Loader\XliffFileLoader; $locale = 'sk'; $translator = new Translator($locale); $translator->addLoader('xlf', new XliffFileLoader()); $translator->addResource('xlf', 'translations/validators.en.xlf', 'en', 'validators'); $translator->addResource('xlf', 'translations/validators.sk.xlf', 'sk', 'validators'); $validator = Validation::createValidatorBuilder() ->setTranslator($translator) ->setTranslationDomain('validators') ->getValidator(); $name = ''; $constraint = new Assert\NotBlank(['message' => 'name.not_blank']); $violations = $validator->validate($name, $constraint); // dump($violations); if (0 === count($violations)) { echo 'validation passed'; } else { echo $violations->get(0)->getMessage(); }
V príklade použijeme preklady pri validácii jednej premennej.
$locale = 'sk';
Zvolíme slovenskú lokalizáciu.
$translator = new Translator($locale); $translator->addLoader('xlf', new XliffFileLoader()); $translator->addResource('xlf', 'translations/validators.en.xlf', 'en', 'validators'); $translator->addResource('xlf', 'translations/validators.sk.xlf', 'sk', 'validators');
Vytvoríme Translator
a priradíme súbory prekladov. Preklady rozdeľujeme do skupín, ktorým sa hovorí doména (domain). V našom prípade máme doménu validators
.
$validator = Validation::createValidatorBuilder() ->setTranslator($translator) ->setTranslationDomain('validators') ->getValidator();
Pri vytváraní validátoru mu priradíme translator a doménu.
$constraint = new Assert\NotBlank(['message' => 'name.not_blank']);
Nakoniec validačnému pravidlu priradíme v atribúte message
prislúchajúci identifikátor prekladu.
$ php translate_val.php Užívateľské meno nesmie byť prázdne
Po spustení programu dostaneme takýto výstup.
Validácia pomocou XML súboru
V nasledujúcom príklade si zapíšeme validačné pravidlá do externého XML súboru.
<?php // src/Entity/User.php namespace App\Entity; use Symfony\Component\Validator\Constraints; class User { private $first_name; private $last_name; private $email; public function __construct(string $first_name, string $last_name, string $email) { $this->first_name = $first_name; $this->last_name = $last_name; $this->email = $email; } public function __toString() { return "$this->first_name $this->last_name $this->email"; } }
Máme objekt User
, ktorý má tri atribúty. Tieto atribúty budú mať validačné pravidlá umiestnené v XML súbore.
<?xml version="1.0" encoding="UTF-8" ?> <!-- config/validation.xml --> <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> <class name="App\Entity\User"> <property name="first_name"> <constraint name="NotBlank"/> </property> <property name="last_name"> <constraint name="NotBlank"/> </property> <property name="email"> <constraint name="Email"/> </property> </class> </constraint-mapping>
V tomto XML súbore si zadefinujeme validačné pravidlá. Atribúty sa definujú pomocou property
tagov a pre pravidlá slúžia constraint
tagy.
<property name="first_name"> <constraint name="NotBlank"/> </property>
Pre atribút first_name
aplikujeme pravidlo NotBlank
, teda meno užívateľa nesmie byť prázdne.
<?php // xml_validation.php require('vendor/autoload.php'); use App\Entity\User; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Validation; $user = new User('', 'Novak', 'pnovak@examplecom'); $validator = Validation::createValidatorBuilder() ->addXmlMapping('config/validation.xml')->getValidator(); $violations = $validator->validate($user); $messages = []; if (0 === count($violations)) { echo 'validation passed'; } else { foreach ($violations as $violation) { $messages[$violation->getPropertyPath()] = $violation->getMessage(); } dump($messages); }
V príklade validujeme objekt User
pomocou XML súboru. Pri vytváraní objektu validátora použijeme metódu addXmlMapping()
, ktorou určíme názov validačného XML súboru.
$ php xml_validation.php array:2 [ "first_name" => "This value should not be blank." "email" => "This value is not a valid email address." ]
Po spustení programu dostaneme takýto výpis.
Validácia pomocou anotácií
Pravdepodobne najčastejší spôsob validácie v súčasnosti v Symfony aplikáciách je pomocou anotácií.
<?php // src/Entity/User.php namespace App\Entity; use Symfony\Component\Validator\Constraints as Assert; class User { /** * @Assert\NotBlank */ public $first_name; /** * @Assert\NotBlank */ public $last_name; /** * @Assert\Email */ public $email; public function __construct(string $first_name, string $last_name, string $email) { $this->first_name = $first_name; $this->last_name = $last_name; $this->email = $email; } public function __toString() { return "$this->first_name $this->last_name $this->email"; } }
Validačné pravidlá sa nachádzajú v anotáciách, ktoré sú umiestnené v PHP komentároch.
/** * @Assert\NotBlank */ public $first_name;
Na atribút $first_name
aplikujeme pravidlo @Assert\NotBlank
. Ako sme si už uviedli vyššie, v Symfony je zaužívaný postup používať alias Assert
.
<?php // annot_val.php require('vendor/autoload.php'); use App\Entity\User; use Symfony\Component\Validator\Validation; use Doctrine\Common\Annotations\AnnotationRegistry; use Symfony\Component\Validator\Constraints as Assert; $user = new User('', 'Novak', 'pnovak@examplecom'); $loader = require __DIR__ . '/vendor/autoload.php'; AnnotationRegistry::registerLoader(array($loader, 'loadClass')); $validator = Validation::createValidatorBuilder() ->enableAnnotationMapping()->getValidator(); $violations = $validator->validate($user); $messages = []; if (0 === count($violations)) { echo 'validation passed'; } else { foreach ($violations as $violation) { $messages[$violation->getPropertyPath()] = $violation->getMessage(); } } dump($messages);
Tento príklad validuje objekt User
pomocou anotácií.
$loader = require __DIR__ . '/vendor/autoload.php'; AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
Toto je režijný kód pre spojazdnenie autoloadingu pre Doctrine anotácie. Musíme ho použiť, lebo pracujeme s terminálovou aplikáciou. V Symfony webových aplikáciách je takýto režijný kód už pre nás automaticky vygenerovaný.
$validator = Validation::createValidatorBuilder() ->enableAnnotationMapping()->getValidator(); $violations = $validator->validate($user);
Pri generovaní validátora nastavíme validáciu pomocou Doctrine anotácií metódou enableAnnotationMapping()
.
$ php annot_val.php array:2 [ "first_name" => "This value should not be blank." "email" => "This value is not a valid email address." ]
Pri spustení programu dostaneme dve chybové hlášky.
V tento časti seriálu sme sa podrobnejšie zaoberali validáciou dát pomocou Symfony Validator komponenta. V ďalšej časti seriálu si spojíme validáciu s formulármi a flash správami.