edit only the first entity inside an embedded collection - symfony

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.

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 form with some disable field

i have a form with some field and i want user edit only some field and the other field must be only visible .
In my controller i have the form
$element = $this->getDoctrine()->getRepository('AppBundle:Element')->find($id_element);
$form = $this->createForm(ElementType::class, $element, array('user' => $user));
code here`$form->handleRequest($request);
Now in my twig file
{{ form_start(form) }}
{{ form_widget(form.element,{ 'attr':{'disabled':'disabled'}}) }}
In this way when i click submit button say not focusable field so i add
{{ form_widget(form.element,{ 'attr':{'disabled':'disabled'}}) }}
but when i save the field element in database become null
How i can do it ??
Use readonly attribute for it
{{ form_widget(form.element,{ 'attr':{'readonly':'1'}}) }}
Values of disabled controls are not submitted with a form.
https://www.w3.org/TR/PR-html40-971107/interact/forms.html#h-17.7

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

symfony trying to export a csv file from a link

I have this function in the controller, that returns a form with a select box. When the values are selected, I retrieve them with
$manifestations = $form['manifestations']->getData();
then put them in a repository function that queries the database $invites = $repository->searchInviteByManif($manifestations);
public function indexAction() {
$entity = new Invite();
$form = $this->createForm(new ManifSearchType(), $entity);
$request = $this->get('request');
$invites = null;
if ($request->getMethod() == 'POST') {
$form->handleRequest($request);
$message = '';
$manifestations = $form['manifestations']->getData();
$repository = $this->getDoctrine()
->getManager()
->getRepository('PrifProtocoleBundle:Invite');
$invites = $repository->searchInviteByManif($manifestations);
$response1 = $this->render('PrifProtocoleBundle:Invite:index.html.twig', array(
'form' => $form->createView(),
'entities' => $invites,
'message' => $message,
));
return $response1;
}
return array(
'form' => $form->createView(),
'entities' => $invites,
);
}
This function then returns a view index.html.twig with a table and all the fields found in the db.
What I want is to export all the queried data $invites in a CSV file, by clicking on a link directly from the HTML table.
So I've put an href="" link in the Twig file,
{% if message is defined %}
<div class="pdf">
<img height="40px" width="40px" src={{ asset('bundles/prifprotocole/images/excel.jpg') }}>
{% for entity in entities %}
<tr class="{{ cycle(['odd', 'even'], loop.index0) }}">
<td>{% if entity.etat == 1 %} Actif {% else %} Inactif {% endif %}</td>
<td>{{ entity.titreGrade }} {{ entity.prenom }} {{ entity.nom }}</td>
<td>{{ entity.fonction }}</td>
This is how I use to export the CSV file without the link :
$response2 = $this->render('PrifProtocoleBundle:Invite:export.csv.twig', array(
'entities' => $invites));
$response2->headers->set('Content-Type', 'text/csv');
$csvfile = $response2->headers->set('Content-Disposition', 'attachment; filename="export.csv"');
return $csvfile;
export.csv.twig file
{% for entity in entities %}
Id {{ entity.id }};
Etat {{ entity.etat }};
Titregrade {{ entity.titreGrade }};
Prenom {{ entity.prenom }};
Nom {{ entity.nom }};
Fonction {{ entity.fonction }};
{% endfor %}
Can someone give me a detailed solution on how to perform this? Much thanks!
You should simply pass the filter criterias, here the $manifestations array as a route parameter, by serializing it first.
Then, you should put a controller like downloadCsvAction() handling this route, querying the $invites from the database, and rendering your export.csv.twig template.
Another option, if you cannot serialize the data in URI, is to create a form with hidden fields containing the data. Then you replace the download link with the submit button of this hidden form.
Guillaume's answer is great if you don't mind fetching your data twice. Once when you display your page, and then when you download the CSV. Another way to do this is to cache the answer. I will give an example using memcache (which is what I use).
First, make sure that memcache is active in your php.ini.
Then before rendering your first page (after fetching the data), cache it:
$key = "csv.$userId";
$memcache = new \Memcache();
$memcache->connect($yourServerHost, $yourServerPort);
$memcache->set($key, json_encode($invites), MEMCACHE_COMPRESSED, 86400); // 24h
The trick is to find a good key, to add your report to the cache, and then be able to retrieve it. It has to be unique. Here, I assume that you have a user that is logged in, and I use the user ID to create a key. If that is not the case, you could create yourself a token, and pass it when rendering the page (along with $invites). This way, you can add the token to the URL to generate the CSV.
Then, in your second action, where you download the CSV, you just fetch the cached data:
$memcache = new \Memcache();
$memcache->connect($yourServerHost, $yourServerPort);
$json = $memcache->get($key);
$invites = json_decode($json , true);
That's it. You should probably create yourself a service for Memcache, so you don't have to create an object, and connect everytime.

Resources