I have a Doctrine extensions tree Entity which I want to put entirely (or only a node and all its children) in a form. That is, I want to be able to modify the entire (sub)tree in a single form. I have taken a look at “multiple rows in form for the same entity in symfony2,” however, I'm unable to apply it to a tree with all its children in Symfony3.
I was thinking of something as a controller like
$repository = $this->getDoctrine()->getRepository('AppBundle:Category');
$tree = $repository->children(null, true);
$form = $this->createForm(CategoryType::class, $tree);
and a CategoryType like
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Category::class /* or should it be `null`? */,
));
}
Use the following Controller:
public function editAction(Request $request)
{
$repository = $this->getDoctrine()->getRepository('AppBundle:Category');
$categories = $repository->children(null, false); // get the entire tree including all descendants
$form = $this->createFormBuilder(array('categories' => $categories));
$form->add('categories', CollectionType::class, array(
'entry_type' => CategoryType::class,
));
$form->add('edit', SubmitType::class);
$form = $form->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
// $data['categories'] contains an array of AppBundle\Entity\Category
// use it to persist the categories in a foreach loop
}
return $this->render(...)
}
The CategoryType is just like ‘normal,’ e.g., the one in my question.
It is key to create the form builder with array('categories' => $categories) and add a form CollectionType field with the name categories.
Related
In my code I need to download from database some data and put it to the form, but I don't know how to get doctrone outside Controller class.
I tried create new service, but it didn't work (I think I can't use in this case __controller(), am I right?). I tried also transfer instance of the controller to the parameters of buildForm() method but I got message: FatalErrorException: Compile Error: Declaration of MyBundle\Form\Type\TemplateType::buildForm() must be compatible with that of Symfony\Component\Form\FormTypeInterface::buildForm() ).
This is my code:
class TemplateType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name', 'text')
// ...
->add('description', 'textarea');
}
public function getName() {
return 'template';
}
}
How can I use inside buildForm() doctrine?
In order to send data from doctrine to your form, you need to do this into your controller:
public function doSomethingWithOneObjectAction( $id )
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository( 'AcmeBundle:ObjectEntity' )->find( $id );
if ( ! $entity) {
throw $this->createNotFoundException( 'Unable to find Object entity.' );
}
$form = $this->createForm(
new TemplateType(),
$entity
);
return array(
'entity' => $entity,
'form' => $form->createView()
);
}
If you want to access a service from container inside your form type, you need first to register it as an service and inject into it the services you need. Something like this
I want to create a dynamic form Photo with these fields:
title
album
Album is a checkboxes field ( with 'multiple' attr ).
Photo has a manyToOne relationship with Album.
What I want to do is persist several times the photo with different album values, not persist an arrayCollection of albums in one photo.
I tried to do
if ($request->getMethod() == 'POST') {
$form->bind($request);
if ($form->isValid()) {
$data = $form->getData();
$listeAlbum = $data['album'];
foreach ($listeAlbum as $album) {
$em->persist($photo);
$em->flush();
}
I get the error ( at the line bind->($request) )
Catchable Fatal Error: Argument 1 passed to MySite\TestBundle\Entity
\Photo::setAlbum() must be an instance of MySite\TestBundle\Entity\Album,
instance of Doctrine\Common\Collections\ArrayCollection given, ...
Is there a way to do this?
EDIT : more code.
my form type
class PhotoType extends AbstractType {
private $securityContext;
public function __construct(SecurityContext $securityContext)
{
$this->securityContext = $securityContext;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('file', 'file') // , array("attr" => array("multiple" => "multiple", ))
->add('titre');
$user = $this->securityContext->getToken()->getUser();
if (!$user) {
throw new \LogicException(
'The FriendMessageFormType cannot be used without an authenticated user!'
);
}
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($user) {
$form = $event->getForm();
$formOptions = array(
'multiple' => true, //several choices
'expanded' => true, // activate checkbox instead of list
'class' => 'EVeilleur\DefuntBundle\Entity\Album',
'property' => 'titre',
'query_builder' => function (EntityRepository $er) use ($user) {
// build a custom query
return $er->createQueryBuilder('u')->add('select', 'u')
->add('from', 'EVeilleurDefuntBundle:Album u');
// ->add('where', 'u.id = ?1')
// ->add('orderBy', 'u.name ASC');
},
);
// create the field, = $builder->add()
// field name, field type, data, options
$form->add('album', 'entity', $formOptions);
}
);
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'EVeilleur\DefuntBundle\Entity\Photo'
));
}
/**
* #return string
*/
public function getName()
{
return 'eveilleur_defuntbundle_photo';
} }
entity relation, in Photo.php
/**
* #ORM\ManyToOne(targetEntity="EVeilleur\DefuntBundle\Entity\Album")
* #ORM\JoinColumn(nullable=true)
*/
private $album;
I think the problem is that you are adding Album to your Photo form as a single entity field, but your options mean it gets translated into a set of checkboxes that is logically turned on submit by Symfony into an ArrayList. Your Photo entity expects only a single Album.
Could you post the rest of your Photo entity, specifically the setAlbum method (apologies if you don't actually need to create one in this case, I always create mine even if they're trivial!)?
If Photo is ManyToOne with Album, then many Photos can be in one Album, and the Album field should probably be a dropdown - you shouldn't be able to choose multiple Albums. This may be the root of the issue.
If many Photos can be in one Album, but also each Photo can be in many Albums, then you really have a ManyToMany relationship, doesn't seem to be modelled like that here.
I can't find how to update data in form collection to database, like in normal Edit action, the EditForm generated and pass to UpdateAction. I can make form for EditForm but can't find how to update data to database.
How to Embed a Collection of Forms is showing how to add and delete it using persist and remove but how to bind it from post data and update it into database? My collection actually just entity without table in database. It's used just for population many fields from my primary entity DftAbsensi into single form.
This is my primary entity DftAbsensi (without getter and setter):
<?php
namespace Sifo\AdminBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class DftAbsensi
{
private $id;
private $tanggal;
private $status;
This is the collection entity for Absensi :
<?php
namespace Sifo\AdminBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
class CollectionAbsensi
{
private $statusS;
private $tanggal;
public function __construct()
{
$this->tanggalS = new ArrayCollection();
$this->statusS = new ArrayCollection();
}
public function setTanggal($tanggal)
{
$this->tanggal = $tanggal;
return $this;
}
public function getTanggal()
{
return $this->tanggal;
}
public function setStatusS(ArrayCollection $statusS)
{
$this->statusS = $statusS;
return $this;
}
public function getStatusS()
{
return $this->statusS;
}
}
This is DftAbsensiType :
<?php
namespace Sifo\AdminBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class DftAbsensiType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', 'text', array('required' => false))
->add('status', 'choice', array(
'choices' => array('H' => 'Hadir', 'A' => 'Tanpa Keterangan', 'S' => 'Sakit', 'I' => 'Izin', 'L' => 'Libur'),
'required' => false,
'empty_value' => '- Pilih -'))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Sifo\AdminBundle\Entity\DftAbsensi'
));
}
public function getName()
{
return 'sifo_adminbundle_dftabsensi';
}
}
Actually I'm using collection just for populating many fields from databases. Persist database just in my primary entity DftAbsensi above. This is Collection for Absensi Type :
<?php
namespace Sifo\AdminBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CollectionAbsensiType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('tanggal', 'date', array('label' => false, 'required' => false, 'attr'=>array('style'=>'display:none;'), 'widget' => 'single_text', 'format' => 'yyyy-MM-dd'))
->add('statusS', 'collection', array(
'label' => false,
'options' => array('label' => false, 'required' => false),
'type' => new DftAbsensiType())
);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Sifo\AdminBundle\Entity\CollectionAbsensi'
));
}
public function getName()
{
return 'sifo_adminbundle_collectionabsensi';
}
}
This is how to population data in my controller. This form used for EditForm :
<?php
namespace Sifo\AdminBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sifo\AdminBundle\Entity\DftAbsensi;
use Sifo\AdminBundle\Entity\CollectionAbsensi;
use Sifo\AdminBundle\Form\CollectionAbsensiType;
use Sifo\AdminBundle\Form\DftAbsensiType;
/**
* DftAbsensi controller.
*
*/
class DftAbsensiController extends Controller
{
public function manageAction(Request $request, $id)
{
$user = $this->getUser();
$emGrupPelajar = $this->getDoctrine()->getManager();
$entityGrupPelajar = $emGrupPelajar->getRepository('SifoAdminBundle:DftGrupPelajar')->findByIdGrup($id);
/* check tanggal and set if exist */
$tanggal = $request->request->get('sifo_adminbundle_collectionabsensi')['tanggal'];
if($tanggal == NULL)
$tanggal = $request->request->get('form')['tanggal'];
$tanggal = new \DateTime($tanggal);
/* Show data */
$emShow = $this->getDoctrine()->getManager();
$collectionAbsensi = new CollectionAbsensi();
foreach ($entityGrupPelajar as $temp) {
$entity = new DftAbsensi();
$entity = $emShow->getRepository('SifoAdminBundle:DftAbsensi')->findOneBy(array('idGrupPelajar' => $temp, 'tanggal' => $tanggal));
if ($entity)
{
$entityPelajar = $emShow->getRepository('SifoAdminBundle:MstPelajar')->find($temp->getIdPelajar());
$dftAbsensi = new DftAbsensi();
$dftAbsensi->setId($entity->getId())
->setIdGrupPelajar($entity->getIdGrupPelajar())
->setStatus($entity->getStatus())
;
$collectionAbsensi->getStatusS()->add($dftAbsensi);
$collectionAbsensi->setTanggal($tanggal);
}
}
$emShow->flush();
$formEdit = $this->createForm(new CollectionAbsensiType(), $collectionAbsensi, array(
'action' => $this->generateUrl('admin_absensi_update', array('id' => $id)),
'method' => 'PUT',
));
$formEdit->add('save', 'submit', array('attr' => array('class' => 'btn btn-info')));
return $this->render('SifoAdminBundle:DftAbsensi:manage.html.twig', array(
'form_refresh' => $formRefresh->createView(),
'form_edit' => $formEdit->createView(),
'user' => $user,
));
}
As mentioned before, my CollectionAbsensi actually just used for population fields from databases. But for updating I'm using DftAbsensi Entity. There is no table for CollectionAbsensi in my databases. This is how I update the data:
public function updateAction(Request $request, $id)
{
$user = $this->getUser();
$emGrupPelajar = $this->getDoctrine()->getManager();
$entityGrupPelajar = $emGrupPelajar->getRepository('SifoAdminBundle:DftGrupPelajar')->findByIdGrup($id);
/* set tanggal */
$tanggal = new \DateTime($request->request->get('sifo_adminbundle_collectionabsensi')['tanggal']);
/* populate data */
$emShow = $this->getDoctrine()->getManager();
$collectionAbsensi = new CollectionAbsensi();
foreach ($entityGrupPelajar as $temp) {
$entity = new DftAbsensi();
$entity = $emShow->getRepository('SifoAdminBundle:DftAbsensi')->findOneBy(array('idGrupPelajar' => $temp, 'tanggal' => $tanggal));
if ($entity)
{
$entityPelajar = $emShow->getRepository('SifoAdminBundle:MstPelajar')->find($temp->getIdPelajar());
$dftAbsensi = new DftAbsensi();
$dftAbsensi->setId($entity->getId())
->setIdGrupPelajar($entity->getIdGrupPelajar())
->setStatus($entity->getStatus())
;
$collectionAbsensi->getStatusS()->add($dftAbsensi);
$collectionAbsensi->setTanggal($tanggal);
}
}
$formEdit = $this->createForm(new CollectionAbsensiType(), $collectionAbsensi);
$formEdit->handleRequest($request);
$emShow->flush();
$response = $this->forward('SifoAdminBundle:DftAbsensi:manage', array(
'id' => $id,
'request' => $request,
));
return $response;
}
There is no error from this code. The problem is the databases not updated when I press Save button. I confused for binding data and how to update them into database in updateAction above. Can a collection form not be used for updating data?
My form is attendance system which look like this :
Actually this is still not really answer my question about "How to update data in form collection?". I do update my data with old way : Get all the data from request manually and bind it to entity then update it into database.
Here my code :
public function updateAction(Request $request, $id)
{
$user = $this->getUser();
/* get request */
$data = $request->request->get('sifo_adminbundle_collectionabsensi')['statusS'];
/* update data */
$total = count($data);
for ($i = 0; $i < ($total / 2); $i++) {
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('SifoAdminBundle:DftAbsensi')->find($data[$i]['id']);
if ($entity){
$entity->setStatus($data[$i]['status'])
->setOperator($user->getNama());
$em->flush();
}
}
$response = $this->forward('SifoAdminBundle:DftAbsensi:manage', array(
'id' => $id,
'request' => $request,
));
return $response;
}
If someone has a better idea, post your answer here.
I am learning to use Symfony2 and in the documentation I have read, all entities being used with Symfony forms have empty constructors, or none at all. (examples)
http://symfony.com/doc/current/book/index.html Chapter 12
http://symfony.com/doc/current/cookbook/doctrine/registration_form.html
I have parametrized constructors in order to require certain information at time of creation. It seems that Symfony's approach is to leave that enforcement to the validation process, essentially relying on metadata assertions and database constraints to ensure that the object is properly initialized, forgoing constructor constraints to ensure state.
Consider:
Class Employee {
private $id;
private $first;
private $last;
public function __construct($first, $last)
{ .... }
}
...
class DefaultController extends Controller
{
public function newAction(Request $request)
{
$employee = new Employee(); // Obviously not going to work, KABOOM!
$form = $this->createFormBuilder($employee)
->add('last', 'text')
->add('first', 'text')
->add('save', 'submit')
->getForm();
return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
}
}
Should I not be using constructor arguments to do this?
Thanks
EDIT : Answered Below
Found a solution:
Looking into the API for the Controllers "createForm()" method I found something that is not obvious from the examples. It seems that the second argument is not necessarily an object:
**Parameters**
string|FormTypeInterface $type The built type of the form
mixed $data The initial data for the form
array $options Options for the form
So rather than pass in an instance of the Entity, you can simply pass in an Array with the appropriate field values:
$data = array(
'first' => 'John',
'last' => 'Doe',
);
$form = $this->createFormBuilder($data)
->add('first','text')
->add('last', 'text')
->getForm();
Another option (which may be better), is to create an empty data set as a default option in your Form Class.
Explanations here and here
class EmployeeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('first');
$builder->add('last');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'empty_data' => new Employee('John', 'Doe'),
));
}
//......
}
class EmployeeFormController extends Controller
{
public function newAction(Request $request)
{
$form = $this->createForm(new EmployeeType());
}
//.........
}
Hope this saves others the head scratching.
I have a FormType in Symfony2. It is used to display the settings. The settings are stored as entities in a database. Using Doctrine2, I fetch the settings and create a form, like below:
public function showSettingsAction()
{
if(false === $this->get('security.context')->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedException();
}
$settings = new CommunitySettings();
$repository = $this->getDoctrine()->getRepository('TestTestingBundle:CommunitySettings');
$allSettings = $repository->findAll();
$form = $this->createForm('collection', $allSettings, array(
'type' => 'settings_form'
));
$request = $this->container->get('request');
if($request->getMethod() === 'POST') {
$form->bindRequest($request);
if($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$settings = $form->getData();
foreach($settings as $setting) {
$oldsetting = $em->getRepository('TestTestingBundle:CommunitySettings')
->find($setting->getId());
if(!$oldsetting) {
throw $this->createNotFoundException('No setting found for id '.$setting->getId());
}
$oldsetting->setSettingValue($setting->getSettingValue());
$em->flush();
}
$this->get('session')->setFlash('message', 'Your changes were saved');
return new RedirectResponse($this->generateUrl('_admin_settings'));
}
}
return $this->render('TestTestingBundle:Admin:settings.html.twig',array(
'form' => $form->createView(),
));
}
This is the line of code where I send the array of $allSettings to the settings_form:
$form = $this->createForm('collection', $allSettings, array(
'type' => 'settings_form'
));
This is how the settings form looks like:
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('settingValue', 'text');
}
I have a label, a value and a field type stored in the entity and I would like to use those for building the form. However, when I use this it only shows me the variable names in the Form, like this:
0
Settingvalue //Is a checkbox, where it says Settingvalue, it should be the label stored in the entity
0
1
Settingvalue //Is a integer, where it says Settingvalue, it should be the label stored in the entity
3000
How can I use the variables stored in the Entity to build the Form fields with?
You can use an event listener in your settings form type to solve this problem.
public function buildForm(FormBuilder $builder, array $options)
{
$formFactory = $builder->getFormFactory();
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($formFactory) {
$form = $event->getForm();
$data = $event->getData();
$form->add($formFactory->createNamed('settingsValue', $data->getSettingsType(), array(
'label' => $data->getSettingsLabel(),
)));
});
}