What is best practice to include another info in form from another entity?
I have Student Entity, Group Entity, StudentsGroup and Attendance Entity.
Student have id, code, and name.
Group have id and name.
StudentsGroup have id, group_id, and student_id.
Attendance have id, students_group_id, date and status.
Group can have many Student which saved in StudentsGroup. Why I make StudentsGroup? Because actually 1 Group can have some sub groups like SubjectsGroup etc. And Attendance save student information by StudentsGroup Id which same student can have different students_group_id.
Now, the problem is : How to show Student information in collection form of attendance?
All Entity relationship is declared as object, so actually we can access em freely from any entity. But I don't know how to do that in form. Here my form :
<?php
/* Collection Form */
$tanggal = new \DateTime($request->request->get('sifo_adminbundle_studentsgrouping')['tanggal']);
$attendances = new StudentsGrouping();
foreach ($entities as $temp) {
$entity = new Attendance();
$entity = $em->getRepository('SifoAdminBundle:Attendance')->findOneBy(array('studentsGrouping' => $temp, 'date' => $tanggal));
if ($entity){
$attendances->getAttendances()->add($entity);
}
}
$form = $this->createCollectionForm($attendances, $id, $tanggal);
return $this->render('SifoAdminBundle:DftAbsensi:manage.html.twig', array(
'form' => $form->createView(),
));
This is how I render it in twig :
{{ form_start(form_collection) }}
{{ form_row(form_collection.tanggal) }}
{% for attendance in form_collection.attendances %}
{{ form_row(attendance.status) }}
{% endfor %}
{{ form_end(form_collection) }}
## Concept ##
I'm thinking about creating entity and pass it into form like this :
foreach ($entities as $temp) {
$entity = new Student();
$entity = $em->getRepository('SifoAdminBundle:Student')->find($temp->getId());
if ($entity){
$entities[i] = $entity;
}
$i++
}
and then in twig show it like this :
{{ form_start(form_collection) }}
{{ form_row(form_collection.tanggal) }}
{% for key, attendance in form_collection.attendances %}
{{ entities[key].code }}
{{ entities[key].name }}
{{ form_row(attendance.status) }}
{% endfor %}
{{ form_end(form_collection) }}
But I feel not comfort with this. Am I really need to make new entity just for showing name and code from Student Entity? Is there a best practice to do this?
You can use entity field.
$builder->add('users', 'entity', array(
'class' => 'AcmeHelloBundle:User',
'multiple' => true, /* you can choose more than one */
'mapped' => false, /* if you are using the form with an entity */
'query_builder' => function(EntityRepository $er) {
/* use query builder to get correct results */
return $er->createQueryBuilder('u')->orderBy('u.username', 'ASC');
},
));
There are two important keys in above.
'multiple' => true ----- you can able to choose more than one
'mapped' => false ----- if you are using form with an entity, your form will automatically looks for a connection between these entities and if can not found, throws exception. to avoid of that problem you should set this option to false
http://symfony.com/doc/current/reference/forms/types/entity.html
Related
I'm running Symfony 3.4 LTS and I have an entity Attribute:
<?php
class Attribute
{
private $id;
private $code;
private $value;
private $active;
// My getters and setters
Below the database table:
I would like to get, in a form, all the rows with code == 'productstatus'. I tried:
<?php
$attributes = $this->getDoctrine()->getRepository(Attribute::class)->findBy(['code' => 'productstatus']);
// $attributes contains array with 3 objects 'Attribute'
$form = $this->createFormBuilder($attributes);
$form->add('value', TextType::class);
$output = $form->getForm()->createView();
If I dump() the $output var in Twig:
... I'm unable to make a loop to display the 3 fields with values.
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
Result :
My goal is to allow the user to edit all the values of a specific attributes in the same form (or multiple forms, but in the same page). I already tried with CollectionType without success.
I found a solution: create 2 nested forms and use CollectionType. Hope it helps.
<?php
// Controller
$attributes = $this->getDoctrine()->getRepository(Attribute::class)->findBy(['code' => 'productstatus']);
$form = $this->createForm(AttributeForm::class, ['attributes' => $attributes]);
// AttributeForm
$builder
->add('attributes', CollectionType::class, [
'entry_type' => AttributeValueForm::class,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
]);
// AttributeValueForm
$builder
->add('value', TextType::class, ['label' => false, 'required' => false])
->add('active', CheckboxType::class, ['label' => false, 'required' => false])
// Twig view
{{ form_start(form) }}
{% for attribute in form.attributes.children %}
<tr>
<td>{{ form_widget(attribute.value) }}</td>
<td>{{ form_widget(attribute.active) }}</td>
</tr>
{% endfor %}
{{ form_end(form) }}
You an use an if statement in the your AttributeType like in the example below:
$builder
->add('entree', EntityType::class, array('class'=>'cantineBundle:plat',
'choice_label'=>function($plat){
if($plat->getType()=='Entree'&&$plat->getStatus()=="non reserve"){
return $plat->getNomPlat();
}
},
'multiple'=>false)
);
I have a collection of forms like in the code below, which leads to a form that looks like the following (with two example entries):
When clicking submit, the data i provided to the form (builder) instance is updated accordingly.
Problem: The problem i have is, that the list can be quite long, so i need a way of knowing which instances have been updated.
I thought about storing a clone of the original data (here $leadPartnerList in my session. But that does not feel right.
Does symfony (specifically form builder) provide such functionality out of the box? Or what would be an efficient solution to know which fields in the form have been updated and which not?
My Twig:
{% block content %}
<div>
{{ form_start(form) }}
{% for partner in lead_partners %}
{{ form_row(partner.name) }}
{% endfor %}
{{ form_end(form) }}
</div>
{% endblock content %}
My Controller Code:
public function overview(Request $request, \App\Utility\LeadPartnerLoader $LeadPartnerLoader)
{
$leadPartnerList = $leadPartnerLoader->loadAll();
$formBuilderData = [
'lead_partners' => $leadPartnerList
];
$listForm = $formFactory->createNamedBuilder('listForm', FormType::class, $formBuilderData)
->add('lead_partners', CollectionType::class, [
'entry_type' => LeadPartnerFormType::class,
'allow_add' => true
])
->add('submit', SubmitType::class, [
'label' => 'Submit Changes'
])
->getForm();
... handleRequest and so on and so forth...
}
And the Form Type (LeadPartnerFormType):
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => LeadPartner::class,
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class);
}
$leadPartnerList is of type array with LeadPartner instances within each entry of the array.
PLEASE NOTE: I'm not using Doctrine here!
Use symfony EventListener or EventSubscriber. See: https://symfony.com/doc/current/doctrine/event_listeners_subscribers.html
I am trying to pull in a repository into a form in Symfony 3.4 and then use the chosen option when the form is submitted.
Here's the form code:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('text', TextareaType::class, [
'label' => 'Text'
])
->add('category', EntityType::class, [
'class' => Category::class,
'choice_label' => 'name',
'query_builder' => function(CategoryRepository $repo) {
return $repo->createQueryBuilder('c')
->groupBy('c.name');
}
])
->add('subcategory', EntityType::class, [
'class' => Category::class,
'choice_label' => 'subcategory',
'query_builder' => function(CategoryRepository $repo) {
return $repo->createQueryBuilder('c')
->groupBy('c.subcategory');
}
]);
}
With this I can render the form and it looks good. I can choose the various options in the CategoryRepository.
{% block body %}
{{ form_start(form) }}
{{ form_label(form.name) }}
{{ form_errors(form.name) }}
{{ form_widget(form.name) }}
{{ form_label(form.subcategory) }}
{{ form_errors(form.subcategory) }}
{{ form_widget(form.subcategory) }}
{{ form_end(form) }}
On submit, when checking with Xdebug, the category is the object Category. I can see the correct values present (those chosen in the drop down of the form) but I want just the string, e.g. category.name. How do I do that?
Also, it might need a different question, but when I select one of the categories, I'd like the subcategory to be updated to exclude those that don't belong to that chosen category. I realise this may require jquery.
I'm certain there are better ways, but one solution would be to
$category = $data->getCategory();
$data->setCategory($category->getName());
$data->setSubcategory($category->getSubcategory());
And to exclude the subcategories I use jQuery / JavaScript to retrieve the filtered results from a controller / repository, then remove or add those options in the HTML.
I have overridden the Sensio Generator Bundle for CRUD in order to better suit my needs.
What I would like to do is to be able to loop through the entity fields.
It is done by default in show.html.twig but not in new and edit views.
When I implement the same logic in new.html.twig.twig it doesn't work though it does for edit.html.twig.twig.
{#app/Resources/SensioGeneratorBundle/skeleton/crud/views/new.html.twig.twig#}
{% for field, metadata in fields %}
{% if field not in 'id' %}
{{ '{{ form_row(edit_form.' ~ field ~ ')}}' }}
{% endif %}
{% endfor %}
When running the generator, the error is: Variable "fields" does not exist in "crud/views/new.html.twig.twig" at line 9
Ok, in fact it is an issue in Sensio Generator Bundle.
In the file: sensio\generator-bundle\Sensio\Bundle\GeneratorBundle\Generator\DoctrineCrudGenerator.php the generateNewView function is missing a paramter. It is not passing the fields as opposed to generateShowView.
Here is the comparison:
protected function generateNewView($dir)
{
$this->renderFile('crud/views/new.html.twig.twig', $dir.'/new.html.twig', array(
'bundle' => $this->bundle->getName(),
'entity' => $this->entity,
'route_prefix' => $this->routePrefix,
'route_name_prefix' => $this->routeNamePrefix,
'actions' => $this->actions,
));
}
versus
protected function generateShowView($dir)
{
$this->renderFile('crud/views/show.html.twig.twig', $dir.'/show.html.twig', array(
'bundle' => $this->bundle->getName(),
'entity' => $this->entity,
'fields' => $this->metadata->fieldMappings,
'actions' => $this->actions,
'route_prefix' => $this->routePrefix,
'route_name_prefix' => $this->routeNamePrefix,
));
}
I'll try to post this as an improvement.
There is the following code
$form->with('Item')->add('parent', null, array(
'label' => 'Category',
'required' => true,
'query_builder' =>
function($er) use ($id) {
$qb = $er->createQueryBuilder('p');
if ($id){
$qb->where('p.id <> :id')
->setParameter('id', $id);
}
$qb->orderBy('p.root, p.lft', 'ASC');
return $qb;
}
.........
Result is the entity-objects collection which is given to the string (__toString method).
It return name-field.
But I need get the another field - url.
How to get url value instead the name in the select-list form?
The query_builder type return object => how to change this form that it works likewise query_builder?
I didn't work with SonataAdminBundle forms, but I think that it works absolutely like symfony forms. All that you need here is to add 'class' and 'property' values to your options list:
$form->with('Item')->add('parent', null, array(
'class' => 'Acme\DemoBundle\Entity\Category',
'property' => 'url',
'label' => 'Category',
'required' => true,
'query_builder' =>
function($er) use ($id) {
$qb = $er->createQueryBuilder('p');
if ($id){
$qb->where('p.id <> :id')
->setParameter('id', $id);
}
$qb->orderBy('p.root, p.lft', 'ASC');
return $qb;
}
property - is the name of the field in your entity that will represent your Entity's value instead of calling __toString().
But also... If you need always represent your Entity as URL you can simply override __toString() method in the Entity class to something like that:
public function __toString() {
return $this->url;
}
In my case, ->orderBy() used in query_builder worked, but was overwritten for unknown reasons "somewhere" later. Fun fact: when I used extended => true, everything was rendered like sorted in the query_builder. But by using extended => false my <option>s are re-sorted before select2 touched it.
As a workaround I did this:
config/packages/twig.yaml
twig:
paths:
'%kernel.project_dir%/templates': '%kernel.project_dir%/templates'
# This is to prevent a infinite loop when extending the parent template
'vendor/sonata-project/admin-bundle/src/Resources/views': SonataAdminBundleOriginal
And then I done the sorting I wanted again in twig for the project entity:
templates/bundles/SonataAdminBundle/Form/form_admin_fields.html.twig:
{% extends '#SonataAdminBundleOriginal/Form/form_admin_fields.html.twig' %}
{%- block choice_widget_options -%}
{% if name == 'project' %}
{% set options = options|sort((a, b) => a.data.numberSortable <=> b.data.numberSortable)|reverse %}
{% endif %}
{{ parent() }}
{%- endblock choice_widget_options -%}
Just in case if someone needs to "filter" an entity by a FK (foreign key) just like i needed (and searched for 3 days), here is the solution:
In the __construct you can set/find what you need. In my case i work this "sessions" (year). So i find the session->status=true:
$this->sessionActive = $this->sessionRepository->findOneBy(['status'=>true]);
Then in the QueryBuilder:
protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
{
$rootAlias = current($query->getRootAliases());
$query
->select($rootAlias)
->where($rootAlias.'.session = :id')
->addOrderBy($rootAlias.'.dateStart', 'ASC')
->setParameter('id', $this->sessionActive->getId());
return $query;
}
Note: I have always one Session->status set to "true". If you might have null values don't forget to make a condition so that it doesn't give you an error!
Regards.