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 }
Related
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 have an entity Acme\Bundle\Entity\Author which has a one to many relationship to Acme\Bundle\Entity\Article and I have two JMS serializer event subscribers for both entities.
Subscriber for Acme\Bundle\Entity\Author;
acm.author_serialization_listener:
class: Acm\Bundle\Listener\AuthorSerializationListener
tags:
- { name: jms_serializer.event_subscriber }
class AuthorSerializationListener implements EventSubscriberInterface
{
/**
* #inheritdoc
*/
public static function getSubscribedEvents()
{
return array(
array('event' => 'serializer.pre_serialize', 'class' => 'Acme\Bundle\Entity\Author' 'method' => 'onPreSerialize'),
);
}
public function onPreSerialize(PreSerializeEvent $event)
{
if($event->getObject() instanceof Author) {
return;
}
}
}
Subscriber for Acme\Bundle\Entity\Article;
acm.article_serialization_listener:
class: Acm\Bundle\Listener\ArticleSerializationListener
tags:
- { name: jms_serializer.event_subscriber }
class ArticleSerializationListener implements EventSubscriberInterface
{
/**
* #inheritdoc
*/
public static function getSubscribedEvents()
{
return array(
array('event' => 'serializer.pre_serialize', 'class' => 'Acme\Bundle\Entity\Article' 'method' => 'onPreSerialize'),
);
}
public function onPreSerialize(PreSerializeEvent $event)
{
if($event->getObject() instanceof Article) {
return;
}
}
}
The problem occurs in the onPreSerialize function when serializing Author along with related Article s in the ArticleSerializationListener subscriber class where it injects Proxies\__CG__\Acme\Bundle\Entity\Article instead of Acme\Bundle\Entity\Article entities which lead to exclude the related Article entities. Is there something I am doing wrong or is there a workaround for this?
Instead of get_class you should simply use the operator instanceof because the proxies inherit from the entities. You compare two strings.
if($event->getObject() instanceof \Acme\Bundle\Entity\Article) {
return;
}
Sidenode: Also the operator !=== does not exist.
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.
I created a simple entity-class that holds some properties and a public getter-Method that executes some stuff and returns true or false.
class Item {
public prop1;
public prop2;
public function isGetterConstraint() {
return true // or false based on some calculations
}
}
Then I defined the constraints for that class in the validation.yml:
Foo\MyBundle\Entity\Item:
properties:
prop1:
- NotBlank: ~
prop2:
- NotBlank: ~
getters:
getterConstraint:
- "True": { message: "zu" }
That's what you see in many examples, but I have not found out how to access this getter-constraint in a form either within the controller or the twig template. In my case I do need it within the template.
If I define my FormType like that
class ClientType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('prop1', 'text')
->add('prop2', 'text');
}
public function getName()
{
return 'item';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Foo\MyBundle\Entity\Item',
));
}
}
and my Controller like that
class ItemController extends Controller {
public function createAction(Request $request) {
$item = new Item();
$form = $this->buildForm($item);
$form->handleRequest($request);
if ($form->isValid()) {
// do some nice things like saving the data
}
return $this->render('FooMyBundle:Item:form.html.twig', array('form' => $form->createView());
}
}
the form does not know about the getter-constraint and I can't access it like I'm used to with property constraints:
// form.html.thwig
{% if form.prop1.vars.errors %}{% endif %} // works
{% if form.getterConstraint.vars.errors %}{% endif %} // doesnot work
Is there a way to add the getter constraint to the FormType or what do I have to do to make the form consider this constraint when validating the data and expose the result to the controller or template?
You can actually use the getter constraint direct in your entity
// src/Acme/BlogBundle/Entity/Author.php
// ...
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\True;
class Author
{
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addGetterConstraint('passwordLegal', new True(array(
'message' => 'The password cannot match your first name',
)));
}
public function isPasswordLegal()
{
return $this->firstName != $this->password;
}
}
Demo code taken from Symfony Validation
Or you can define the callbacks in your entity with annotations
// src/Acme/BlogBundle/Entity/Author.php
// ...
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ExecutionContextInterface;
/**
* #Assert\Callback(methods={"isPasswordLegal"})
*/
class Author
{
public function isPasswordLegal(ExecutionContextInterface $context)
{
$context->addViolationAt('password', 'wrong password');
}
}
And in controller you can do so
// src/KnpU/QADayBundle/Controller/EventController.php
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ExecutionContextInterface;
use KnpU\QADayBundle\Entity\Event;
// ...
public function newAction(Request $request)
{
$form = $this->createFormBuilder(null, array(
'data_class' => 'KnpU\QADayBundle\Entity\Event',
'constraints' => array(
new Assert\Callback(array(array($this, 'validateEventDates')))
)
))
->add('name', 'text')
->add('startDate', 'datetime')
->add('endDate', 'datetime')
->getForm()
;
// ...
}
public function validateEventDates(Event $event, ExecutionContextInterface $context)
{
$context->addViolationAt('startDate', 'There is already an event during this time!');
}
Custom Validation, Callback and Constraints
Problem
I used this solution https://stackoverflow.com/a/15167450/2910183, but I have a following error when I'm trying open admin dashboard (http://localhost/app_dev.php/admin/dashboard). This happens also after cleaning cache.
ClassNotFoundException: Attempted to load class "AcmeBlockService" from namespace "Acme\ProductBundle\Block" in ~/htdocs/symfony2training/app/cache/dev/appDevDebugProjectContainer.php line 2216. Do you need to "use" it from another namespace?
Does anybody know where is problem?
Code
Here is part of my app/config/config.yml
sonata_block:
default_contexts: [cms]
blocks:
sonata.admin.block.admin_list:
contexts: [admin]
sonata.user.block.menu: # used to display the menu in profile pages
sonata.user.block.account: # used to display menu option (login option)
acme.block.products:
sonata_admin:
dashboard:
blocks:
# display a dashboard block
- { position: left, type: acme.block.products }
- { position: left, type: sonata.admin.block.admin_list }
Part of my src/Acme/ProductBundle/Resources/config/services.yml
services:
acme.block.products:
id: acme.block.products
class: Acme\ProductBundle\Block\ProductsBlockService
arguments:
- { name: service, id: templating }
tags:
- { name: sonata.block }
My src/Acme/ProductBundle/Document/Products.php file in :
<?php
namespace Acme\ProductBundle\Block;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
class ProductsBlockService extends BaseBlockService {
public function getName() {
return 'Products';
}
public function getDefaultSettings() {
return array();
}
public function validateBlock(ErrorElement $errorElement, BlockInterface $block) {
}
public function buildEditForm(FormMapper $formMapper, BlockInterface $block) {
}
public function execute(BlockInterface $block, Response $response = null) {
$settings = array_merge($this->getDefaultSettings(), $block->getSettings());
return $this->renderResponse('AcmeProductBundle:Block:admin_products.html.twig', array(
'block' => $block,
'settings' => $settings
), $response);
}
}
Solution
I looked into another block (vendor/sonata-project/user-bundle/Sonata/UserBundle/Block/AccountBlockService.php) and I realized, that its file name is like class name. So I changed src/Acme/ProductBundle/Document/Products.php to src/Acme/ProductBundle/Document/ProductsBlockService.php
It works, but another error appeared:
FatalErrorException: Compile Error: Declaration of Acme\ProductBundle\Block\ProductsBlockService::execute() must be compatible with Sonata\BlockBundle\Block\BlockServiceInterface::execute(Sonata\BlockBundle\Block\BlockContextInterface $blockContext, Symfony\Component\HttpFoundation\Response $response = NULL) in ~/htdocs/symfony2training/src/Acme/ProductBundle/Block/ProductsBlockService.php line 0
The solution is written in the error message: Acme\ProductBundle\Block\ProductsBlockService::execute() must be compatible with Sonata\BlockBundle\Block\BlockServiceInterface::execute(). So I looked into my helper (AccountBlockService.php from 1.), compare execute() methods and write my own solution.
So in the end my src/Acme/ProductBundle/Document/ProductsBlockService.php file looks as below.
<?php
namespace Acme\ProductBundle\Block;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Validator\ErrorElement;
use Sonata\BlockBundle\Block\BaseBlockService;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\UserBundle\Menu\ProfileMenuBuilder;
use Sonata\UserBundle\Model\UserInterface;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
class ProductsBlockService extends BaseBlockService {
public function getName() {
return 'Products';
}
public function validateBlock(ErrorElement $errorElement, BlockInterface $block) {
}
public function buildEditForm(FormMapper $formMapper, BlockInterface $block) {
}
public function setDefaultSettings(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'template' => 'AcmeProductBundle:Block:admin_products.html.twig',
'ttl' => 0
));
}
public function execute(BlockContextInterface $blockContext, Response $response = null) {
return $this->renderPrivateResponse($blockContext->getTemplate(), array(
'block' => $blockContext->getBlock(),
'context' => $blockContext,
), $response);
}
}