I'm new on Symfony2 world. I was trying to learn the basics of Validation in Symfony2 when I ran into a problem with it. According to the guide, to manage properly a sequence of validation groups you have to add this annotation's line on your Entity class:
/**
* #Assert\GroupSequence({"User", "Strict"})
*/
And put some annotation wherever you want to handle the proper rule. In my case as well as one of the guide is the password field that should be valid only if firstly it has compiled (and respects my rules such as minimum length) and then if is different from username value. The problem is it doesnt' work for me!
I mean, I have the same User class and I used the same form of their example:
$form = $this->createFormBuilder($user, array('validation_groups' => array('signup','strict')))
->add('name', 'text')
->add('email', 'text')
->add('password', 'password')
->add('signup', 'submit')
->getForm();
Here's my User class:
<?php
namespace XXX\SiteBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* User
*
* #ORM\Table(name="users")
* #ORM\Entity
* #Assert\GroupSequence({"User", "signup", "strict"})
*/
class User
{
//..
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
* #Assert\NotBlank(groups={"signup"})
* #Assert\Length(min=3,groups={"signup"})
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=255)
* #Assert\NotBlank(groups={"signup"})
* #Assert\Length(min=7,groups={"signup"})
*/
private $password;
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=255)
* #Assert\NotBlank(groups={"signup"})
* #Assert\Email(checkMX=true, groups={"signup"})
*/
private $email;
/**
* #Assert\True(groups={"strict"})
*/
public function isPasswordLegal()
{
return $this->name != $this->password;
}
//..some getter\setter methods
}
When I submit the form without putting values in the fields it shows me every error (and that's right) but also one that isPasswordLegal() launches, even BEFORE the others!
What am I missing? Thank you all!
The reason why the error is displayed before the others is, that you use it as a method validator and Symfony assigns the error message to the form instance and not the form field.
Edit:
All forms provide the error_mapping option which lets you define where the error messages should be shown.
In your case it would look like this:
$options = array(
'validation_groups' => array('signup','strict'),
'error_mapping' => array(
'isPasswordLegal' => 'password',
),
);
$form = $this->createFormBuilder($user, $options)
->add('name', 'text')
->add('email', 'text')
->add('password', 'password')
->add('signup', 'submit')
->getForm();
Related
I am working on a REST webservice (FOSRestBundle 2.0.0, Symfony 3.1.3) and testing the creation of entities. The creation itself works fine with a correct set of data but if I try to omit a required value the controller still says the form is valid.
The entity itself:
class Customer implements ExportableEntity
{
use Traits\FilterableTrait;
use Traits\UuidTrait;
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #Serializer\Exclude()
* #Serializer\ReadOnly()
*/
private $id;
/**
* #var int
*
* #ORM\Column(name="customer_index", type="integer", unique=true)
*/
private $customerIndex;
/**
* #var string
*
* #ORM\Column(name="customerName", type="string", length=255)
*/
private $customerName;
// [... accessors ...]
The controller:
/**
* #ApiDoc(
* resource=false,
* description="Create a new customer",
* section="Customers",
* statusCode={
* 200="Action successful",
* 403="Authorization required but incorrect / missing information or unsufficient rights",
* 500="Returned if action failed for unknown reasons"
* }
* )
*
* #param Customer $customer
* #return RestResponse
*/
public function postCustomerAction(Request $request) {
$manager = $this->container->get('corebundle.managers.customer');
// Internal usage only, no link with the WS issue
$manager->setChecksEnabled(false);
$customer = new Customer();
$form = $this->get('form.factory')->createNamed(null, CustomerType::class, $customer, ['csrf_protection' => false]);
$form->handleRequest($request);
//if ($form->isValid()) {
if ($form->isSubmitted() && $form->isValid()) {
print('VALID');
exit();
$manager->create($customer);
// Return 201 + Location
}
return \FOS\RestBundle\View\View::create($form, 400);
}
And the FormType:
class CustomerType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('customerName', Type\TextType::class, array('label' => 'Customer name'))
->add('customerIndex', Type\IntegerType::class, array('label' => 'Customer Index'))
->add('comment', Type\TextareaType::class, array('label' => 'Comments',
'required' => false, ))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array('data_class' => 'NetDev\CoreBundle\Entity\Customer'));
}
/**
* #return string
*/
public function getBlockPrefix()
{
return 'netdev_corebundle_customer';
}
}
If I try to create a new Customer and omit the "customerIndex" field, I belieev that I should get an invalid form error but I ain't getting it.
I tried to change the "handleRequest" with
$form->submit([])
and
$form->submit($request->request->get($form->getName()))
to no avail. If I add a "NotBlank()" constraint to the entity itself it works but I am under the impression that this would be a workaround, not a fix. Did I miss something ?
$form->isValid()
This line will verify that your submitted data respected all the constraints written in your entity files (with Assert annotation, for example #Assert\NotBlank()).
So, you did not miss something.
I am currently creating a form that lets the user choose a certain skills from a Dropdown and a Checkbox for hobbies that lets the user check as much he/she wants.
Here is my Table for that: CurriculumVitae
/* namespace ........... */
use Doctrine\ORM\Mapping as ORM;
/**
* CurriculumVitae
*
* #ORM\Table(name="foo_cv")
* #ORM\Entity
*/
class CurriculumVitae
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
* #ORM\ManyToOne(targetEntity="Foo\BarBundle\Entity\Skills")
* #ORM\JoinColumn(name="skills", referencedColumnName="id")
*/
private $skills;
/**
* #var integer
* #ORM\ManyToOne(targetEntity="Foo\BarBundle\Entity\Hobby", cascade={"persist"})
* #ORM\JoinColumn(name="hobbies", referencedColumnName="id")
*/
private $hobby;
/* Setters and Getters ....... */
}
Here are some codes for my Form Type: CurriculumVitaeType
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('skills', 'entity', array('class' =>'FooBarBundle:Skills','property' => 'skills'))
->add('hobby', 'entity', array( 'class' => 'FooBarBundle:Hobby','property' => 'hobbies', 'expanded'=>true,'multiple'=>true, 'label' => 'hobbies'))
->add('save','submit',array('label'=>'Submit'))
;
}
/* OptionsResolverInterface ..... */
/* getName() .... */
I call my form in my twig this way in: cv.twig.html
{{ form(curriculumForm) }}
And lastly in my controller: CurriculumController
$em = $this->getDoctrine()->getManager();
$cv = new CurriculumVitae();
$curriculumForm = $this->createForm(new CurriculumVitaeType(), $cv);
$curriculumForm->handleRequest($request);
if ($curriculumForm->isValid()) {
$em->persist($cv);
$em->flush();
return $this->redirect($this->generateUrl('foo_main_window'));
}
return array('curriculumForm'=> $curriculumForm->createView());
The Form Displays correctly but when I choose a skill from the dropdown and assign a certain hobby and click on submit, an error is thrown.
Found entity of type Doctrine\Common\Collections\ArrayCollection on association Foo\BarBundle\Entity\CurriculumVitae#hobby, but expecting Foo\BarBundle\Entity\Hobby
I dont know if i missed something but i think the error occurs in the process of persisting the data after the form is submitted.
That's because you have many-to-one relation, which means
Many CurriculumVitaes can have (the same) single Hobby
But on the other hand you've created in your form a field with option 'multiple'=>true, which means that you let the user to choose multiple hobbies. Therefore form returns ArrayCollection of Hobby entites instead of single instance.
That doesn't match. You need to either remove multiple option, or make many-to-many relation on $hobby property.
In Sonata I have created several lists and all work fine. (Please not that that was a while ago, so I may have done something there which fixed the issue I will describe here...).
Now I have created a new listing of Playlist entities:
As you can see in the picture, both the "Id" column and the "Aangemaakt op" columns are sortable, however the "Playlist" column is not.
Both the "Aangemaakt op" and the "Playlist" fields are date-fields, but since the "Aangemaakt op" field is sortable I would say that has nothing to do with it.
I have been searching the Sonata documentation, Google and StackOverflow, but haven't found any clue concerning this issue. I did find thread about sorting a list based on an Entity field, but my field isn't an entity.
Relevant code:
/**
* #param ListMapper $listMapper
*/
protected function configureListFields(ListMapper $listMapper) {
$listMapper
->add('id')
->add('playlist_date', 'date', array('label' => 'Playlist'))
->add('created', 'datetime', array('label' => 'Aangemaakt op'))
->add(
'_action', 'actions', array(
'actions' => array(
'delete' => array(),
)
)
);
}
Some StackOverflow threads and an answer below mention adding 'sortable' => true to the field that must be sortable.
Doing that indeed makes the column clickable to sort, but clicking it results in the following exception:
Catchable Fatal Error: Argument 1 passed to
Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQuery::entityJoin()
must be of the type array, null given, called in
/path/of/my/project/sonata-project/doctrine-orm-admin-bundle/Datagrid/ProxyQuery.php
on line 142 and defined.
According to other StackOverflow threads that is because a join must be created. However, the field is simply a field of the same Mysql record as the other fields. I did find a StackOverflow thread mentioning this as well and in which they joined the same record in order to make this work, but I didn't get that to work. Besides, I thank that shouldn't be the way to order the contents of a column.
Does anyone have a clue?
Update in reaction to Hibatallah Aouadni's answer
As Hibatallah suggests, I added the following to my PlaylistsAdmin:
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('id')
->add('playlist_date')
->add('created');
}
This resulted in the following error message:
Notice: Undefined index: playlist_date
So I inspected my Entity and I found that it has a UniqueConstraint:
uniqueConstraints={#ORM\UniqueConstraint(name="playlist_date", columns={"playlist_date"})}
It does not have an actual "index" defined, but ofcourse it is. However as a test I added the following:
, indexes={#ORM\Index(name="playlist_date", columns={"playlist_date"})}
This didn't give any different result.
So still no luck at all :(
** Entity and Entity admin **
Entity Admin:
<?php
namespace Company\AdminBundle\Admin;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Sonata\AdminBundle\Route\RouteCollection;
class PlaylistsAdmin extends Admin
{
protected $baseRoutePattern = 'playlists';
protected $baseRouteName = 'playlists';
protected function configureRoutes(RouteCollection $collection) {
$collection->clearExcept(array('list', 'delete', 'show'));
}
/**
* #param ListMapper $listMapper
*/
protected function configureListFields(ListMapper $listMapper) {
$listMapper
->add('id')
->add('playlist_date', 'date', array('label' => 'Playlist'))
->add('created', 'datetime', array('label' => 'Aangemaakt op'))
->add(
'_action', 'actions', array(
'actions' => array(
'show' => array(),
/*'edit' => array(),*/
'delete' => array(),
)
)
);
}
public function getBatchActions() {
return array();
}
}
Entity:
<?php
namespace Company\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Playlist
*
* #ORM\Table(name="playlist", uniqueConstraints={#ORM\UniqueConstraint(name="playlist_date", columns={"playlist_date"})})
* #ORM\Entity()
*/
class Playlist
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", options={"unsigned"=true})
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #var \DateTime
*
* #ORM\Column(name="playlist_date", type="date", nullable=true)
*/
protected $playlistDate;
/**
* #var \DateTime
*
* #ORM\Column(name="created", type="datetime", nullable=true)
*/
protected $created;
/**
* #var \Doctrine\Common\Collections\Collection
*/
protected $video;
/**
* Constructor
*/
public function __construct() {
$this->video = new \Doctrine\Common\Collections\ArrayCollection();
$this->setCreated(new \DateTime());
}
/**
* Get id
*
* #return integer
*/
public function getId() {
return $this->id;
}
/**
* Set playlistDate
*
* #param \DateTime $playlistDate
* #return Playlist
*/
public function setPlaylistDate($playlistDate) {
$this->playlistDate = $playlistDate;
return $this;
}
/**
* Get playlistDate
*
* #return \DateTime
*/
public function getPlaylistDate() {
return $this->playlistDate;
}
/**
* Set created
*
* #param \DateTime $created
* #return Playlist
*/
public function setCreated($created) {
$this->created = $created;
return $this;
}
/**
* Get created
*
* #return \DateTime
*/
public function getCreated() {
return $this->created;
}
/**
* Add video
*
* #param \Company\AppBundle\Entity\Video $video
* #return Playlist
*/
public function addVideo(\Company\AppBundle\Entity\Video $video) {
$this->video[] = $video;
return $this;
}
/**
* Remove video
*
* #param \Company\AppBundle\Entity\Video $video
*/
public function removeVideo(\Company\AppBundle\Entity\Video $video) {
$this->video->removeElement($video);
}
/**
* Get video
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getVideo() {
return $this->video;
}
}
Finally I found it, it's so absurd, in the configureListFields method, you have to call the attribute with its name not the DataBase name, so:
change
->add('playlist_date', 'date', array('label' => 'Playlist'))
to
->add('playlistDate', 'date', array('label' => 'Playlist'))
:D I can't beleive we spend all this time for some absurd mistake ;)
all you need to do is to add the argument sortable to array and true as value:
->add('playlist_date', 'date', array(
'label' => 'Playlist',
'sortable' => true
))
try to add the playlist field in configureDatagridFilters in your entity admin:
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('id')
->add('playlist_date')
->add('created');
}
and it will work ;)
Symfony version : 2.8.5
Context: I have an entity Restaurant which has a OneToOne relationship with an entity Coordinates which have several relations with other entities related to Coordinates informations. I my backend I create a form related to Restaurant entity with a custom nested form related to Coordinates.
Nota : I use EasyAdminBundle to generate my backend.
Entities relations scheme :
Restaurant
1 ________ 1 `Coordinates`
* ________ 1 `CoordinatesCountry`
1 ________ 1 `CoordinatesFR`
* ________ 1 `CoordinatesFRLane`
* ________ 1 `CoordinatesFRCity`
Backend view :
At this point I try the following scenario :
I create a new Restaurant, so I fill the fields related form for the first time. I let the Coordinates nested form blank (empty fields). So after form submission, validation messages are displayed (see image below).
I edit the previous form and this time I fill the fields of the Coordinates nested form. After form submission, a new Coordinates entity is hydrated and a relationship is created between Restaurant and Coordinates.
Once again I edit the previous form and this time I clear all the fields of the Coordinates nested form. The validation is not triggered and I get the following error :
Expected argument of type "FBN\GuideBundle\Entity\CoordinatesFRCity",
"NULL" given
I precise that in CoordinatesFRType (see code below), to trigger the validations message the first time I had to use the option empty_data with a closure (like described in the official doc) to instatiate a new CoordinatesFR instance in case of empty datas (all fields blank). But here, in this article (written by the creator of the Symfony form component), it is explained (see empty_data and datta mappers paragraphs) that the empty_data is only called at object creation. So I think this the reason why my validation does not work anymore in case of edition.
Question : why the validation is not effective anymore when editing my form and clearing all embedded form ?
The code (only what is necessary) :
Restaurant entity
use Symfony\Component\Validator\Constraints as Assert;
class Restaurant
{
/**
* #ORM\OneToOne(targetEntity="FBN\GuideBundle\Entity\Coordinates", inversedBy="restaurant", cascade={"persist"})
* #ORM\JoinColumn(nullable=true, onDelete="SET NULL")
* #Assert\Valid()
*/
private $coordinates;
}
Coordinates entity
use Symfony\Component\Validator\Constraints as Assert;
class Coordinates
{
/**
* #ORM\ManyToOne(targetEntity="FBN\GuideBundle\Entity\CoordinatesCountry")
* #ORM\JoinColumn(nullable=false)
*/
private $coordinatesCountry;
/**
* #ORM\OneToOne(targetEntity="FBN\GuideBundle\Entity\CoordinatesFR", inversedBy="coordinates", cascade={"persist"})
* #ORM\JoinColumn(nullable=true, onDelete="SET NULL")
* #Assert\Valid()
*/
private $coordinatesFR;
/**
* #ORM\OneToOne(targetEntity="FBN\GuideBundle\Entity\Restaurant", mappedBy="coordinates")
* #ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
private $restaurant;
}
CoordinatesFR entity
use Symfony\Component\Validator\Constraints as Assert;
class CoordinatesFR extends CoordinatesISO
{
/**
* #ORM\ManyToOne(targetEntity="FBN\GuideBundle\Entity\CoordinatesFRLane")
* #ORM\JoinColumn(nullable=true)
* #Assert\NotBlank()
*/
private $coordinatesFRLane;
/**
* #ORM\ManyToOne(targetEntity="FBN\GuideBundle\Entity\CoordinatesFRCity")
* #ORM\JoinColumn(nullable=false)
* #Assert\NotBlank()
*/
private $coordinatesFRCity;
/**
* #ORM\OneToOne(targetEntity="FBN\GuideBundle\Entity\Coordinates", mappedBy="coordinatesFR")
* #ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
private $coordinates;
}
Easy Admin config (equivalent to RestaurantType)
easy_admin:
entities:
Restaurant:
class : FBN\GuideBundle\Entity\Restaurant
form:
fields:
- { property: 'coordinates', type: 'FBN\GuideBundle\Form\CoordinatesType' }
CoordinatesType
class CoordinatesType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('CoordinatesCountry', EntityType::class, array(
'class' => 'FBNGuideBundle:CoordinatesCountry',
'property' => 'country',
))
->add('coordinatesFR', CoordinatesFRType::class)
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FBN\GuideBundle\Entity\Coordinates',
));
}
}
CoordinatesFRType
class CoordinatesFRType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('laneNum', TextType::class)
->add('coordinatesFRLane', EntityType::class, array(
'class' => 'FBNGuideBundle:CoordinatesFRLane',
'property' => 'lane',
'placeholder' => 'label.form.empty_value',
))
->add('laneName', TextType::class)
->add('miscellaneous', TextType::class)
->add('locality', TextType::class)
->add('metro', TextType::class)
->add('coordinatesFRCity', EntityType::class, array(
'class' => 'FBNGuideBundle:CoordinatesFRCity',
'property' => 'display',
'query_builder' => function (CoordinatesFRCityRepository $repo) {
return $repo->getAscendingSortedCitiesQueryBuilder();
},
'placeholder' => 'label.form.empty_value',
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FBN\GuideBundle\Entity\CoordinatesFR',
// Ensures that validation error messages will be correctly displayed next to each field
// of the corresponding nested form (i.e if submission and CoordinatesFR nested form with all fields empty)
'empty_data' => function (FormInterface $form) {
return new CoordFR();
},
));
}
}
I have a formular that I want to validate. The user should not be able to leave fields unfilled and that why I use #Assert\NotBlank but it does not seem to be working this is a part of my entity:
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
...
/**
* #var string
*
* #ORM\Column(name="device", type="string", length=255, nullable=false)
* #Assert\NotBlank(message="This value cannot be empty!")
*/
private $device;
...
And in the controler I'm using formbuilder from symfony like this:
...
$form = $this->createFormBuilder()
->add('device', 'text', array(
'label' => 'Device:',
'attr' => array('placeholder' =>'Dell 2407WPB - Monitor'),
'required' => true,
))
...
Do you have any suggestions about what may I be doing wrong? I've been stuck in this problem too long.
Thanks in advance :)
When you create the form with createFormBuilder, you should pass an instance of the entity.
$form = $this->createFormBuilder(new MyEntity())
so that the form know the class that holds the data (and his constraints).