Form edit with ArrayCollection - php

Quick explanation of the subject:
A "Project" can have several "Activities"
I had to create simultaneously as many "Activities" as needed (You click on a button "Add an activity" and you got a new line which means a new activity). I managed to do that by doing Collections on my fields.
Here's the code of the formBuilder :
namespace CoreBundle\Form\Type;
use CoreBundle\Entity\Activities;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CreateNewActivitiesType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'aeAmount',
CollectionType::class,
array(
'entry_type' => TextType::class,
'allow_add' => true,
'allow_delete' => true,
'label' => 'Montant de l\'AE',
'label_attr' => array(
'class' => 'aeAmount visuallyhidden'
),
'entry_options' => array(
'attr' => array(
'placeholder' => '80 000 €'
),
),
'required' => true
)
)
->add(
'amountSpent',
CollectionType::class,
array(
'entry_type' => TextType::class,
'allow_add' => true,
'allow_delete' => true,
'label' => 'RDP : ',
'label_attr' => array(
'class' => 'amountSpent visuallyhidden'
),
'entry_options' => array(
'attr' => array(
'placeholder' => '35 000 €'
)
),
'required' => true,
)
)
->add(
'afName',
CollectionType::class,
array(
'entry_type' => TextType::class,
'allow_add' => true,
'allow_delete' => true,
'required' => true,
'label' => 'AF : ',
'label_attr' => array(
'class' => 'afName visuallyhidden',
),
'entry_options' => array(
'attr' => array(
'placeholder' => 'AERT-496'
)
)
)
)
->add(
'year',
CollectionType::class,
array(
'entry_type' => TextType::class,
'allow_delete' => true,
'allow_add' => true,
'required' => true,
'entry_options' => array(
'attr' => array(
'readonly' => true
)
),
'label' => 'Année : ',
'label_attr' => array(
'class' => 'year visuallyhidden'
)
)
)
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'CoreBundle\Entity\Activities'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'corebundle_collection_activities';
}
}
(I know i can do this much shorter, but refactorisation will be later.)
So, this is currently working, with his associated controller to add my activities, here it is :
/**
* #param integer $projectId
* #param string $projectType
* #param integer $rdId
*
* #return \Symfony\Component\HttpFoundation\Response
*/
public function createNewActivityAction($projectId, $projectType, $rdId)
{
$activity = new Activities();
$request = $this->get('request_stack')->getCurrentRequest();
$form = $this->createForm(CreateNewActivitiesType::class, $activity);
$form->handleRequest($request);
$em = $this->getDoctrine()->getManager();
if ($form->isSubmitted() && $form->isValid()) {
$rdAffiliated = $em->getRepository('CoreBundle:DecisionStatement')->findOneBy(['id' => $rdId]);
$formData = $form->getData();
$yearList = $formData->getYear();
$aeAmountList = $formData->getAeAmount();
$afNameList = $formData->getAfName();
$amountSpentList = $formData->getAmountSpent();
for ($i = 0; $i < count($yearList); $i++) {
$yearDatetime = new DateTime($yearList[$i] . '-01-01 00:00:00');
$existingActivity = $em->getRepository('CoreBundle:Activities')->getExistingActivityWithoutIdentifier(
$yearDatetime,
$rdAffiliated
);
if ($existingActivity) {
/**
* #var Activities $currentActivity
*/
$currentActivity = $existingActivity[0];
$currentActivity->setAeAmount(
$currentActivity->getAeAmount() + $aeAmountList[$i]
);
$currentActivity->setAmountSpent(
$currentActivity->getAmountSpent() + $amountSpentList[$i]
);
$em->persist($currentActivity);
} else {
$newActivity = new Activities();
$newActivity->setYear($yearDatetime)
->setAeAmount($aeAmountList[$i])
->setAfName($afNameList[$i])
->setAmountSpent($amountSpentList[$i])
->setAfReception(false)
->setDecisionStatement($rdAffiliated)
->setPeopleByMonth(0);
$em->persist($newActivity);
}
}
$em->flush();
return $this->redirectToRoute('rd_show_activities', array(
'rdId' => $rdId,
'projectType' => $projectType,
'projectId' => $projectId
));
}
return $this->render('#Core/html/13-edit-activite.html.twig', array(
'page' => 'activities_creation',
'createActivitiesForm' => $form->createView(),
'projectParentId' => $projectId,
'projectParentType' => $projectType,
'rdId' => $rdId
));
}
Here is also a screenshot from the var_dump when activities form is submitted :
But where's my problem begins, it is when I want to edit because my form is based on the entity "Activities". But I want to edit all the existing "Activities" for a given project, I'll have an array containing my "Activities" objects (found by the findBy method), so I can't pass my array into my form, which results in an error.
How to transform this array of many "Activities" objects into only one "Activities" object?

I don't understand why you have only one object "Activities" that contains several collection.
You should create an object activities that contains several activities.
Each activity has only one attribute "year", "amountSpent"...
When you edit your object "ActivitieS" with the appropriate form you will be able to edit each activity linked with this object.

Embed a collection of forms
To correctly manage this kind of problem you need to build a Collection of forms in Symfony.
A "Project" can have several "Activities"
I had to create simultaneously as many "Activities" as needed [...]
At first you need to build a Project entity that will contain all the Activities as a Doctrine ArrayCollection as you can see below:
src/AppBundle/Entity/Project.php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
class Project
{
protected $activities;
public function __construct()
{
$this->activities = new ArrayCollection();
}
public function getTags()
{
return $this->tags;
}
}
src/AppBundle/Form/Type/ProjectType.php
namespace AppBundle\Form\Type;
use AppBundle\Entity\Project;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class ProjectType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('tags', CollectionType::class, array(
'entry_type' => ActivitiesType::class,
'allow_add' => true,
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Project::class,
));
}
}
Then you need to enable the user to dynamically add an Activity to a Project you need to make advantage of the prototype variable, using Twig and JavaScript.

I've taken into consideration all the comments that you made and decided to review the whole thing.
DecisionStatement.php :
Added in the __construct() method a new ArrayCollection for the Activities affiliated for a given DecisionStatement.
The method to return the Activities affiliated was already here.
documents = new ArrayCollection();
$this->activitiesAffiliated = new ArrayCollection();
}
/**
* Get activitiesAffiliated
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getActivitiesAffiliated()
{
return $this->activitiesAffiliated;
}
I've created a new FormType (DecisionStatementType.php), to have my Collection of forms :
use CoreBundle\Entity\DecisionStatement;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DecisionStatementType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'activitiesAffiliated',
CollectionType::class,
array(
'entry_type' => NewActivityType::class,
'allow_add' => true,
'allow_delete' => true
)
)
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => DecisionStatement::class
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'corebundle_collection_decisionstatement';
}
}
And here is the "subForm", newActivityType.php :
use CoreBundle\Entity\Activities;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class NewActivityType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'aeAmount',
TextType::class,
array(
'label' => 'Montant de l\'AE',
'label_attr' => array(
'class' => 'aeAmount visuallyhidden'
),
'attr' => array(
'placeholder' => '80 000 €'
),
'required' => true
)
)
->add(
'amountSpent',
TextType::class,
array(
'label' => 'RDP : ',
'label_attr' => array(
'class' => 'amountSpent visuallyhidden'
),
'attr' => array(
'placeholder' => '35 000 €'
),
'required' => true,
)
)
->add(
'afName',
TextType::class,
array(
'required' => true,
'label' => 'AF : ',
'label_attr' => array(
'class' => 'afName visuallyhidden',
),
'attr' => array(
'placeholder' => 'AERT-496'
)
)
)
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'CoreBundle\Entity\Activities'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'corebundle_collection_activities';
}
}
In my view's DOM, i have now this :
FormCollection Data-Prototype in DOM
So, is this a bit cleaner ?

Related

Pre-filling a field of a Symfony form of type EntityType

I'm working on a Symfony project and I'm currently encountering a problem with a form that I want to pre-fill from a collection of objects.
The form field in question is of type EntityType::class. I would like to pre-select elements of this field from a collection that contains objects of the same type(Classe).
One of the solutions I found is to add a 'data' => $defaultClass property in the buildForm, which would contain the data to be inserted, and to pass this object in the parameters($options) on the formBuilder.
Unfortunately, the two objects of type Classe do not appear in the field once the form is generated even though the $options variable contains the objects.
Thanks in advance for your help. Here are the codes concerned :
SearchCourseData
<?php
namespace App\Data;
use App\Entity\Classe;
use App\Entity\Teacher;
use App\Entity\Location;
class SearchCourseData
{
/**
* #var integer
*/
public $page = 1;
/**
* #var Classe[]
*/
public $classe = [];
// Missing lines
/**
* #var String
*/
public $status;
}
Code SearchCourseForm
class SearchCourseForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$defaultClass = new Classe();
if (!empty($options['data']->classe)) {
$defaultClass = $options["data"]->classe;
}
$builder
->add('classe', EntityType::class, [
'class' => Classe::class,
'label' => false,
'required' => false,
'expanded' => false,
'multiple' => true,
'query_builder' => function (ClasseRepository $qb) {
return $qb->createQueryBuilder('a')->orderBy('a.title', 'ASC');
},
'choice_label' => function (Classe $atelier) {
return($atelier->getTitle());
},
'attr' => [
'placeholder' => 'Atelier',
'class' => 'select-classes'
],
'data' => $defaultClass,
])
>add('status', ChoiceType::class, [
'required' => true,
'choices' => [
'Disponible' => "Disponible",
'Brouillon' => "Brouillon",
'Archivé' => "Archivé"
],
'label' => false,
])
;
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => SearchCourseData::class,
'method' => 'GET',
'csrf_protection' => false
]);
}
public function getBlockPrefix() {
return '';
}
}
Code CourseController
/**
* #Route("/course")
*/
class CourseController extends AbstractController {
/**
* #Route("/",name="course")
*/
public function courseList(CourseRepository $courseRepository, Request $request) {
$data = new SearchCourseData();
$defaultClassB = $this->getDoctrine()->getRepository(Classe::class)->find(49);
$defaultClassA = $this->getDoctrine()->getRepository(Classe::class)->find(1);
$defaultClass[] = new ArrayCollection();
$defaultClass[0] = $defaultClassA;
$defaultClass[1] = $defaultClassB;
$data->classe = $defaultClass;
$form = $this->createForm(SearchCourseForm::class, $data);
}
}
#V-light is right you are setting a collection in an index of an array - then overwrite it...
$defaultClass[] = new ArrayCollection();
// === $defaultClass[0] = new ArrayCollection();
the correct and easiest way would be:
/**
* #Route("/course")
*/
class CourseController extends AbstractController {
/**
* #Route("/",name="course")
*/
public function courseList(CourseRepository $courseRepository, Request $request) {
$data = new SearchCourseData();
$data->classe = $this->getDoctrine()->getRepository(Classe::class)->findById([49, 1]);
$form = $this->createForm(SearchCourseForm::class, $data);
}
}
EDIT - the corrected form:
'data' of classe gets set by the ModelTransformer automaticly.
class SearchCourseForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('classe', EntityType::class, [
'class' => Classe::class,
'label' => false,
'required' => false,
'expanded' => false,
'multiple' => true,
'query_builder' => function (ClasseRepository $qb) {
return $qb->createQueryBuilder('a')->orderBy('a.title', 'ASC');
},
'choice_label' => function (Classe $atelier) {
return($atelier->getTitle());
},
'attr' => [
'placeholder' => 'Atelier',
'class' => 'select-classes'
],
])
>add('status', ChoiceType::class, [
'required' => true,
'choices' => [
'Disponible' => "Disponible",
'Brouillon' => "Brouillon",
'Archivé' => "Archivé"
],
'label' => false,
])
;
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => SearchCourseData::class,
'method' => 'GET',
'csrf_protection' => false
]);
}
}

IS THERE A WAY TO CHECK VALUE IN FORM SYMFONY 2.5

I have an embedded form (oneTo Many relationship).
1 - Entity request
2 - Entity products of the request
I am looking for adapt the form.
I want form can dynamic modify the value when i check the value of the price of this object.
The best is to check the value in the form and compare but I don't know which method can works for my issue.
I would like to do this check in the form "ProductRequestType".
Exemple of my form ProductRequestType :
<?php
namespace \testBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\EntityManager;
class testRequestProductType extends AbstractType {
/** #var \Doctrine\ORM\EntityManager */
private $em;
/**
* Constructor
*
* #param Doctrine $doctrine
*/
public function __construct($em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$disabled_exchange = $options['attr']['disabled_exchange'];
$customer_id = $options['attr']['customer_id'];
$builder->add('id', 'hidden');
$builder->add('part_number');
$builder->add('_number');
$builder->add('source_quantity');
$builder->add('quantity');
$builder->add('serial');
$builder->add('buy_date', 'date', array('required' => false,'widget' => 'single_text', 'fotestt' =>'dd/MM/yy'));
$builder->add('return_date', 'date', array('required' => false,'widget' => 'single_text', 'fotestt' =>'dd/MM/yy'));
$builder->add('is_new', null, array('required' => false));
$builder->add('test_comment');
$builder->add('available_stock', 'integer', array('mapped' => false));
$builder->add('buying_price', 'integer', array('mapped' => false));
$builder->add('test_reason', 'entity',
array(
'class' => 'testBundle:testReason',
'property' => 'name',
'expanded' => false,
'multiple' => false
)
);
//I would like to get the price and if the price < 100 the variable $disabled_exchange = true
if($disabled_exchange){
$builder->add('test_action', 'entity',
array(
'class' => 'testBundle:testAction',
'property' => 'label',
'expanded' => false,
'multiple' => false,
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('u')
->where('u.name != :action')
->setParameter('action', 'exchange');
}
)
);
}else{
$builder->add('test_action', 'entity',
array(
'class' => 'testBundle:testAction',
'property' => 'label',
'expanded' => false,
'multiple' => false
)
);
}
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => '\testBundle\Entity\testRequestProduct',
'disabled_exchange' => false
));
}
public function getName()
{
return 'test_request_product';
}
}
Thank you for your advices.
You can do this by using form event listeners
symfony event doc

Symfony2 Form Events Listener and Data Transformer Error Cannot instantiate interface Doctrine\....\ObjectManager

I'm working on a Symfony 2.3 form which require that a field be only available on "create new" mode.
The field is a link in a Many-to-One relationship, i've managed to change the dropdown list by using a jQuery AutoComplete for ease of use via a FormEvents::PRE_SET_DATA event ,however the FormEvents::PRE_SUBMIT which is to turn the ID supplied into an object require a Doctrine ObjectManager.
This turn to be an Interface and throw the 'Cannot instantiate interface Doctrine\Common\Persistence\ObjectManager' .
I've looked at the following similar questions and documentations (Form Events , Form Events Dropdown, Doctrine Data Transformer, Dynamic Form Modification) but being so close to the end(And a newbie to both php and symfony) i wish no to re-write this piece.
May you find below my Event Listener and Data Transformer classes:
namespace XXXXXXXBundle\Form\EventListener;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use XXXXXXXXXXXXXBundle\Form\DataTransformer\rneToEcoleTransformer;
class AddFieldSubscriber implements EventSubscriberInterface {
private $manager;
public static function getSubscribedEvents() {
// Tells the dispatcher that you want to listen on the form.pre_set_data
// event and that the preSetData method should be called.
return array(\Symfony\Component\Form\FormEvents::PRE_SET_DATA => 'preSetData',
\Symfony\Component\Form\FormEvents::PRE_SUBMIT => 'preSubmit');
}
public function preSetData(\Symfony\Component\Form\FormEvent $event) {
$parents = $event->getData();
$form = $event->getForm();
//Verifie si le parent d'eleve est un nouveau
//ou s'il s'agit d' une mise à jour
if (!$parents || null === $parents->getId()) {
$form->add('ecole', 'text', array('invalid_message' => 'Ce RNE n\'est pas valide',
'attr' => array('data-id' => 'ecoles',
'data-url' => 'parents_json_ecoles'
)
)
);
}
}
public function preSubmit(\Symfony\Component\Form\FormEvent $event) {
$this->manager = new \Doctrine\Common\Persistence\ObjectManager;
$parents = $event->getData();
if (empty($parents['ecole'])) {
return;
}
$event->getForm()->get('ecole')->getConfig()->addModelTransformer(new rneToEcoleTransformer($this->manager));
}}
namespace XXXXXXXBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class rneToEcoleTransformer implements DataTransformerInterface {
private $manager;
public function __construct(\Doctrine\Common\Persistence\ObjectManager $manager) {
$this->manager = $manager;
}
public function transform($ecole) {
if (null === $ecole) {
return '';
}
return $ecole->getRne();
}
public function reverseTransform($rneNumber) {
if (!$rneNumber) {
return;
}
$ecole = $this->manager
->getRepository('XXXXXXXBundle:Ecole')
->find($rneNumber);
if (null === $ecole) {
throw new TransformationFailedException(sprintf(
'No matching data was found "%s" !', $rneNumber
));
}
return $ecole;
}}
Here is the action used :
private function createCreateForm(Parents $entity) {
$manager = $this->getDoctrine()->getManager();
$form = $this->createForm(new ParentsType($manager), $entity, array(
'action' => $this->generateUrl('parents_create'),
'method' => 'POST',
));
$form->add('submit', 'submit', array('label' => 'Add'));
return $form;
}
The Formtype :
class XXXXType extends AbstractType {
private $manager;
public function __construct(\Doctrine\Common\Persistence\ObjectManager $manager) {
$this->manager = $manager;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('nom', 'text', array('label' => 'Nom *',
'attr' => array('placeholder' => 'Nom ',
'maxlength' => 250,
'trim' => true)
)
)
->add('prenoms', 'text', array('label' => 'Prénom(s) *',
'attr' => array('placeholder' => 'Prénom(s)',
'maxlength' => 250,
'trim' => true)
)
)
->add('date_de_naiss', 'date', array('widget' => 'single_text', 'format' => 'dd-MM-yyyy',
'label' => 'Date de naissance ',
'required' => false, 'data' => null,
'attr' => array('name' => 'datepicker')
)
)
->add('telephone', 'text', array('label' => 'Telephone *',
'attr' => array('placeholder' => '123-456-7890 or 1234567890')
)
)
->add('email', 'email', array('attr' => array('placeholder' => 'xxxx#xxxx.com',),
'trim' => true,
'required' => false,
'data' => null
)
);
$builder->addEventSubscriber(new AddFieldSubscriber($manager));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'XXXXXXBundle\Entity\Parents'
));
}
/**
* #return string
*/
public function getName() {
return 'XXXXXXXXXsbundle_parents';
}
}
Try this:
class AddFieldSubscriber implements EventSubscriberInterface {
private $manager;
public function __constructor(ObjectManager $manager){
$this->manager = $manager;
}
.
.
.
}
And in your service.yml definition:
<service_name>:
class: XXXXXXXBundle\Form\EventListener\AddFieldSubscriber
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: kernel.event_subscriber }
If you are going to use only one repository from the ObjectManager I would suggest to Inject only this specific repository.
In this case your class would change to:
class AddFieldSubscriber implements EventSubscriberInterface {
private $ecoleRepository;
public function __constructor(EntityRepository $ecoleRepository){
$this->ecoleRepository = $ecoleRepository;
}
.
.
.
}
And in your service.yml:
xxx.ecole.repository:
class: Doctrine\ORM\EntityRepository
factory_service: doctrine.orm.default_entity_manager
factory_method: getRepository
arguments:
- XXXXXXXBundle\Entity\Ecole
<service_name>:
class: XXXXXXXBundle\Form\EventListener\AddFieldSubscriber
arguments: ["#xxx.ecole.repository"]
tags:
- { name: kernel.event_subscriber }
I finally managed to make it work by using the data transformer instead of the event subscriber as follow:
`
class ParentsType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('nom', 'text', array('label' => 'Nom *',
'attr' => array('placeholder' => 'Nom du Parent d\'élève',
'maxlength' => 250,
'trim' => true)
)
)
->add('prenoms', 'text', array('label' => 'Prénom(s) *',
'attr' => array('placeholder' => 'Prénom(s) du parent d\' élève',
'maxlength' => 250,
'trim' => true)
)
)
->add('date_de_naiss', 'date', array('widget' => 'single_text', 'format' => 'dd-MM-yyyy',
'label' => 'Date de naissance ',
'required' => false, 'data' => null,
'attr' => array('name' => 'datepicker')
)
)
->add('telephone', 'text', array('label' => 'Telephone *',
'attr' => array('placeholder' => '123-456-7890 or 1234567890')
)
)
->add('email', 'email', array('attr' => array('placeholder' => 'xxxx#xxxx.com',),
'trim' => true,
'required' => false,
'data' => null
)
);
$entityManager = $options['em'];
$transformer = new idToEcoleTransformer($entityManager);
$builder->add(
$builder->create('ecole','text',array('required' => true,
'attr' => array( 'data-id'=>'ecoles',
'data-url'=>'parents_json_ecoles',)
)
)->addModelTransformer($transformer)
);
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'XXXXXXXXBundle\Entity\Parents',
'csrf_protection' => false,
'validation_groups' => array('filtering')
));
$resolver->setRequired(array('em'));
$resolver->setAllowedTypes(array('em'=>'Doctrine\Common\Persistence\ObjectManager',));
}
/**
* #return string
*/
public function getName() {
return 'parentsbundle_parents';
}
`
With this i don't need to pass the ObjectManager via the constructor , but just to use the Resolver

Symfony2 custom handle of fields

I am developing my first complex form with Symfony 2 and I have gotten stuck.
This image represents my situation
So, I have a very simple entity called TShirt with the following attributes:
name -> mapped as a string column in the database
tags_data -> mapped as a string column in the database (that will contain a JSON object)
Then I have a form with more fields than the entity has:
class TShirtType extends AbstractType {
public function buildForm( FormBuilderInterface $builder, array $options ) {
$builder
->add('name', 'text', array(
'required' => true,
))
->add( 'colours', 'choice', array(
'choices' => $availableColours,
'multiple' => true,
'expanded' => true,
'mapped' => false,
))
->add( 'sizes', 'choice', array(
'choices' => $availableSizes,
'multiple' => true,
'expanded' => true,
'mapped' => false,
))
->add( 'materials', 'choice', array(
'choices' => $availableMaterials,
'multiple' => true,
'expanded' => true,
'mapped' => false,
));
}
/**
* {#inheritdoc}
*/
public function setDefaultOptions( OptionsResolverInterface $resolver ) {
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\TShirt',
));
}
public function getName() {
return 'app_bundle_tshirt_form';
}
}
I have read about Data Transformers but as far as I know they cannot help me in this issue because they work with mapped fields.
Giving this scenario, I want to add something like the Data Transformers that takes the colours, sizes and materials form fields and builds the JSON object to store it in the Entity.
So my question is: Is there a way for custom handling the non-mapped fields in the form class?
Any help would be very appreciated! Thanks!!
Maybe you could use an EventListener in your FormType like below :
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$formFactory = $builder->getFormFactory();
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (\Symfony\Component\Form\FormEvent $event) use ($formFactory, $builder) {
$data = $event->getData();
$form->add(
'customFieldName',
'text',
array('some_options' => 'someOptionValue')
);
}
Be careful about FormEvents (here : PRE_SET_DATA).
Also, $data = $event->getData(); allows you to get the related object. You could loop on it and parse JSON (or create as much method as your JSON array contains and call them over and over) in order to display as much $form->add(.. as you have properties ion you JSON array.
it's not good solution to store it in tagsData !
solution for SYMFONY 2.7
Controller.php:
$colors = [
'white' => 'white',
'black' => 'black',
'green' => 'green'
];
$sizes = [
'M' => 'M',
'L' => 'L',
'XL' => 'XL'
];
$materials = [
'Cotton' => 'Cotton',
'Wool' => 'Wool',
'Silk' => 'Silk'
];
$object = new TShirt();
$form = $this->createForm(new TShirtType(), $object, ['colours' => $colors, 'sizes' => $sizes, 'materials' => $materials]);
$form->handleRequest($request);
TShirtType.php
class TShirtType extends AbstractResourceType
{
public function buildForm( FormBuilderInterface $builder, array $options ) {
$builder
->add('name', 'text', array(
'required' => true,
))
->add( 'colours', 'choice', array(
'choices' => $options['colours'],
'multiple' => true,
'expanded' => true,
'mapped' => false
))
->add( 'sizes', 'choice', array(
'choices' => $options['sizes'],
'multiple' => true,
'expanded' => true,
'mapped' => false
))
->add( 'materials', 'choice', array(
'choices' => $options['materials'],
'multiple' => true,
'expanded' => true,
'mapped' => false
));
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event)
{
$object = $event->getData();
$form = $event->getForm();
$tagsData = [
'colours' => $form->get('colours')->getData(),
'sizes' => $form->get('sizes')->getData(),
'materials' => $form->get('materials')->getData(),
];
$object->setTagsData($tagsData);
$event->setData($object);
});
}
public function finishView(FormView $view, FormInterface $form, array $options)
{
$tagsData = $view->vars['data']->getTagsData();
if($tagsData) {
$types = ['colours', 'sizes', 'materials'];
foreach($types as $type) {
foreach($view->offsetGet($type) as $checkbox) {
$checkbox->vars['checked'] = isset($tagsData[$type]) && in_array($checkbox->vars['value'], $tagsData[$type]);
}
}
}
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver ) {
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\TShirShirt',
));
$resolver->setRequired(['colours', 'sizes', 'materials']);
}
public function getName() {
return 'app_bundle_tshirt_form';
}
}
I finally solved the problem by creating a custom DataMapper as this tutorial explains
https://webmozart.io/blog/2015/09/09/value-objects-in-symfony-forms/

Collection of entities (and textarea) in Symfony2

I have a very simple form (ModuleType.php) calling a collection (ModuleControleType.php).
This code works perfectly (add, modify and delete : no problem), but I want to customize the query for find Description (I just want to add a Where) in ModuleControleType.php.
I can do a query_builder with a field of type " Entity ", but not with a field " Collection " type.
ModuleType.php :
class ModuleType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('point', 'textarea', array(
'label' => 'Point(s) à observer',
'required' => false,
'attr' => array(
'rows' => 5
)
))
->add('controles', 'collection', array(
'label' => false,
'type' => new ModuleControleType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
))
;
}
ModuleControleType.php :
class ModuleControleType extends AbstractType
{
use Description;
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', 'textarea', array(
'label' => 'Description',
'required' => false,
'attr' => array(
'rows' => $rows
)
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AdministrationBundle\Entity\ModuleControle',
'intention' => $this->getName() . '_token'
));
}
/**
* #return string
*/
public function getName()
{
return 'administration_module_controle_form';
}
}

Categories