What should I do? Doctrine relations - symfony

I want to create a dynamic form Photo with these fields:
title
album
Album is a checkboxes field ( with 'multiple' attr ).
Photo has a manyToOne relationship with Album.
What I want to do is persist several times the photo with different album values, not persist an arrayCollection of albums in one photo.
I tried to do
if ($request->getMethod() == 'POST') {
$form->bind($request);
if ($form->isValid()) {
$data = $form->getData();
$listeAlbum = $data['album'];
foreach ($listeAlbum as $album) {
$em->persist($photo);
$em->flush();
}
I get the error ( at the line bind->($request) )
Catchable Fatal Error: Argument 1 passed to MySite\TestBundle\Entity
\Photo::setAlbum() must be an instance of MySite\TestBundle\Entity\Album,
instance of Doctrine\Common\Collections\ArrayCollection given, ...
Is there a way to do this?
EDIT : more code.
my form type
class PhotoType extends AbstractType {
private $securityContext;
public function __construct(SecurityContext $securityContext)
{
$this->securityContext = $securityContext;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('file', 'file') // , array("attr" => array("multiple" => "multiple", ))
->add('titre');
$user = $this->securityContext->getToken()->getUser();
if (!$user) {
throw new \LogicException(
'The FriendMessageFormType cannot be used without an authenticated user!'
);
}
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($user) {
$form = $event->getForm();
$formOptions = array(
'multiple' => true, //several choices
'expanded' => true, // activate checkbox instead of list
'class' => 'EVeilleur\DefuntBundle\Entity\Album',
'property' => 'titre',
'query_builder' => function (EntityRepository $er) use ($user) {
// build a custom query
return $er->createQueryBuilder('u')->add('select', 'u')
->add('from', 'EVeilleurDefuntBundle:Album u');
// ->add('where', 'u.id = ?1')
// ->add('orderBy', 'u.name ASC');
},
);
// create the field, = $builder->add()
// field name, field type, data, options
$form->add('album', 'entity', $formOptions);
}
);
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'EVeilleur\DefuntBundle\Entity\Photo'
));
}
/**
* #return string
*/
public function getName()
{
return 'eveilleur_defuntbundle_photo';
} }
entity relation, in Photo.php
/**
* #ORM\ManyToOne(targetEntity="EVeilleur\DefuntBundle\Entity\Album")
* #ORM\JoinColumn(nullable=true)
*/
private $album;

I think the problem is that you are adding Album to your Photo form as a single entity field, but your options mean it gets translated into a set of checkboxes that is logically turned on submit by Symfony into an ArrayList. Your Photo entity expects only a single Album.
Could you post the rest of your Photo entity, specifically the setAlbum method (apologies if you don't actually need to create one in this case, I always create mine even if they're trivial!)?
If Photo is ManyToOne with Album, then many Photos can be in one Album, and the Album field should probably be a dropdown - you shouldn't be able to choose multiple Albums. This may be the root of the issue.
If many Photos can be in one Album, but also each Photo can be in many Albums, then you really have a ManyToMany relationship, doesn't seem to be modelled like that here.

Related

Multiple rows from the same entity in single form

I have a Doctrine extensions tree Entity which I want to put entirely (or only a node and all its children) in a form. That is, I want to be able to modify the entire (sub)tree in a single form. I have taken a look at “multiple rows in form for the same entity in symfony2,” however, I'm unable to apply it to a tree with all its children in Symfony3.
I was thinking of something as a controller like
$repository = $this->getDoctrine()->getRepository('AppBundle:Category');
$tree = $repository->children(null, true);
$form = $this->createForm(CategoryType::class, $tree);
and a CategoryType like
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Category::class /* or should it be `null`? */,
));
}
Use the following Controller:
public function editAction(Request $request)
{
$repository = $this->getDoctrine()->getRepository('AppBundle:Category');
$categories = $repository->children(null, false); // get the entire tree including all descendants
$form = $this->createFormBuilder(array('categories' => $categories));
$form->add('categories', CollectionType::class, array(
'entry_type' => CategoryType::class,
));
$form->add('edit', SubmitType::class);
$form = $form->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
// $data['categories'] contains an array of AppBundle\Entity\Category
// use it to persist the categories in a foreach loop
}
return $this->render(...)
}
The CategoryType is just like ‘normal,’ e.g., the one in my question.
It is key to create the form builder with array('categories' => $categories) and add a form CollectionType field with the name categories.

One to many relation throw exception on save

I have two entites. "Institution" and "User". The Insitution have a onetomany relation to user. I create a form "InstitutionType" and if i want to save a new insitution the "handleReqeust($request)" throw this exception.
Neither the property "users" nor one of the methods "addUser()"/"removeUser()", "setUsers()", "users()", "__set()" or "__call()" exist and have public access in class "App\UserBundle\Entity\Institution".
Entity User
/**
* #ORM\ManyToOne(targetEntity="Institution", inversedBy="users")
* #ORM\JoinColumn(name="institution_id", referencedColumnName="id")
*/
protected $institution;
public function setInstitution(\App\UserBundle\Entity\Institution $institution = null)
{
$this->institution = $institution;
return $this;
}
public function getInstitution()
{
return $this->institution;
}
Entity Institution
/**
* #ORM\OneToMany(targetEntity="App\UserBundle\Entity\User", mappedBy="institution")
*/
protected $users;
public function addUser(\App\UserBundle\Entity\User $users)
{
$this->users[] = $users;
return $this;
}
public function removeUser(\Ebm\UserBundle\Entity\User $users)
{
$this->users->removeElement($users);
}
public function getUsers()
{
return $this->users;
}
InstitutionType
$users = $this->entityManager->getRepository('AppUserBundle:User')->findByIsActive(true);
->add('users', 'entity', array(
'label' => 'responsibleperson',
'attr' => array(),
'class' => 'AppUserBundle:User',
'choices' => $users,
'multiple' => false,
'expanded' => false,
'empty_value' => '----------------')
)
Can someone help my to solve this issue?
Rename addUser to addUsers and removeUser to removeUsers.
Symfony2/Doctrine obviously has no knowledge of singular/plural words and can't guess that to add a single entry to the users collection it would be semantically more correct to call addUser() instead of addUsers.
Please note that you can always use the console command doctrine:generate:entities AcmeDemoBundle:Institution to generate missing fields in the entity classes.
If this doesn't help you need to make sure you use the same notation in your form type like in your entity configuration (annotation, yml or xml).

Symfony2 Unidirectional ManyToMany Collection Form

I'm stuck :-)
Maybe I just have the wrong keywords in my research. But I don't get through. So I hope one of the crowd can help me!
I have a unidirectional ManyToMany Association. When I try to submit the form (and therefore persist), I get the error:
A new entity was found through the relationship 'TrainerInnenPool\AppBundle\Entity\Trainer#applicationFields' that was not configured to cascade persist operations for entity: TrainerInnenPool\AppBundle\Entity\ApplicationField#0000000022ef36c600000000087bcbc3.
When I do "cascade persist" a new Entity is created which actually already exists.
I have 2 Entities:
Trainer
ApplicationField
The Trainer has a unidirectional ManyToMany association to the ApplicationField:
class Trainer {
public function __construct()
{
$this->applicationFields = new ArrayCollection();
}
[...]
/**
* #ORM\ManyToMany(targetEntity="ApplicationField")
*/
protected $applicationFields;
The ApplicationField has a self-referencing OneToMany association:
class ApplicationField {
public function __construct() {
$this->children = new ArrayCollection();
}
[...]
/**
* #ORM\OneToMany(targetEntity="ApplicationField", mappedBy="parent")
*/
private $children;
/**
* #ORM\ManyToOne(targetEntity="ApplicationField", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
I want to create a form where I can add a Trainer - ApplicationField association.
Therefore I have an ApplicationFieldCollectionType:
class ApplicationFieldCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('applicationFields', 'collection', array(
'type' => new ApplicationFieldType(),
'allow_add' => true,
'label' => false
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TrainerInnenPool\AppBundle\Entity\Trainer',
));
}
The embeded Type is as follows:
class ApplicationFieldType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('applicationFieldName', 'entity', array(
'class' => 'TrainerInnenPoolAppBundle:ApplicationField',
'label' => false,
'mapped' => false,
'property' => 'name',
'query_builder' => function(EntityRepository $repository) {
return $repository->createQueryBuilder('application_field')
->where('application_field.parent is NULL');
}
));
$builder->add('name', 'entity', array(
'class' => 'TrainerInnenPoolAppBundle:ApplicationField',
'label' => false,
'property' => 'name',
'query_builder' => function(EntityRepository $repository) {
return $repository->createQueryBuilder('application_field')
->where('application_field.parent = 1');
}
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TrainerInnenPool\AppBundle\Entity\ApplicationField',
));
}
Last missing part: The controller:
public function editApplicationField($id, Request $request)
{
$entityManager = $this->getDoctrine()->getEntityManager();
$trainer = $entityManager->getRepository('TrainerInnenPoolAppBundle:Trainer')->find($id);
$editForm = $this->createForm(new ApplicationFieldCollectionType(), $trainer);
if ($request->getMethod() == 'POST') {
$editForm->handleRequest($request);
$entityManager->flush();
}
When I fetch the ApplicationField entities from the Trainer and try to persist those,
$editForm->handleRequest($request);
$af = $trainer->getApplicationFields();
foreach ($af as $applicationField) {
$trainer->addApplicationField($applicationField);
$entityManager->persist($applicationField);
}
$entityManager->flush();
I'm not able to do so, since I get a "Duplicate Entry For Key PRIMARY" - Exception.
I think I terribly miss any obvious point. If someone could help me, gave me a hint or just mentioned to update my question with information, I would be so thankful.
Kind regards...
Your nested type setters are not called because of a missing property:
->add('applicationFields', 'collection', array(
'type' => new ApplicationFieldType(),
...
'by_reference' => false
...
http://symfony.com/doc/current/reference/forms/types/collection.html#by-reference
When you plan to have collection fields that are addable/deletable("allow_add", "allow_delete"), you should always provide the "by_reference" => false option, in order to call the setters directly on the related entities and then build the association, rather than chain methods from the original entity.
Hope this helps!
Well you need the cascade={"persist"} annotation because you want to persist the whole entity with its association to ApplicationField. If you don't use cascade={"persist"}, you'd have to manually persist the entities.
The entities are already added to the trainer, so if you want to manually persist the entities you should remove the line
$trainer->addApplicationField($applicationField);
and only execute the persist.
This should work. Try it. But I think the effect will be the same as if you use cascade persist. So it's not the final solution I think, but the first step to understand the problem, why the manual persist didn't work before.

Symfony formbuilder self-reference entity grouped

I want a choice list (dropdown) grouped by parent categories (not selectable).
Is this even possible?
Example:
- Vehiculs (not selectable)
-- Car (selectable)
-- Boat (selectable)
- Computers (not selectable)
Entity Category
/**
* #ORM\OneToMany(targetEntity="Category", mappedBy="parent")
**/
private $children;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
**/
private $parent;
Form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text',
[
'attr' => ['placeholder' => 'Titel', 'class' => 'form-control', 'autocomplete' => 'off'],
'label' => false
]
)
...
->add('category', 'entity',
[
'property' => 'name',
'class' => '***ArticleBundle:Category',
]
)
;
}
With the code above i only get the parents and they are selectable.
I would like to group the children of those parents (1 depth) and make only the children selectable options.
Just posting this because this seems to be the first google hit for this problem and I had to solve it.
You in fact can directly tell symfony to create a tree select in FormBuilder with a small workaround which is totally clean.
The fact that the "Entity" is a child of "Choice" helps there a lot. The solution consists of three parts: Controller, FormType and Entity Repository
Let's start with the Controller
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(new AcmeType(array(
'repository' => $em->getRepository('AcmeBundle:Category'),
)), $data, array(
'action' => *add your route here*
'method' => 'POST'
));
Straight forward form generation, except the adition of the repository as a parameter for your FormType (which we need there)
Your FormType includes the following
/** #var CategoryRepository */
protected $repository;
public function __construct($options = array()){
$this->repository = $options['repository'];
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
*add your other stuff here*
->add('category', 'entity', array(
'empty_value' => 'Please Select...',
'required' => true,
'choices' => $this->repository->getCategoryData(),
'class' => 'Acme\AcmeBundle\Entity\Category'
))
;
}
We create an Entity field type and add the "choices" and fill it with the response from the "getCategoryData".
In your Repository of your Data Entity (in this case Category Entity) you create the following function
public function getCategoryOptions()
{
$data = array();
$categories = $this
->createQueryBuilder('c')
->select('c')
->getQuery()
->getResult();
foreach( $categories as $category ){
/** #var Category $category */
if( !$category->getParent() ){
continue;
}
if(!array_key_exists($category->getParent()->getName(), $data) ){
$data[$category->getParent()->getName()] = array();
}
$data[$category->getParent()->getName()][$category->getId()] = $category;
}
return $data;
}
This simple function does a simple query to the Database to select your Categories and then runs a foreach through them and builds an array. As seen in the FormType above it parses that array directly to the 'choices' parameter of your Entity field type.
The Form Renderer of symfony is smart enough to add tags with labels.
Et voila, you have a grouped dropdown box with non-selectable "Parents".
You can not directly tell symfony to create a tree select in form builder.
First, you have to check here;
http://symfony.com/doc/current/reference/forms/types/entity.html
You can use query builder on your entity field to get parent - child relation but parents also would be selectable.
So you have to look another solutions like this:
How to disable specific item in form choice type?

Symfony2 Forms: validate related entity property

I have entity Message with ManyToOne relation with entity User:
class Message
{
...
/**
* #var User $sender
*
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User")
* #ORM\JoinColumn(name="sender_id", referencedColumnName="id")
*
**/
private $sender;
...
}
If $sender doesn't have email value i need to create new field for my form, so i create form for Message entity in Contoller:
$user = $this->getUser();
$message = new Message();
$message->setSender($user);
$formBuilder = $this->createFormBuilder($message, array(
'cascade_validation' => true
));
$formBuilder->add('body', 'textarea');
if (!$user->getEmail()) {
$formBuilder->add('email', 'email', array(
'property_path' => 'sender.email'
));
}
And i have some validation rules in validation.yml for entity User. Can i automatically validate this field by my validation rules for User entity in another entity's form? I don't know how to do it.
I found workaround right now: create new MissingEmailType:
class MissingEmailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\UserBundle\Entity\User',
'validation_groups' => array(
'MissingEmail'
),
));
}
public function getName()
{
return 'missing_email';
}
}
But it looks complicated. Is there any better solutions?
You could redirect the page to the user profile page instead of loading the message form and state that the user needs to add an email prior to adding the message. If you redirect quickly or create a popup, the user might not be turned off as long as they can return to the original page after adding their email. Then validtion is simple since you only need to validate the user entity.

Resources