Symfony 5 Constraint Validation : Customize error message - php

I want to use the new NotCompromisedPassword released on SF 4.3 :
https://symfony.com/blog/new-in-symfony-4-3-compromised-password-validator
I've set it up on my validation.yaml like this :
App\Entity\User:
constraints:
- App\Validator\Constraints\ConstraintPassword: ~
properties:
plainPassword:
- Symfony\Component\Validator\Constraints\NotCompromisedPassword: ~
It works, but i want to customize the error message, for example, by using it directly on my ConstraintPasswordValidator.php :
<?php
namespace App\Validator\Constraints;
use App\Entity\User;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\NotCompromisedPassword;
use Symfony\Component\Validator\ConstraintValidator;
class ConstraintPasswordValidator extends ConstraintValidator
{
/**
* #param User $user
* #param Constraint $constraint
*/
public function validate($user, Constraint $constraint)
{
if (strlen($user->getPlainPassword()) < 8 || strlen($user->getPlainPassword() < 35)) {
$this->context->buildViolation($constraint->lengthError)
->addViolation();
}
// Doing something like that
$notCompromised = new NotCompromisedPassword();
$notCompromised->message = "My custom error message";
//Then, build the violation if password leaked
}
}
Maybe it needs to be instantiated and customized in my ConstraintPassword.php ? But i don't know how
<?php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
class ConstraintPassword extends Constraint
{
public $lengthError = 'Erreur : La longueur du mot de passe doit être comprise entre 8 et 35 caractères';
public function validatedBy()
{
return \get_class($this).'Validator';
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}

You can pass the message option on the validation.yaml
App\Entity\User:
properties:
plainPassword:
- Symfony\Component\Validator\Constraints\NotCompromisedPassword:
message: "You error message"
But if you want to validate a constraint into a validator, you can use :
class MyValidator extends ConstraintValidator
{
public function validate($value, Constraint $chain)
{
// Previous check...
$groups = $this->context->getGroup();
$violations = $this->context->getViolations();
$current = $violations->count();
// Execute the new constraint
$this->context->getValidator()
->inContext($this->context)
->validate($value, new MyOtherConstraint(), $groups);
// Check if the constraint has failed
if ($violations->count() !== $current) {
return;
}
}
}

Related

How to get an error message with a unique constraint

I have a form where a reference number gets added for a product. I've made this field unique because there will be no duplicate reference numbers.
The problem I'm having is when I add a reference number that is already attached to another product, I run into an exception Integrity constraint violation: 1062 Duplicate entry for key 'reference'. Instead I would like the form to display a message to the user to inform them that reference is already in use.
Edit: I have created a custom validator to check if the given reference already exists but I get the following error with this:
Type error: Too few arguments to function Backend\Modules\Glasses\Domain\Glasses\Validator\Constraints\DuplicateReferenceValidator::__construct(), 0 passed in /var/www/html/vendor/symfony/symfony/src/Symfony/Component/Validator/ContainerConstraintValidatorFactory.php on line 52 and exactly 1 expected
I can't seem to get my GlassesRepository in the DuplicateReferenceValidator
Data transfer object:
<?php
namespace Backend\Modules\Glasses\Domain\Glasses;
use Backend\Modules\Glasses\Domain\Brand\Brand;
use Backend\Modules\MediaLibrary\Domain\MediaGroup\MediaGroup;
use Common\Doctrine\Entity\Meta;
use Symfony\Component\Validator\Constraints as Assert;
use Backend\Modules\Glasses\Domain\Glasses\Validator\Constraints as CustomAssert;
class GlassesDataTransferObject
{
/*** Other fields ***/
/**
* #var string
*
* #Assert\NotBlank(message="err.FieldIsRequired")
* #CustomAssert\DuplicateReference
*/
public $reference;
/*** Other fields ***/
DuplicateReference.php
<?php
namespace Backend\Modules\Glasses\Domain\Glasses\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class DuplicateReference extends Constraint
{
public $message = '"{{ reference }}" already exists.';
}
DuplicateReferenceValidator.php
<?php
namespace Backend\Modules\Glasses\Domain\Glasses\Validator\Constraints;
use Backend\Modules\Glasses\Domain\Glasses\GlassesRepository;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class DuplicateReferenceValidator extends ConstraintValidator
{
private $repository;
/**
* DuplicateUserValidator constructor.
*/
public function __construct(GlassesRepository $repository)
{
$this->repository = $repository;
}
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof DuplicateReference) {
throw new UnexpectedTypeException($constraint, DuplicateReference::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 UnexpectedTypeException($value, 'string');
}
$qb = $this->repository->createQueryBuilder('g');
$qb->select('g.reference')
->where('g.reference = :reference')
->setParameter('reference', $value);
$match = $qb->getQuery()->execute();
if ($match) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ reference }}', $value)
->addViolation();
}
}
}
I figured it out myself.
I needed to add the entity manager to my services.yml file in order to be able to access it in the validator.
Backend\Modules\Glasses\Domain\Glasses\Validator\Constraints\DuplicateReferenceValidator:
attributes:
- "#doctrine.orm.entity_manager"

Symfony custom Constrait Validate for entity

I use symfony 2.8 and I have some entity and I need validate this entity by some condition
I create constrait ContainsInvValidator and call validate service in action and validate entity but when debugged I did not entered in ContainsInvValidator how to correct use custom validate ?
this is my ContainsInvValidator
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class ContainsInvValidator extends ConstraintValidator
{
public function validate($entity, Constraint $constraint)
{
if (!$entity->getInvoiceNumber()) {
$this->context->buildViolation($constraint->message)
->atPath('foo')
->addViolation();
}
if (!$entity->getReference()) {
$this->context->buildViolation($constraint->message)
->atPath('foo')
->addViolation();
}
if (!$entity->getInvoiceDate()) {
$this->context->buildViolation($constraint->message)
->atPath('foo')
->addViolation();
}
}
}
and ContainsInv:
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class ContainsInv extends Constraint
{
public $message = 'The string "{{ string }}" no valid.';
}
add config:
services:
app.contains_test_check_validator:
class: AppBundle\Validator\Constraints\ContainsInv
tags:
- { name: validator.constraint_validator }
and my entity class for which I create custom validator
/**
* InboundInvoice
* #ContainsInv(groups={"test_check"})
*/
class InboundInvoice
{
and then in my action
public function handleInvoiceStatusAction(Request $request, InboundInvoice $invoice)
{
$resultHandling = $invoice->changedStatus();
$errors = $this->get('validator')->validate($invoice, [], ['test_check']);
and in variables errors I have
‌Symfony\Component\Validator\ConstraintViolationList::__set_state(array(
'violations' =>
array (
),
))
Your approach is valid for a property of a class. However if you want to validate the whole class (and not a single property), you have to overwrite the getTargets method in your ContainsInv Constraint class.
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
See also Class Constraint Validator.

Unit testing validator constraints with symfony2.3

I would like to make a test unit using constraint but I have this error when running my test
This are my different classes and the obtaining error after running phpunit
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class Age18 extends Constraint
{
public $message = 'Vous devez avoir 18 ans.';
}
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class Age18Validator extends ConstraintValidator
{
public function validate($dateNaissance, Constraint $constraint)
{
if ($dateNaissance > new \DateTime("18 years ago"))
{
$this->context->addViolation($constraint->message);
}
}
}
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class Age18ValidatorTest extends \PHPUnit_Framework_TestCase
{
private $constraint;
public function setUp()
{
$this->constraint = $this->getMock('Symfony\Component\Validator\Constraint');
}
public function testValidate()
{
/*ConstraintValidator*/
$validator = new Age18Validator();
$context = $this
->getMockBuilder('Symfony\Component\Validator\ExecutionContext')
->disableOriginalConstructor()
->getMock('Age18Validator', array('validate'));
$context->expects($this->once())
->method('addViolation')
->with('Vous devez avoir 18 ans.');
$validator->initialize($context);
$validator->validate('10/10/2000', $this->constraint);
}
public function tearDown()
{
$this->constraint = null;
}
}
Expectation failed for method name is equal to <string:addViolation> when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
Please could you help me to solve this problem?
Thanks you!!
Check the type of your element: in the validator class you use the comparator between two DateTime object but in the test you pass a string to the validator.
This is my test class:
namespace Acme\DemoBundle\Tests\Form;
use Acme\DemoBundle\Validator\Constraints\Age18;
use Acme\DemoBundle\Validator\Constraints\Age18Validator;
class Age18ValidatorTest extends \PHPUnit_Framework_TestCase
{
private $constraint;
private $context;
public function setUp()
{
$this->constraint = new Age18();
$this->context = $this->getMockBuilder('Symfony\Component\Validator\ExecutionContext')->disableOriginalConstructor()->getMock();
}
public function testValidate()
{
/*ConstraintValidator*/
$validator = new Age18Validator();
$validator->initialize( $this->context);
$this->context->expects($this->once())
->method('addViolation')
->with($this->constraint->message,array());
$validator->validate(\Datetime::createFromFormat("d/m/Y","10/10/2000"), $this->constraint);
}
public function tearDown()
{
$this->constraint = null;
}
}
Hope this help

Constraint relative to Locale parameter in Symfony2

I need to validate a form field against bad words dictionary (Array for example). So to do this I have to create a new Constraint + ConstraintValidator. It works great, the only problem I have is that I want to have different dictionaries for different locales.
Example:
namespace MyNameSpace\Category\MyFormBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class ContainsNoBadWordsValidator extends ConstraintValidator
{
protected $badWordsEN = array('blabla');
protected $badWordsFR = array('otherblabla');
public function validate($value, Constraint $constraint)
{
if (in_array(strtolower($value), array_map('strtolower', $this->getBadWords()))) {
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
}
}
protected function getBadWords($locale = 'EN')
{
switch ($locale) {
case 'FR':
return $this->badWordsFR;
break;
default:
return $this->badWordsEN;
break;
}
}
}
So how do I pass the locale to Constraint? Or should I implement it differently?
The locale parameter is a member of the Request object.
However, the request object is not created all the time (eg. in a CLI application)
This solution allows you to decouple your validation from the request object, and let your validation to be easily unit-tested.
The LocaleHolder is a request-listener which will hold upon creation the %locale% parameter and then, switch to the Request locale when the event is triggered.
Note: The %locale% parameter is the default parameter defined in config.yml
Your validator must then get this LocaleHolder as a constructor parameter, in order to be aware of the current locale.
services.yml
Here, declare the two services you will need, the LocaleHolder and your validator.
services:
acme.locale_holder:
class: Acme\FooBundle\LocaleHolder
arguments:
- "%locale%"
tags:
-
name: kernel.event_listener
event: kernel.request
method: onKernelRequest
acme.validator.no_badwords:
class: Acme\FooBundle\Constraints\NoBadwordsValidator
arguments:
- #acme.locale_holder
tags:
-
name: validator.constraint_validator
alias: no_badwords
Acme\FooBundle\LocaleHolder
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class LocaleHolder
{
protected $locale;
public function __construct($default = 'EN')
{
$this->setLocale($default);
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$this->setLocale($request->getLocale());
}
public function getLocale()
{
return $this->locale;
}
public function setLocale($locale)
{
$this->locale = $locale;
}
}
Acme\FooBundle\Constraints
use Acme\FooBundle\LocaleHolder;
class ContainsNoBadwordsValidator extends ConstraintValidator
{
protected $holder;
public function __construct(LocaleHolder $holder)
{
$this->holder = $holder;
}
protected function getBadwords($locale = null)
{
$locale = $locale ?: $this->holder->getLocale();
// ...
}
}

Symfony2 - Custom validator and dependancy injection

I am trying to use dependancy injection for a custom validator, in order to be able to use the entityManager.
I followed the Symfony Example: Dependency Injection, but I am allways getting this error message:
FatalErrorException: Error: Class 'isdoi' not found in
/home/milos/workspace/merrin3/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Validator/ConstraintValidatorFactory.php
line 68
Here are my classes:
1. The IsDOI class:
<?php
namespace Merrin\MainBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class IsDOI extends Constraint
{
public $message_publisher_DOI = 'The Publisher DOI abbreviation does not correspond to the DOI you filled in !';
public $message_journal_DOI = 'No journal found with the DOI you filled in !';
public $journal;
public $doiAbbreviation;
public function validatedBy() {
return "isdoi";
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
2. The IsDOIValidator class:
<?php
namespace Merrin\MainBundle\Validator\Constraints;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class IsDOIValidator extends ConstraintValidator
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function validate($value, Constraint $constraint)
{
$em_mdpipub = $this->entityManager('mdpipub');
//Do some tests here...
}
}
3. Service:
merrin.main.validator.isdoi:
class: Merrin\MainBundle\Validator\Constraints\IsDOIValidator
arguments:
entityManager: "#doctrine.orm.entity_manager"
Where am I wrong? Thank you for your help.
You have wrong service file, when You add tags and alias you could use "isdoi" name
merrin.main.validator.isdoi:
class: Merrin\MainBundle\Validator\Constraints\IsDOIValidator
arguments:
entityManager: "#doctrine.orm.entity_manager"
tags:
- { name: validator.constraint_validator, alias: isdoi }
You're telling Symfony2 that the validator class for your constraint is isdoi (validateBy method). However, your validator is IsDOIValidator.
You must use :
public function validateBy()
{
return "IsDOIValidator";
}
However, if your Constraint class name is IsDOI, Symfony will automatically look for IsDOIValidator as a ConstraintValidator. The default behavior for validateBy is to append "Validator" to the constraint name, and look for the class with this name. So if you do not overload validateBy, Symfony2 will automatically search for IsDOIValidator.

Categories