Symfony 3 : how to use Collection and validate it - symfony

My problems obviously come from the fact that I do not understand well how to use CollectionType and validate it.
Here are classes that I have :
class Reservation {
private startDate;
private startTime;
private endDate;
private endtime;
.....
}
class Mission {
private $reservations
public function __construct() {
$this->reservations = new ArrayCollection();
}
....
}
At a certain time I create my Mission object, and I fill the reservations firld with a collection of some Reservation objects.
My form type is like this concerning the collection :
$builder->add('reservations', CollectionType::class, array(
'entry_type' => ReservationType::class,
'entry_options' => array('label' => false),
'label' => false,
'required'=> false
));
if the ReservationType class I have this :
$builder->add('startDate', DateType::class, array(
'label' => 'Date Aller',
'widget' => 'single_text',
'format' => 'dd/MM/yyyy',
'attr' => array(
'no_results_text' => 'JJ/MM/AAAA'
)
));
$builder->add('startTime', TextType::class, array(
'label' => 'Heure Aller',
'attr' => array(
'placeholder' => 'Heure:Minutes'
)
));
$builder->add('endDate', DateType::class, array(
'label' => 'Date Aller',
'widget' => 'single_text',
'format' => 'dd/MM/yyyy',
'attr' => array(
'no_results_text' => 'JJ/MM/AAAA'
)
));
$builder->add('endTime', TextType::class, array(
'label' => 'Heure Aller',
'attr' => array(
'placeholder' => 'Heure:Minutes'
)
));
In the twig :
{% for reservation in form_mission.reservations %}
I display the reservation Object startDate, startTime, endDate, endTime
{% endfor %}
Everything is well displayed.
Let's try some simple validation with annotations (NotBlank one). Inside Mission class, above reservations private field :
/**
* #Assert\Collection(
* fields = {
* "startDate" = #Assert\NotBlank,
* "startTime" = #Assert\NotBlank,
* "endDate" = #Assert\NotBlank,
* "endTime" = #Assert\NotBlank
* },
* allowMissingFields = true
* )
*/
private $reservations
Inside the reservations collection of my mission object, I have six Reservation Object.
I make sure that one the startDate of one of the Reservation Object is null.
Validating the form, no error appears but inside the development bar, I have 6 errors. One for each Reservation and it is 6 times the same error :
This field was not expected. 0 Caused by: ConstraintViolation {#1358 ▶}
This field was not expected. 1 Caused by: ConstraintViolation {#1370 ▶}
This field was not expected. 2 Caused by: ConstraintViolation {#5597 ▶}
This field was not expected. 3 Caused by: ConstraintViolation {#1371 ▶}
This field was not expected. 4 Caused by: ConstraintViolation {#1378 ▶}
This field was not expected. 5 Caused by: ConstraintViolation {#1382 ▶}
Inside of the ConstraintViolation I have this :
root: Form {#3185 …}
path: "data.reservations[0]"
value: Reservation {#3054 ▶}
So the errors occuring have nothing to do with the one expected, and I dont understand the errors I have.
Maybe fields property expects the keys of the collection (0, 1, 2, 3, 4, 5). So I dont understand how to validate my startDate field.
I replaced my annotation like this :
/**
* #Assert\Collection(
* fields = {
* "3" = #AssertPersonnelNotBlank,
* "4" = #AssertPersonnelNotBlank,
* "5" = #AssertPersonnelNotBlank,
* },
* allowMissingFields = true,
* allowExtraFields = true
* )
*/
The value inside my custom validator is a Reservation Object:
Conclusion : in the custom validator, you check all of the fields that should not be blank and return a single message
I dont know how to display the error in the twig
Obviously there is one display for any error in the collection. No way to show the error on the object of the collection ?

Your collection is array of classes, so you need to add validation rules to Reservation class, for example
class Reservation {
/**
* #Assert\NotBlank
*/
private startDate;
/**
* #Assert\NotBlank
*/
private startTime;
/**
* #Assert\NotBlank
*/
private endDate;
/**
* #Assert\NotBlank
*/
private endtime;
.....
}
And then in your parent entity add #Assert\Valid to collection field
/**
* #Assert\Valid
*/
private $reservations

Related

Symfony form validation does not work on edit

I have a form with 3 fields as below : storeClient , type and line that belong to Fashion entity.
I basically have the same problem mentioned here :
Symfony Form Validation not working in Edit
But I was surprised when I edited the form and chose the placeholder as option for Client and I filled the line and type fields , I got that for the client field, it DOES display my validation message "Please choose an option" .which is good .
However for the remaining two fields, if line or type are edited in such a way to choose the placeholder option, the application crashed and gives the argumet exception error.
I did a dump; die; inside the controller (you can see it commented below) . And I got the $fashion object like this when I chose the placeholder for all the fields aka for the client,type and line fields :
Fashion object :
storeClient: null
line: Proxies ...\RefLine
___isinitilized___ :true
and all the info of the line that was set initiallly in this object when I first enterede its edit page.
type: Proxies ...\RefType
___isinitilized___ :true
and all the info of the type that was set initiallly in this object when I first enterede its edit page.
so my question is why the validations work on edit for the client field and does not work for line and type ? I do not think that it is related to the fact that it is a choicettype whereas the other two are entitytype. Moreover, I didn't put a "?" in the setter of client. So i don't see why it works for this field and WHY it gave a Null value when I printed the object and it didn't print the initial client value that was already set in the object when I first landed on the edit page although the two other fields hold the values that were already stored in the object initially.
FashionType.php
->add('storeClient', ChoiceType::class,
[
'label' => 'Store Client',
'choices' => $choicesClient,
'choice_value' => function ($value) {
if ($value instanceof Client) {
return $value->getId();
} else {
return $value;
}
},
'placeholder' => 'Choose ..',
'choice_label' => 'diplayLabel',
'attr' => ['class' => "chosen"],
'required' => true,
]
)
->add('type',
EntityType::class,
[
'label' => 'Clothes Type',
'class' => RefType::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('refType')
->orderBy('refType.id', 'ASC');
},
'attr' => ['class' => "chosen"],
'placeholder' => 'Choose..',
'required' => true,
'choice_label' => 'label',
])
->add('line',
EntityType::class,
[
'label' => 'cotation.creation_form.ligne_de_cotation',
'class' => RefLine::class,
'choice_value' => function ($value) {
if ($value instanceof RefLine) {
return $value->getId();
} else {
return $value;
}
},
'query_builder' => function (EntityRepository $er) {
return $er->getShoppingLines();
},
'attr' => ['class' => "chosen"],
'placeholder' => 'Choose..',
'required' => true,
'choice_label' => 'getLabel',
])
IN my controller, this function is called upon submitting the form :
public function validerAction(Request $request, $idFashion)
{
$em = $this->getDoctrine()->getManager();
/** #var Fashion $fashion */
$fashion = ($idFashion === null) ? new Fashion() : $em->getRepository(
'App:Fashion'
)->find($idFashion);
$form = $this->createForm(FashionType::class, $fashion);
// try {
$form->handleRequest($request);
//} catch(\InvalidArgumentException) {
//dump($fashion);die;
// }
if ($form->isSubmitted() && $form->isValid()) {..}
Here are my setters:
/**
* Set line
*
* #param Refline $line
*
* #return Fashion
*/
public function setLine(RefLine $line)
{
$this->line = $line;
return $this;
}
/**
* Set type
*
* #param RefType $type
*
* #return Fashion
*/
public function setType(RefType $type)
{
$this->type = $type;
return $this;
}
/**
* Set storeClient
*
* #param Client $storeClient
* #return Fashion
*/
public function setStoreClient($storeClient)
{
$this->storeClient = $storeClient;
return $this;
}
THe three fields were declared like this :
/**
* #ORM\ManyToOne(targetEntity="App\Entity\RefLine")
* #ORM\JoinColumn(name="line_id", referencedColumnName="id", nullable=false)
*/
private $line;
In EntityType::class field type is by default nullable. If you want to add validation on that then you have to write this
/**
*
* #Assert\NotBlank(message="Please enter Line", groups="groupName")
*/
private $line;
For more details you can read https://symfony.com/doc/current/validation.html
if you are using group name then you should declare in Form
$resolver->setDefaults([
// ...
'validation_groups' => ['Default', 'groupName'],
]);

Problems to validate empty date field with extbase

Why did Extbase throws an exception if my start date field is empty. If the date has the wrong format, the validation works. But an empty value shows this:
Uncaught TYPO3 Exception ... Events::setEnd() must be an instance of DateTime, null given ...
What is wrong with the following code in my model?
/**
* start
*
* #var \DateTime
* #validate NotEmpty
* #validate(type="DateTime", options={"locale"="de_DE"})
*/
protected $start;
And here is my TCA
'start' => [
'exclude' => false,
'label' => 'Start',
'config' => [
'type' => 'input',
'renderType' => 'inputDateTime',
'size' => 10,
'eval' => 'datetime',
'default' => time()
],
],
Cache cleared, typo3temp folder deleted.
UPDATE:
Perhaps the error is in my initializeUpdateAction() where i have to set the date format?
public function initializeUpdateAction() {
$user = $this->request->getArgument('feUsers');
$events = $user['events'];
if( is_array($events) ) {
foreach ($events as $i => $event) {
$this->arguments->getArgument('feUsers')
->getPropertyMappingConfiguration()->forProperty('events.'.$i.'.start')
->setTypeConverterOption(
'TYPO3\\CMS\\Extbase\\Property\\TypeConverter\\DateTimeConverter',
\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::CONFIGURATION_DATE_FORMAT,
'd.m.Y'
);
}
}
}
enhance your validation:
'eval' => 'datetime,int',
an empty date field is represented with 0, which is no valid date format.

unable to transform value for property path "tagname". Expected a Doctrine\Common\Collections\Collection object

I am working with two ManyToMany related entities, namely category and tag.
The entity Tag(relevant details):
/**
*
* #var string
*
* #ORM\Column(name="tagname", type="string")
*/
protected $tagname;
/**
* #ORM\ManyToMany(targetEntity="Category", mappedBy="tags")
*/
protected $categories;
The entity Category(relevant details):
/**
*
* #var string
*
* #ORM\Column(name="CategoryName", type="string",length=200)
*/
protected $categoryname;
/**
* #ORM\ManyToMany(targetEntity="Tag", inversedBy="categories")
*/
protected $tags;
I have a form with a select-input(CategoryType) and a multiple select-input(TagType) fields. Both the fields are EntityType fields. The TagType is embedded inside the CatgoryType.
For this I am not able to utilise the cascade=persist functionality and am adding the submitted tags manually inside my controller. On submission the form data gets persisted in the database without any issues.
The problem is, after submission, when I fetch the submitted category(and the associated tags) in my controller, and pass it to the form, I get this error - Unable to transform value for property path "tagname": Expected a Doctrine\Common\Collections\Collection object.
The var_dump result of the fetched category object(var_dump($category->getTags()->getValues());) gives me an array of the associated Tag objects, with the property protected 'tagname' => string 'tag1'.
From what I understand Interface Collection is quite similar to a php array and My guess is that the tagname field expects all the tagnames in an ArrayCollection or Collection object format. I am not sure whether what is the specific difference.
However I am still clueless how do I pass the already persisted category object in my form.
Here are the categoryname and tags field in the CategoryType:
$builder->add('categoryname', EntityType::class, array(
'class' => 'AppBundle:Category',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.id', 'ASC');
},
'choice_label' => 'categoryname',
'expanded' => false,
'multiple' => false,
'label' => 'Choose Category',
));
$builder->add('tags', CollectionType::class, array(
'entry_type' => TagType::class,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
));
Here is the embedded tagname field in the TagType:
$builder->add('tagname', EntityType::class, array(
'class' => 'AppBundle:Tag',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('t')
->orderBy('t.id', 'ASC');
},
'choice_label' => 'tagname',
'expanded' => false,
'multiple' => true,
'label' => 'Choose Tags',
));
Any ideas?
Try to rid off 'multiple' => true in embedded form. This worked for me.
I've had a similar problem where my EntityType form element gave me this error message:
Unable to reverse value for property path 'my_property' Expected an array.
Setting multiple to false would make the implementation useless, so a better option for me was to set the empty_data to an empty array:
"empty_data" => [],
In your case you might be able to solve the problem by setting the empty_data to a Collection, something like this will probably work:
"empty_data" => new ArrayCollection,
Here is how you do it (from source: https://www.youtube.com/watch?v=NNCpj4otnrc):
***And here is a text version of this image #1, the reason I did not add text because it was hard to read when the format is not proper!
<?php
namespace App\Form;
use App\Entity\Post;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
class PostType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, [
'attr' => [
'placeholder' => 'Etner the title here',
'class' => 'custom_class'
]
])
->add('description', TextareaType::class, [
'attr' => [
'placeholder' => 'Enter teh description here',
]
])
->add('category', EntityType::class, [
'class' => 'App\Entity\Category'
])
->add('save', SubmitType::class, [
'attr' => [
'class' => 'btn btn-success'
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Post::class,
]);
}
}
***And here is a text version of this image #2, the reason I did not add text because it was hard to read when the format is not proper!
class FormController extends AbstractController
{
/**
* #Route("/form", name="form")
* #param Request $request
* #return Response
*/
public function index(Request $request)
{
$post = new Post(); // exit('this');
/*$post->setTitle('welcome');
$post->setDescription('description here');*/
$form = $this->createForm(PostType::class, $post, [
'action' => $this->generateUrl('form'),
'method' => 'POST'
]);
// handle the request
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
// var_dump($post); die();
// save to database
$em = $this->getDoctrine()->getManager();
$em->persist($post);
$em->flush();
}
return $this->render('form/index.html.twig', [
'postaform' => $form->createView()
]);
}
My assumption was wrong, I need to either make tagname field an array or create a ManyToOne or ManyToMany relationship with any other entity so it can be an arraycollection. Only then it is possible to use tagname as a multiple select field.
/**
*
* #var array
*
* #ORM\Column(name="tagname", type="array")
*/
protected $tagname;
or
/**
* #ORM\ManyToMany(targetEntity="SOME_ENTITY", mappedBy="SOME_PROPERTY")
*/
protected $tagname;
or
/**
* #ORM\ManyToOne(targetEntity="SOME_ENTITY", mappedBy="SOME_PROPERTY")
*/
protected $tagname;
The exception thrown is pretty explicit, and the problem is probably here:
$builder->add('tagname', EntityType::class, array()
According to your Tag entity, Tag::$tagname is not an entity collection, it's a string. You should add the property with
$builder->add('tagname', TextType::class, array()
shouldn't you ?

Symfony 2 Persist multiple entities in one form

I work on a symfony 2 app and I need some help to resolve a case I never had before.
My app has only one page with multiple blocs. Let's consider animals for a simple example. On my page, I have a first bloc where I show N dogs with all their caracteristics, then a second bloc where I show rabbits, then a third bloc with cats ect.
I also have an admin page where the user may modify the displayed data on the main page. The problem I have is that I must use only one page to admin all the blocs. That means that I have N dog, M rabbits, P cats entities displayed on my admin page and when I submit my form, Symfony must delete, update or insert each entity in database.
To do so, I created an entity and a formType for each animal, and a mapped superclass called Website with arraycollections for each entity.
Website Entity :
/**
* Website
* #ORM\MappedSuperclass()
*/
class Website
{
/**
* #var ArrayCollection
*/
private $dogs;
/**
* #var ArrayCollection
*/
private $rabbits;
/**
* #var ArrayCollection
*/
private $cats;
...
}
Website Type :
class WebsiteType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('dogs', 'collection', array(
'type' => new DogType(),
'allow_add' => true,
'allow_delete' => true
))
->add('rabbits', 'collection', array(
'type' => new RabbitType(),
'allow_add' => true,
'allow_delete' => true
))
->add('cats', 'collection', array(
'type' => new CatType(),
'allow_add' => true,
'allow_delete' => true
))
->add('save', 'submit')
;
}
}
In my controller :
$manager = $this->getDoctrine()->getManager();
$dogs = $manager->getRepository('MyBundle:Dog')->findAll();
$rabbits = $manager->getRepository('MyBundle:Rabbit')->findAll();
$cats = $manager->getRepository('MyBundle:Cat')->findAll();
$website = new Website();
$website->setDogs($dogs);
$website->setRabbits($rabbits);
$website->setCats($cats);
$form = $this->createForm(new WebsiteType(), $website);
if ($form->handleRequest($request)->isValid()) {
$manager->persist($website);
$manager->flush();
}
return $this->render('MyBundle:Default:admin.html.twig', array(
'form' => $form->createView(),
'website' => $website
));
When I submit the form, I have this error :
The given entity of type 'MyBundle\Entity\Website'
(MyBundle\Entity\Website#0000000040d8aeb900007fd77a072110) has no
identity/no id values set. It cannot be added to the identity map.
What should I do ?

symfony2 Entity Object vs. integer crashes

I have defined a entity like :
/**
* #ORM\ManyToOne(targetEntity="Pr\UserBundle\Entity\Client")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
private $client_id;
.....
public function setClientId($clientId = null)
{
$this->client_id = $clientId;
return $this;
}
There are two controllers with which I can create a new db entry. the first one is "admin-only" where the admin can create a db entry with a client id of his choice:
->add('client_id', 'entity', array(
'data_class' => null,
'attr' => array(
'class' => 'selectstyle'),
'class' => 'PrUserBundle:Client',
'property' => 'name',
'required' => true,
'label' => 'staff.location',
'empty_value' => 'admin.customer_name',
'empty_data' => null
)
)
......
// Handling the form
$em = $this->getDoctrine()->getManager();
$saniType->setName($form->get('name')->getData());
$saniType->setClientId($form->get('client_id')->getData());
$saniType->setCreated(new \DateTime(date('Y-m-d H:m:s')));
$saniType->setCreatedBy($user->getUsername());
$em->persist($saniType);
$em->flush();
The second one is for the client itself, where he's not able to set a different client id. Therefor I just removed the form field "client_id" and replace it by the users->getClientId():
$saniType->setSpecialClientId($form->get('client_id')->getData());
When I add an entry as admin, it work fine. If I try to add one as "client", it crashes with following error message
Warning: spl_object_hash() expects parameter 1 to be object, integer given in /var/www/symfony/webprojekt/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 1389
I'm a newby in symfony, so I haven't got the strength to figure out what happens. I only knew that in the first case, admin will submit a object (entity). In the second (client) case, I set an integer as I get one by
$user = $this->container->get('security.context')->getToken()->getUser();
Is there a solution for it so that I can handle entity object AND given integers like it comes when the client add an entry?
You should change you relation defnition to:
/**
* #ORM\ManyToOne(targetEntity="Pr\UserBundle\Entity\Client")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
private $client;
.....
public function setClient($client = null)
{
$this->client = $client;
return $this;
}
Then in form:
->add('client', 'entity', array(
'data_class' => null,
'attr' => array('class' => 'selectstyle'),
'class' => 'PrUserBundle:Client',
'property' => 'name',
'required' => true,
'label' => 'staff.location',
'empty_value' => 'admin.customer_name',
'empty_data' => null
)
)
Handling the form:
form = $this->createForm(new SaniType(), $entity);
$form->handleRequest($request);
if ($form->isValid()) {
// perform some action...
return $this->redirect($this->generateUrl('some_success'));
}
More about handling form: http://symfony.com/doc/current/book/forms.html#handling-form-submissions
Also worth nothing:
for auto update properties like createdBy / updatedBy i would recommend you to use Doctrine Extension: https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/blameable.md
You don't have to know the client id. You have to set the Client himself.
/**
* #ORM\ManyToOne(targetEntity="Pr\UserBundle\Entity\Client")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
private $client;
.....
public function setClient(\Pr\UserBundle\Entity\Client $client = null)
{
$this->client = $client;
return $this;
}
I found a Solution:
Symfony2: spl_object_hash() expects parameter 1 to be object, string given in Doctrine
It's a workaround but it's ok for the moment.

Resources