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"
Related
Consider the following code:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Car extends Model
{
public static function getTheFirstCar(string $color): ?self
{
/** #var ?self */ // <-- Doesn't apply! Is there any alternative?
return (new self())->newQuery()->firstWhere('color', '=', $color);
}
}
The code is working correctly; nevertheless PhpStorm complains:
Return value is expected to be 'Car|null',
'\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model' returned
Assigning the result of the expression into an annotated variable resolves the warning, but yet introduces a "redundant" variable!
/** #var ?self $redundant */
$redundant = (new self())->newQuery()->firstWhere('color', '=', $color);
return $redundant;
So, is there a way in PhpStorm to enforce an inline type-annotation for the value of the return statement expression explicitly as Car|null, without introducing a redundant variable or specifying all of the expected return types?
You can suppress this warning by adding the #noinspection PhpIncompatibleReturnTypeInspection annotation before your statement.
I personally would not do this, but it's the only answer to you your question about how to "enforce" the return type and suppress warning afaik.
/** #noinspection PhpIncompatibleReturnTypeInspection */
return (new self())->newQuery()->where('color', '=', $color)->first();
If you decide to respect the warning, then this is probably the reason and solution for it:
newQuery() will create a new query on the models table (most likely: cars) without setting the appropriate model (Car).
Internally you're now running a bare query on cars. Therefore you will receive the appropriate record but is not gonna be an instance of Car, but an instance of Model instead. Therefore PhpStorm is expecting multiple additional return types here and printing this warning on your statement since it differs from the methods return type ?self.
The quick solution is to change newQuery() into newModelQuery(). This will create a new query and set the Model Car on the created query and return the appropriate instance or null
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Car extends Model
{
public static function getTheFirstCar(string $color): ?self
{
return (new self())->newModelQuery()->firstWhere('color', '=', $color);
// I'd use this statement though:
// return self::where('color', '=', $color)->first();
}
}
You need to add doc block to your class:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* Class Car
* #package App\Models
*
* #method self|Builder newQuery()
* #method ?self firstWhere($column, $operator = null, $value = null, $boolean = 'and')
*/
class Car extends Model
{
public static function getTheFirstCar(string $color): ?self
{
return (new self())->newQuery()->firstWhere('color', '=', $color);
}
}
As of PhpStorm 2022.2 anonymous #var annotations are supported, so your code already should work as is:
/** #var self */
return (new self())->newQuery()->firstWhere('color', '=', $color);
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;
}
}
}
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.
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.
Im trying to make a custom validator to check if an email is already submited or not. for this I need to execute query in my custom validator, How can I do that?
use Phalcon\Validation\Validator,
Phalcon\Validation\ValidatorInterface,
Phalcon\Validation\Message;
Class Unique extends Validator implements ValidatorInterface {
public function validate($validator, $attribute) {
// how to execute "SELECT * FROM myTable" here...
}
}
If myTable is mapped to a Model you can just:
use Phalcon\Validation\Validator;
use Phalcon\Validation\ValidatorInterface;
use Phalcon\Validation\Message;
use MyTable;
class Unique extends Validator implements ValidatorInterface
{
public function validate($validator, $attribute)
{
$result = MyTable::findFirst("id = 1 AND status = 'sent'");
...
}
}