UniqueEntity constraint - symfony

I want to validate user entity for uniqueness. I do it this way:
$builder->add('email', 'email', array(
'required' => true,
'constraints' => array(
new NotBlank(), new Email(), new UniqueEntity(array('fields' => array('email')))
)
)
)
But I am getting following error:
Warning: get_class() expects parameter 1 to be object, string given in
vendor/symfony/symfony/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php
line 66
What am I doing wrong?

It's failing because UniqueEntity needs to be applied against the entity and not an individual field. Called a class constraint. Your best bet is to use validation.yml as described in : http://symfony.com/doc/current/reference/constraints/UniqueEntity.html
However, it should be possible to apply it using setDefaultOptions:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'constraints' => array(
new UniqueEntity(array('fields' => array('email'))),

I'm using the following code in my object class in order to handle the unicity of the site's name, maybe you could try it.
#UniqueEntity(
fields={"name"},
errorPath="name",
message="This name is already in use, please chose another one."
)

SF3+ inside your FormType class
public function configureOptions( OptionsResolver $resolver ): void {
parent::configureOptions( $resolver );
$resolver->setDefaults( array(
'constraints' => array( new UniqueEntity( array( 'fields' => array( 'email' ) ) ) )
) );
}
All the credit goes to Cerad.

Related

Expression Validator gives "Unable to get a property on a non-object"

can anybody figure out why this:
$builder
->add('networkLoIp', IntegerType::class, array(
'constraints' => array(
new A\NotBlank(),
new A\Expression(array(
'expression' => 'value <= this.getNetworkHiIp()'
))
)
))
->add('networkHiIp', IntegerType::class, array(
'constraints' => array(
new A\NotBlank()
)
))
->setMethod('post')
;
gives an error like this: "Unable to get a property on a non-object" ?
When I dump data after submit I can see values I've put inside the form.
EDIT
A very similar error I get after moving expression to options, i.e.
public function configureOptions(OptionsResolver $optionsResolver)
{
$optionsResolver->setDefaults(array(
'constraints' => array(
new A\Expression(array(
'expression' => "value['networkLoIp'] <= value['networkHiIp']"
))
)
));
}
"Unable to get an item on a non-array."
For your particular case you can use this:
$builder
->add('networkLoIp', IntegerType::class, array(
'constraints' => array(
new A\NotBlank(),
new A\LessThanOrEqual($this->getNetworkHiIp())
// or if the method is static and defined somewhere else
// new A\LessThanOrEqual(ThatObject::getNetworkHiIp())
)
))
Reference for LessThanOrEqual.
If the field stores an IP address I would use Ip validator as well.
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use AppBundle\Form\CreateLicence
class crateLicenciaonType extends AnstractType{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$entity= new CreateLicence();
$builder
->add('networkLoIp', IntegerType::class, array(
'constraints' => array(
new A\NotBlank(),
new A\LessThanOrEqual($entity->getNetworkHiIp())
// or if the method is static and defined somewhere else
// new A\LessThanOrEqual(ThatObject::getNetworkHiIp())
)
))
}
You can't apply expression contrainst on a non object but you can use an other one for the same purpose or create an object (DTO) link to your form.

Symfony2 FormBuilder with Entity class

I have a form that works well, there is just one issue with it and I'm hoping that I'll get an answer on how to do what I need to do.
<?php
namespace ADS\UserBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Security\Core\SecurityContext;
class UserType extends AbstractType {
private $type;
public function __construct($type) {
$this->type = $type;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('firstName', 'text', array('required' => true))
->add('lastName', 'text', array('required' => true));
$builder->add('email', 'email', array('required' => true));
$builder->add('parentCompany', 'entity', array(
'class' => 'ADSUserBundle:Company',
'expanded' => false,
'empty_value' => 'CHOOSE ONE',
'required' => false,
'property' => 'companyName'
))
->add('enabled', 'choice', array('choices' => array('1' => 'Enabled', '0' => 'Disabled')))
->add('roles', 'entity', array(
'class' => 'ADSUserBundle:Roles',
'required' => true,
'property' => 'displayName',
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array('data_class' => 'ADS\UserBundle\Entity\User'));
}
public function getName() { return 'ads_userbundle_user'; }
}
I have this form, the portion I am looking at is the 'roles' portion... Right now it created a multiple select box ( as I expect it to ), though the value is sequentially ie: 0,1,2,3,4...
What I really need is to figure out how to take this entity, and make the property to be the displayName ( as it is now ) and get the value to be the corresponding internalName This way it'll give me an array like:
array('ROLE_EXAMPLE' => 'EXAMPLE', 'ROLE_EXAMPLE1' => 'EXAMPLE1')
Any ideas how to accomplish this is greatly appreciated.
Kamil Adryjanek is correct, it is going to be much easier if you change it from an entity to a choice field. I've done some testing, both with FOSUserBundle and without the bundle - in both cases I hit some interesting road blocks.
First, I tried to run it through QueryBuilder in a repository, that didn't work out as it should have. The reason being, the fact that you wanted to be returning an array instead of a ORM object causes an error.
So next, I started looking at creating the choice field. All the guides, say to use the fieldname role instead of roles so I tried that, but I then had to duplicate the UserInterface from FOSUserBundle - I didn't want to do that -- so here I am stressed, and trying to figure it out.
Here is what I ended up doing, which works well.
private $normalRoles = array();
then in the __construct I add: $this->normalRoles = $roles;
Here is the builder:
$builder
->add('roles', 'choice', array(
'multiple' => true,
'choices' => $this->normalRoles
))
;
Originally, I left the multiple part out, figuring that it'd at least let me see an option box. I ended up getting an Array to String conversion error. So, adding the 'multiple' => true in, fixes that error.
Then, in my repository I created a function called normalizeRoles
public function normalizeRoles() {
$data = array();
$qb = $this->getEntityManager();
$query = $qb->createQuery(
"SELECT r.internalName, r.displayName FROM AcmeUserBundle:Roles r"
)->getArrayResult();
foreach ($query as $k => $v) {
$data[$v['internalName']] = $v['displayName'];
}
return $data;
}
From here, we just have to make some small edits in the DefaultController of the UserBundle in the newAction and editAction ( both are the same changes )
So, first off is to put into your Controller use Acme/UserBundle/Entity/Roles in order to avoid any errors and be able to get that repository.
Next, right before you create the form you run the normalizeRoles() function
$roles = $em->getRepository('AcmeUserBundle:Roles')->normalizeRoles()
Then, you pass it through the construct via: new UserType($roles)
full line for that would look like this:
$form = $this->createForm(new UserType($roles), $entity, array(
'action' => $this->generateUrl('acmd.user.edit', array(
'id' => $id)
)
));
or for new:
$form = $this->createForm(new UserType($roles), $entity, array(
'action' => $this->generateUrl('acmd.user.new')
)
));
At this point -- You'll have a working system that will allow you to dynamically add roles into a database table, and then associate those with a new or current user.
You can try do it via query_builder attribute:
$builder->add('roles', 'entity', array(
'class' => 'ADSUserBundle:Roles',
'required' => true,
'property' => 'displayName',
'query_builder' => function (RolesRepository $queryBuilder) {
return $queryBuilder->someMethod() // some method in this repository that return correct query to db.
},
));
In this case it would be better to use choice field Type (http://symfony.com/doc/current/reference/forms/types/choice.html) instead of entity and pass some role choices as option to form because entity field Type get entity id as key for choices:
public function buildForm(FormBuilderInterface $builder, array $options) {
...
$builder->add('roles', 'choice', array(
'choices' => $options['role_choices']
));
...
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'ADS\UserBundle\Entity\User',
'role_choices' => array()
));
}
Notice: it's recommended to pass variables to form through options parameter, not in constructor.
if I understand your question correctly, you need a data transformers. They help you to show data in form as you want.
Documentation: http://symfony.com/doc/current/cookbook/form/data_transformers.html

add class to subset of symfony checkboxes

I've made a form in Symfony that is not bound to an entity. One of the form elements is a set of checkboxes, fieldtypes. Which fieldtype checkboxes show should depend on the value of the searchby field. I want to add a class to each checkbox to use as a show/hide hook for the javascript I will add later. But so far as I can see, the choices form element only allows for an overall class to be applied.
Here's the relevant bit of my form:
$form = $this->createFormBuilder()
->add('searchby', 'choice', array(
'label' => 'browse.label.searchby',
'choices'=>array(
'name' => 'browse.searchby.name',
'location' => 'browse.searchby.location',
'classification' => 'browse.searchby.classification'
),
'required' => true,
'multiple' => false,
'expanded' => true,
))
->add('fieldtypes', 'choice', array(
'label' => 'browse.label.fieldtypes',
'choices' => array(
'extensionAttribute12' => 'person.label.position.fr',
'title' => 'person.label.position.eng',
'l' => 'person.label.city',
'st' => 'person.label.province',
'co' => 'person.label.country',
'givenname' => 'person.label.firstname',
'sn' => 'person.label.lastname',
'name' => 'person.label.fullname',
),
'required' => true,
'multiple' => true,
'expanded' => true
));
If I want to add the class 'searchbyname' to the radiobuttons created from $options['choices']['givenname'], $options['choices']['sn'], $options['choices']['name'], how would I go about it?
After seeing that choices could be declared like this:
'choices' => array(
'classification' => array(
'extensionAttribute12' => 'person.label.position.fr',
'title' => 'person.label.position.eng',
),
'location' => array(
'l' => 'person.label.city',
'st' => 'person.label.province',
'co' => 'person.label.country',
),
'name' => array(
'givenname' => 'person.label.firstname',
'sn' => 'person.label.lastname',
'name' => 'person.label.fullname',
)
),
where the key to each sub array would be used as an <optgroup> label in a <select>; and after attempting to modify the Twig template (which was every bit a painful as #Cerad said it would be), I tried extending the ChoiceType class by creating a form type extension.
My solution is inefficient since I'm modifying the child views after they are created and because I had to include all the code from ChoiceType::finishView. I can't see how a child view is created. There is a line in ChoiceType::buildForm that reads $remainingViews = $options['choice_list']->getRemainingViews();, but since $options['choices'] was input as an array, I don't know what class getRemainingViews() is being called from.
At any rate, here it is:
<?php
namespace Expertise\DefaultBundle\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ChoiceTypeExtension extends AbstractTypeExtension
{
/**
* #return string The name of the type being extended
*/
public function getExtendedType()
{
return 'choice';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setOptional(array('optgroup_as_class'));
$resolver->setDefaults(array('optgroup_as_class'=>false));
}
public function finishView(FormView $view, FormInterface $form, array $options)
{
if ($options['expanded']) {
// Radio buttons should have the same name as the parent
$childName = $view->vars['full_name'];
// Checkboxes should append "[]" to allow multiple selection
if ($options['multiple']) {
$childName .= '[]';
}
foreach ($view as $childView) {
$childView->vars['full_name'] = $childName;
if($options['optgroup_as_class']){
foreach($options['choices'] as $optclass => $choices){
if(!is_array($choices)) continue;
foreach($choices as $value => $label){
if($childView->vars['value'] == $value && $childView->vars['label'] == $label) {
$childView->vars['attr']['class'] = $optclass;
break 2;
}
}
}
}
}
}
}
}
Add it as a service, use the "optgroup" choices format and set optgroup_as_class to true.
I'd love to see a more efficient method.

Symfony2 - Set translation domain for a whole form

I want to translate a form created with symfony's formbuilder. As i don't want one big translation file it is splitted up into "domains".
Now i have to specify the translation_domain for each form-field, otherwise symfony will look in the wrong file. This option has to be added to every field and i'm wondering if there is a way to set this option to a whole form?
Sample code i'm not happy with:
$builder->add(
'author_name',
'text',
array('label' => 'Comment.author_name', 'translation_domain' => 'comment')
)->add(
'email',
'email',
array('label' => 'Comment.email', 'translation_domain' => 'comment')
)->add(
'content',
'textarea',
array('label' => 'Comment.content', 'translation_domain' => 'comment')
);
You've then to set it as a default option of your form, add this:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'translation_domain' => 'comment'
));
}
to your setDefaultOptions method, in your form.
Update: It is deprecated. Use configureOptions method instead (thanks #Sudhakar Krishnan)
The method name in Ahmed's answer is now deprecated (since Symfony 2.7), the 2.7+ way of doing it is:
/**
* Configures the options for this type.
*
* #param OptionsResolver $resolver The resolver for the options.
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('translation_domain', 'messages');
}
in the same way you set your data_class settings, etc.
To do this using just the form builder, there is an options argument on the form builder. From the controller, for example:
$form = $this->createFormBuilder($entity, ['translation_domain' => 'messages'])->add(..)->getForm();
If you're using the FormFactory service, this would be
$formFactory->createBuilder('form', $entity, ['translation_domain' => 'messages']);
Symfony 3
/**
* Configures the options for this type.
*
* #param OptionsResolver $resolver The resolver for the options.
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'translation_domain' => 'forms',
// Add more defaults if needed
));
}
Or in case you use the Factory's namedBuilder that would be:
$formBuilder = $this->get('form.factory')->createNamedBuilder('myForm', 'form', $data, array(
'translation_domain' => 'forms',
));

Building Symfony 2 Custom Validator that Uses Multiple Fields

I'm building a custom validator that needs to validate the value from TWO form fields in the db in order to get this constraint to pass.
My question is this: the ContractValidator's validate method only has one $value in it's signature so how do I get access to the values from more than just a single field to do the validation?
Here is a typical custom validator:
namespace Acme\WebsiteBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class MyCustomValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
// check $value and return an error
// but in my case, i want the value from more than one form field to do a validation
// why? i'm checking that two pieces of information (ssn + dob year) match
// the account the user is registering for
}
}
Here's an example of a form class with some validations set:
namespace ACME\WebsiteBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\MinLength;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Regex;
use ACME\WebsiteBundle\Validator\Constraints\UsernameAvailable;
class AccountRegistration extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('ssn', 'number', array(
'max_length' => 9,
'required' => true,
'error_bubbling' => true)
);
$builder->add('year_of_birth', 'choice', array(
'choices' => range(date("Y") - 100, date("Y")),
'required' => true,
'empty_value' => 'Select ...',
'label' => 'Year of Birth',
'error_bubbling' => true)
);
$builder->add('username', 'text', array(
'required' => true,
'error_bubbling' => true)
);
$builder->add('password', 'password', array(
'max_length' => 25,
'required' => true,
'error_bubbling' => true)
);
$builder->add('security_question', 'choice', array(
'empty_value' => 'Select ...',
'choices' => array(),
'label' => 'Security Question',
'required' => true,
'error_bubbling' => true)
);
$builder->add('security_question_answer', 'text', array(
'label' => 'Answer',
'required' => true,
'error_bubbling' => true)
);
}
public function getName()
{
return 'account_registration';
}
public function getDefaultOptions(array $options)
{
$collectionConstraint = new Collection(array(
'allowExtraFields' => true,
'fields' => array(
'ssn' => array(new MinLength(array('limit' => 9, 'message' => 'too short.')), new NotBlank()),
'year_of_birth' => array(new NotBlank()),
'username' => array(new NotBlank(), new UsernameAvailable()),
'password' => array(new NotBlank(), new Regex(array(
'message' => 'password must be min 8 chars, contain at least 1 digit',
'pattern' => "((?=.*\d)(?=.*[a-z]).{8,25})"))
),
'security_question' => array(new NotBlank()),
'security_question_answer' => array(new NotBlank()))
)
);
return array(
'csrf_protection' => true,
'csrf_field_name' => '_token',
'intention' => 'account_registration',
'validation_constraint' => $collectionConstraint
);
}
}
Any custom validator that extends ConstraintValidator has access to the $context property. $context is an instance of ExecutionContext that gives you access to submitted data:
Example:
<?php
namespace My\Bundle\MyBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class AppointorRoleValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
$values = $this->context->getRoot()->getData();
/* ... */
}
}
You need to use CLASS_CONSTRAINT as described in the cookbooks. You then get passed the whole class and can use any property of this class. In your example above, this would mean that instead of the $value beeing one string/integer, it would be the whole object you want to validate.
The only thing you need to change is getTargets() functions, which has to return self::CLASS_CONSTRAINT.
Also make sure that you define your validator on class level, not on property level. If you use annotations this means that the validator must be described above the class defnition, not above one specific attribute definition:
/**
* #MyValidator\SomeValidator
*/
class MyClass {
}

Resources