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.
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.
I have a many to many entity relationship, User and Project
The User entity has only one field, the id field since it is extending the FOsUserBundle. I understand that to access the current logged in users by calling the container
$user = $this->container->get('security.context')->getToken()->getUser();
But that's not the case.A project has many users saved in a database.Now I can access it by using a twig loop
users_projects//a many to many table
project_id user_id
3 1
3 2
<td>{% for user in project.users %}{{ user.id}}{% endfor %}</td>
outputs 1,2
The user entity has no getUsername method as it has only one id field.How would you display the username instead of an i.d?
Update
Already solved this.I dont really sure if its the proper way .In ProjectType, I added this
->add('users', 'entity', array(
'class' => 'UserBundle:User',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.username', 'ASC');
},
'expanded' => true,
'multiple' => true
))
With this set up
<td>
{% for user in entity.users %}
{{ user.username }}
{% if not loop.last %},{% endif %}
{% endfor %}
</td>
Now works.Just don't forget to include
use Doctrine\ORM\EntityRepository;
In FormType, else it will throw an error something like
Catchable Fatal Error: Argument 1 passed to.....must be an instance of \EmployeeBundle\Form\EntityRepository, instance of Doctrine\ORM\EntityRepository given
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.
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>