Symfony custom Constrait Validate for entity - php

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.

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 - get an entity object in a field constraint class

I created my custom constraint validator:
class CustomConstraint extends Constraint
{
public $message = '';
}
class CustomConstraintValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
exit($this->context->getObject()); // returns null
}
}
In docs it is stated that:
Returns the currently validated object.
, but for me it returns NULL instead.
P.S. I do not want to assign this constraint to Entity, only to certain forms or fields.
My form property which is validated:
->add('rejectReasons', null, array(
'property' => 'name',
'multiple' => true,
'constraints' => array(
new CustomConstraint(array(
'message' => 'Application can not be refused.'
)),
)
));
Property in entity:
/**
* #ORM\ManyToMany(targetEntity="RejectReason")
* #ORM\JoinTable(name="relationship_application_reject_reasons",
* joinColumns={#ORM\JoinColumn(name="application_id", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="reject_reason_id", referencedColumnName="id")}
* )
*/
private $rejectReasons;
UPDATE
I tried putting constraint on other string property, I still get NULL.
Look at ExecutionContextInterface it says that:
getObject() Returns the currently validated object.
If the validator is currently validating a class constraint, the
object of that class is returned. If it is a validating a property or
getter constraint, the object that the property/getter belongs to is
returned.
In other cases, null is returned.
So as you can see, you have to assign to a class or a property or getter. Otherwise you will get null.
For those making form validation using dependencies itself this can help.
I assume that the Symfony version is 3.4 or 4.1 and you have symfony/form on your project.
Build your CustomConstraintValidator
The best way to deal with Symfony Form Validators with some kind of dependency are using CustomValidators
Above is a example that I use to work with them.
Supposed that we have an Entity like
// src/Entity/myEntity.php
namespace App\Entity;
...
class myEntity
{
private $id;
private $name; // string, required
private $canDrive; // bool, not required (default=false)
private $driveLicense; // string, not required (default = null)
public function __construct()
{
$this->canDrive = false;
}
// getters and setters
}
We don't need to populate $driveLicense (cause the attribute its not mandatory), but if $canDrivechange from false to true, now $driveLicense must have a value.
$driveLicense is $canDrive dependent.
To build a form for that and validate $driveLicense correctly on the FormType (the best practice) we need to build a CustomConstraintValidator.
Building CanDriveValidator
// src/Validator/Constraints/CanDrive.php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
class CanDrive extends Constraint
{
public $message = 'invalid_candrive_args'; // I like translators :D
}
Translator file - optional
//src/translators/validators.en.yaml //
invalid_candrive_args: When "{{ candrivelabel }} " field is checked you must fill "{{ drivelicenselabel }}"
The validator
// src/Validator/Constraints/CanDriveValidator.php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class CanDriveValidator extends ConstraintValidator
{
/**
* Checks if the passed value is valid.
*
* #param mixed $value The value that should be validated
* #param Constraint $constraint The constraint for the validation
*/
public function validate($value, Constraint $constraint)
{
$canDriveField = $this->context->getObject(); // the Field using this validator
$form = $canDriveField->getParent(); // the formType where the Field reside
$myEntity = $form->getData(); // The Entity mapped by formType
if ($myEntity->getCanDrive() == true && $myEntity->getDriveLicense() == null) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ candrivelabel }}', 'Can Drive')
->setParameter('{{ drivelicenselabel }}', 'Drive License')
->addViolation();
}
}
}
The form myEntityType
//src/Form/myEntityType.php
namespace App\Form;
use App\Entity\myEntity;
use App\Validator\Constraints\CanDrive;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class myEntityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name')
->add('canDrive', CheckBoxType::class, [
'required' => false,
'constraints' => array(new canDrive()),
]
)
->add('driveLicense', TextType::class, ['required' => false])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => myEntity::class]);
}
}
Now, when use call isValid() method of myEntityType form and the canDrive field was checked and driveLicense is blank, a Violation will be fired on canDrive field. If canDrive is set to false (not checked, not submitted), nothing happens and form will be valid even when driveLicense is blank.
If you are develep a Class Constraint Validator remember to add the getTargets method as example:
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
As described here in the doc
The answer is simple. Write :
this->context->getRoot()->getData()
and u have the object.

Symfony2 Custom Validation with DB connection but no Entity

I'm simply trying to validate a form (with no entity attached) with one field "username". I want to check if the username exists or not. If it doesn't a proper message has to be displayed at form errors level (or form field errors level, I don't care that much).
Here is the relevant part of my controller:
$formGlobalSearch=$this->createFormBuilder()
->add('username', 'text', array('constraints' => new UsernameExists()))
->add('role', 'choice', array('choices' => $rolesListForForm,'required' => false, 'placeholder' => 'Choose a role', 'label' => false, ))
->add('submitUsername', 'submit', array('label' => 'Search username globally'))
->getForm();
$formGlobalSearch->handleRequest($request);
if ($formGlobalSearch->isValid())
{
// do something in the DB and refresh current page
}
return $this->render(//all the stuff needed to render my page//);
}
Here is the relevant part of service.yml
validator.unique.UsernameExists:
class: AppBundle\Validator\Constraints\UsernameExists
tags:
- { name: validator.constraint_validator, alias: UsernameExists }
Here is the validator class:
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class UsernameExists extends Constraint
{
public $message = 'The username "%string%" does not exist.';
public function validatedBy()
{
return 'UsernameExists';
}
}
Here is the validator:
<?php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class UsernameExistsValidator extends ConstraintValidator
{
public function validate($value)
{
$globalUserToAdd = $this->getDoctrine()->getRepository('AppBundle:User')->findOneBy(
array('username' => $value, 'enabled' => true)
);
if ($globalUserToAdd == null) //I did not find the user
{
$this->context->buildViolation($constraint->message)
->setParameter('%string%', $value)
->addViolation();
}
}
}
The class and the validator are in the directory "AppBundle\Validator\Constraints"
I'm getting the following error:
Expected argument of type
"Symfony\Component\Validator\ConstraintValidatorInterface",
"AppBundle\Validator\Constraints\UsernameExists" given
I of course have the following on top of my controller:
use AppBundle\Validator\Constraints\UsernameExists;
If I add the following to service.yml
arguments: ["string"]
I get the error:
No default option is configured for constraint
AppBundle\Validator\Constraints\UsernameExists
try changing:
public function validatedBy()
{
return 'UsernameExists';
}
validator.unique.UsernameExists:
class: AppBundle\Validator\Constraints\UsernameExists
to:
public function validatedBy()
{
return 'UsernameExistsValidator';
}
validator.unique.UsernameExists:
class: AppBundle\Validator\Constraints\UsernameExistsValidator
Merging (partially) the suggestions and debugging a bit more here is the final code.
Validator:
<?php
namespace AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Doctrine\ORM\EntityManager;
class UsernameExistsValidator extends ConstraintValidator
{
public function __construct(EntityManager $em) //added
{
$this->em = $em;
}
public function validate($value, Constraint $constraint)
{
$em = $this->em;
$globalUserToAdd = $em->getRepository('AppBundle:User')->findOneBy(
array('username' => $value, 'enabled' => true)
);
if ($globalUserToAdd == null) //I did not find the user
{
$this->context->buildViolation($constraint->message)
->setParameter('%string%', $value)
->addViolation();
}
}
}
service.yml
validator.unique.UsernameExists:
class: AppBundle\Validator\Constraints\UsernameExistsValidator
arguments: [ #doctrine.orm.entity_manager, "string"]
tags:
- { name: validator.constraint_validator, alias: UsernameExists }

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