Symfony2 Form with collection type (many-to-many) - symfony

What I have
I have a database table "Teams" and a table "Players", connected with a Teams_has_Players table (many-to-many relationship). I generated my entities. I can now succesfully retrieve the players from a team by doing: $players = $team->getPlayers()->toArray();
What I'm trying to do
I'm trying to make a form where you can edit all of the team's player names and their position on the field. So I basically would like to have some rows with in each row an input field with the name and an input field with his location.
What I tried to do
So I read a lot about the Symfony2 Collection type and tried this:
$form = $this->createFormBuilder(null)
->add('name', 'text', array('label' => 'Name', 'data' => $team->getName()))
->add('players', 'collection', array('data' => $team->getPlayers()->toArray()))
->getForm();
In my view I tried this:
<ul>
{% for player in form.players %}
<li>
{{ form_widget(player.name) }}{{ form_widget(player.position) }}
</li>
{% endfor %}
</ul>
But I get this error:
The form's view data is expected to be of type scalar, array or an
instance of \ArrayAccess, but is an instance of class
MatchTracker\Bundle\AppBundle\Entity\Players. You can avoid this error
by setting the "data_class" option to
"MatchTracker\Bundle\AppBundle\Entity\Players" or by adding a view
transformer that transforms an instance of class
MatchTracker\Bundle\AppBundle\Entity\Players to scalar, array or an
instance of \ArrayAccess.
So I added 'data_class' => 'MatchTracker\Bundle\AppBundle\Entity\Players', but then I get an error that The form's view data is expected to be an instance of class MatchTracker\Bundle\AppBundle\Entity\Players, but is a(n) array. You can avoid this error by setting the "data_class" option to null
Anyone that can help me solve this problem? I just want to edit the team's player names/locations/.. in a form. If that works I'm going to extend the form so I can add/remove players.

This is what your are trying to do.

Related

Symfony 4. ManyToMany association with attribute. Custom form to insert attribute value, how do I?

A Symfony 4 project. I am not able to see ho to code the following spec.
Say I have two entities Alpha and Beta.
When I create an Alpha object alpha, I wish to associate (ManyToMany) to it one or more Beta objects. I know how to render new and edit Forms to do so.
I wish to enrich the AlphaBeta join table with an attribute, say the Cost to associate a Beta to an Alpha. The issue is that I am not able to enrich the forms above as to insert or edit a Cost value, when I create a Alpha object and associate to it a Beta object.
Is there a standard way to code a situation of this kind?
I read that the way to go is to have two OneToMany associations Alpha->AlphaBeta, and Beta->AlphaBeta, but even doing so I am not able to define/render a Form for Alpha as to create a new Alpha object, to associate to it a Beta object (or more) and to assign to such association a Cost value.
Your advise is very welcome. Thanks
Generally, if the relation/association itself should have an attribute, then the many-to-many mapping in doctrine isn't sufficient anymore, since associations can't store additional data. So you correctly note, that you need an extra AlphaBeta entity/class that holds the data. (As already posted by Ali Kazemi, there is a tutorial for this)
But you wonder about how the cost field can be added to your form...
Since the cost is part of the AlphaBeta entity/class in your case, the form field should be in a form type AlphaBetaType that - probably depending on the options provided - should render an AlphaType and/or BetaType sub form, and a cost form field. Custom form themeing can display it in a way, that it doesn't appear as if it was a subform, if that is a concern. (However, it should be noted that custom form theming can be annoying at times...)
In general, the form structure/hierarchy usually very much resembles the entity structure/hierarchy. And only sporadically hiding data or mapping/transforming it to be displayed or handled differently.
Alternatively you can add an unmapped form field and later store that in your AlphaBeta, but that on average isn't simpler, since it involves "manual" handling.
It seems I did find a way, indeed it is pretty straightforward Synfony 4 code.
Hope it could be useful to somebody.
(note: I used php bin/console make:entity/crud/form to write the needed scripts. Below how I needed to modify the code I got from make)
So, say I have Alpha and Beta entities.
I wish to have a form to create a new Alpha object, to associate to it one or more Beta objects, and to fill in a Cost value for each Alpha-Beta association. I want a edit form too.
First I create a new AlphaBeta entity, whose fields are:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Alpha", inversedBy="alphabetas", cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
*/
private $alpha;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Beta", inversedBy="betaalphas", cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
*/
private $beta;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $cost;
Within class Alpha, I need
/**
* #ORM\OneToMany(targetEntity="App\Entity\AlphaBeta", mappedBy="alpha", orphanRemoval=true, cascade={"persist"})
*/
private $alphabetas;
with usual 'getAlphaBeta, addAlphaBeta and removeAlphaBeta methods. (similarly for Beta)
I create usual CRUD controllers for the new AlphaBeta entity. To have an AlphaBeta Form which could be used as a subform too, I define
class `AlphaBetaEmbeddedForm` extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder ->add('Beta', EntityType::class, array(
'class' => Beta::class,
'multiple' => false,
'expanded' => true,
'choice_label' => 'betaTitle' ))
->add('cost', TextType::class);
if(empty($options['remove_alpha_field'])) {
$builder->add('Alpha', EntityType::class, array(
'class' => Alpha::class,
'multiple' => false,
'expanded' => true,
'choice_label' => 'alphaTitle'
));}}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => AlphaBeta::class,
'remove_alpha_field' => false,
]);
}}
remove_alpha_field is the trick that let me use the form above as a subform within a form to create an Alpha object:
class AlphaType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('alphaTitle')
->add('AlphaBetas', CollectionType::class, array(
'entry_type' => AlphaBetaEmbeddedForm ::class,
'entry_options' => ['label' => true, 'remove_alpha_field' => true],
'allow_add' => true,
'label' => 'Betas',
'by_reference' => false
));}
To render the subforms within the main form, I need some JS, as suggested here, to be inserted within the new.html.twig and edit.html.twig templates for Alpha:
{% block javascripts %} <script type="text/javascript">
jQuery(document).ready(function () {
$("#add-another-collection-widget").click(function(e){
var list = jQuery(jQuery(this).attr('data-list-selector'));
var counter = list.data('widget-counter') | list.children().length;
var newWidget = list.attr('data-prototype');
newWidget = newWidget.replace(/__name__/g, counter);
counter++;
list.data('widget-counter', counter);
var newElem = jQuery(list.attr('data-widget-tags')).html(newWidget);
newElem.appendTo(list);
});});</script>{% endblock %}
To apply the latter, it seems you need to write each single row in the main form template _form.html.twig:
{{ form_start(form) }}
<div class="my-custom-class-for-errors">
{{ form_errors(form) }}
</div>
<div id="alphaTitle">
{{ form_row(form.alphaTitle) }}
</div>
{% if form.AlphaBetas %}
<b>Betas</b> </br></br>
{% for Beta in form.AlphaBetas %}
{% for BetaField in Beta %}
{{ form_row(BetaField) }}
{% endfor %}
{% endfor %}
{% endif %}
<div id="AlphaBeta-fields-list"
data-prototype="{{ form_widget(form.AlphaBetas.vars.prototype)|e }}"
data-widget-tags="{{ '<p></p>' |e }}"
data-widget-counter="{{ form.children|length }}">
{% for AlphaBetaField in form.AlphaBetas.vars.prototype.children %}
{{ form_row(AlphaBetaField) }}
{% endfor %}
</div>
<button type="button" id="add-another-collection-widget" data-list-selector="#AlphaBeta-fields-list">Insert Beta</button>
</br>
<button class="btn">{{ button_label|default('Save') }}</button>
{{ form_end(form) }}
That's all.
Note: within the main form to edit an Alpha object, I would like to insert a delete buttons for each Beta associated object. As much as I can see, it is not possible, since it would mean to insert a html form within an html form. Since I may delete a AlphaBeta association by the corresponding AlphaBeta delete action, it is not a big deal.
If you see how I can improve my code, you are welcome to advise me.

Unable to set custom data in show action field in symfony sonata admin

I have a show page and I want to add a custom value.
I have tried doing what I did in other actions which is to add an array to the
third parameter with the data key like so:
protected function configureShowFields(ShowMapper $showMapper)
{
$showMapper
->add('name')
->add('dateEnd')
->add('example', null,
array('data' => 'example value')
)
;
}
In the configureListFields action, this works. I have injected custom values with the data attribute.
But still I am not able to access key example in the show.html.twig file.
It gives me this error
Variable "example" does not exist.
What should I do to access this custom variable in the twig file ?
Try
{{ elements.elements.example.options.data }}
in your twig template
I used this solution. In the configureShowFields() method of an Admin class:
$showMapper
->with('Tab Name')
->add(
'any_name',
null,
[
'template' => 'Admin/Custom/any_name_show_template.html.twig',
'customData' => $this->someRepository->getSomeEntityBy($field),
'anotherCustomData' => $this->someService->getSomeDataBy($value),
]
)
;
In the custom template, you can access custom data by field_description.options.<customFieldName>, so for provided example data accessors would be {{ field_description.options.customData }} and {{ field_description.options.anotherCustomData }}
For the shorter field name in the Twig template, you can do like this:
{% set customData = field_description.options.customData %}
and access the custom data like {{ customData }}
Hope this helps and saves time.

edit only the first entity inside an embedded collection

I have a parent entity called Publisher and a child entity called User with a ManyToMany relation.
Inside the publisher form, I want to create/edit also the first user, which I achieve like this:
$builder
->add('title')
->add('users', 'collection', array(
'type' => new UserType(),
'allow_add' => true,
))
and in my twig template, I do
{{ form_row(edit_form.users.0.firstname) }}
{{ form_row(edit_form.users.0.lastname) }}
{{ form_row(edit_form.users.0.email) }}
This obviously only works as long as there is just one user assigned to the publisher, because otherwise symfony tries to validate the other users as well, whose data is missing.
Can someone give me a hint how to edit only the first user item in the collection from the publisher form?
You can set the field users "rendered" after having displayed the firt user:
{{ form_row(edit_form.users.0.firstname) }}
{{ form_row(edit_form.users.0.lastname) }}
{{ form_row(edit_form.users.0.email) }}
{% do edit_form.users.setRendered %}
With setRendered, Symfony2 won't try to validate the next users.
I solved the problem using Cerad's solution by introducing a getter and setter for the first user.
public function setFirstUser($user)
{
$this->users[0] = $user;
return $this;
}
public function getFirstUser()
{
return $this->users[0];
}
and in the form doing this
$builder
->add('title')
->add('firstUser', new UserType())
and calling the fields of the firstUser in the twig template The setRendered line just supresses other properties of the user object.
{{ form_row(edit_form.firstUser.firstname) }}
{{ form_row(edit_form.firstUser.lastname) }}
{{ form_row(edit_form.firstUser.email) }}
{% do edit_form.firstUser.setRendered %}
EDIT: because of Cerads feedback about the non-deterministic ordering of SQL rows I chose to create a real property "adminUser", which is a OneToOne relation to the first user attached to the entity.

Grouped checkboxes in Symfony / twig

I have 2 entities: Projects and Categories. I have a ManyToMany relation between these two.
The Categories has ManytoOne relation with the entity "industry"
At this moment, there is no direct relation between Projects and industry and I would like to keep this like so, for further search functionality. So in the category table, the list includes categories from all industries.
When I build the form to edit the project (using the form widget), I have a list of checkboxes representing all the categories listed in my category table.
I would like to group the category choices by industry. How can this be done on the form layout only? How can I extract the industry value from the twig widget form data and group the checkboxes by the industry entity?
Thanks Leevi,
I could not find how to implement the suggestion above using both industry and category related entities... I finally found this way of going around the issue, tell me if there is a simpler way, but this works perfect now.
This is my form in the controller
$form = $this->createFormBuilder($project)
->add('categories', 'entity', array(
'class' => 'ACMEProjectBundle:Category',
'property' => 'name',
'expanded' => true,
'multiple' => true,
->getForm();
I also pass to the rendered form the array of industries which has each a list of related categories
$industries = $this->getDoctrine()->getManager()->getRepository('ACMEProjectBundle:Industry')->findall();
In the form.html.twig template
{{ form_errors(form) }}
<form method="post" {{ form_enctype(form) }}>
{% for industry in industries %}
<h4>{{industry.name}}</h4>
<ul class="unstyled">
{% for category in industry.categories %}
{% set key = category.id %}
<li>{{ form_widget(form.categories[key]) }}{{category.name}}</li>
{% endfor %}
</ul>
{% endfor %}
{{form_rest(form)}}
Which gives me the wanted results.
Hopefully this will be enough direction without giving you exact code examples :).
You'll have to setup your form with an expanded, multiple, entity field like so:
<?php
// src/Acme/ProjectBundle/Controller/DefaultController.php
namespace Acme\ProjectBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Acme\ProjectBundle\Entity\Project;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
public function newAction(Request $request)
{
// create a project and give it some dummy data for this example
$project = new Project();
$form = $this->createFormBuilder($project)
->add('categories', 'entity', array(
'expanded' => true,
'multiple' => true,
'group_by' => 'industry.title'
))
->add('save', 'submit')
->getForm();
return $this->render('AcmeProjectBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
}
}
The group_by parameter groups the options based on the property path:
See: http://symfony.com/doc/current/reference/forms/types/entity.html#group-by
Now group_by renders a select tag but you should be able to override that with a custom twig theme or manually in the template.
Given the form above you can access the choices in {{ form.categories.vars.choices }} and iterate over them manually.
See: {% block choice_widget_collapsed %} in form_div_layout.html.twig to see how the select box is rendered.
Here's some more information of form theming: http://symfony.com/doc/current/cookbook/form/form_customization.html

Symfony2 Forms: How do I create a collection of 'entity' forms

I have a one to many unidirectional relationship in my model. A User has one of many Status.
Using doctrine these are mapped as a unidirectional many-to-many with a unique constraint on one of the join columns.
I'd like to use a symfony form to select a status from the status table, submit the form and have symfony persist the relationship.
I've tried two approaches:
Using the Entity form type, however this produces an error (due to the many-to-many relationship doctrine expects to receive an instance of ArrayCollection rather than a single status object.
Using a collection entity objects. When using this approach an empty div with the id status is rendered in the form. Where as I expected a select box to appear containing status options.
Here is the code. Where am I wrong?
Entity code:
/**
* #ORM\ManyToMany(targetEntity="Status")
*/
protected $status;
Form type code:
$builder->add('status', 'collection', array(
'type' => 'entity',
'allow_add' => true,
'options' => array(
'class' => 'SunflyCoreBundle:Status',
'property' => 'name',
))
);
Form template code:
<form action="{{ path('_product_create_process') }}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" />
</form>
HTML Rendered on the page:
<div id="product_status" data-prototype="STUFF THAT WONT RENDER ON SO"></div>

Resources