Symfony2 Container Aware Form Type - symfony

Is there a way to make a form type container aware?
As an example I have 3 entities Account, User and Event. Users have ManyToMany relationship in order to associate many users with many other users (called approvers), reason for this is so that an Event created by a User can have a list of Users who area able approve it. In the User edit form where I have an approvers multiple select field the list needs to be filtered by Account so I need my form type to be container aware in order to filter the list of available Users by Account ID.
Am I right in thinking that making the form type container aware is the right way to go? I'd like to use the entity manager to filter a list of Users by Account.

1 Inject the entity manager through the constructor
<?php
namespace Acme\YourBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\EntityManager;
class YourType extends AbstractType
{
/**
* The entity manager
*
* #var EntityManager
*/
private $entityManager;
/**
* #param EntityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
//build your form here
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\YourBundle\Entity\YourEntity',
));
}
public function getName()
{
return 'name_of_your_form';
}
}
2 Declare it as a service
services:
# Form type
acme_your_bundle.name_of_your_form.form.type:
class: Acme\YourBundle\Form\Type\YourType
arguments:
entityManager: "#doctrine.orm.entity_manager"
Note:
If you're starting with Symfony, take this advice:
look very closely at the code of the FOSMessageBundle, it will give you exactly what you need to do anything in symfony, from form models, to form factories, to the creation of special services (like composer, authorizer, etc..). The more you study this bundle, the quicker you will learn symfony, I guarantee you that 100%. Finally, in your specific case, look at the FormFactory in this bundle

This solution allows you to inject the container into many forms without each of your form types being a service:
Create a new form type:
class ContainerAwareType extends AbstractType implements ContainerAwareInterface
{
protected $container;
public function setContainer(ContainerInterface $container = null) {
$this->container = $container;
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'container' => $this->container
));
}
public function getName() {
return 'container_aware';
}
public function getParent() {
return 'form';
}
}
Declare as a service:
services:
app.container_aware_type:
class: Burgerfuel\CmsBundle\Form\Type\ContainerAwareType
calls:
- [setContainer, ['#service_container']]
tags:
- { name: form.type, alias: 'container_aware' }
This type is now available to be a 'parent' to any other form type - whether its a service or not. In this case the important part is that setDefaultOptions from this class will be used to help build the $options argument that will be passed into any 'child' form types.
In any of your form types you can do this:
class MyType extends AbstractType
{
public function getParent() {
return 'container_aware';
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$container = $options['container'];
$builder->add( ...
This solution will be beneficial if you can't make your form type a service for some reason.
Or it can save you time if you are creating many types that require access to the container.

A simple way to do this, without doing any dependency injection / declaring a service.
In your FormType file, force the form to require an EntityManager
//..
use Doctrine\ORM\EntityManager;
class YourFormType extends AbstractType
{
//...
//...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\YourBundle\Entity\YourEntity',
));
$resolver->setRequired('entity_manager');
$resolver->setAllowedTypes('entity_manager', EntityManager::class);
}
}
Then you'll be able to (and forced - important for testing), pass the entity manager from the controller.
public function yourControllerAction(Request $request)
{
//..
$em = $this->getDoctrine()->getManager();
$form = $this->createForm('Acme\YourBundle\Form\YourEntityType', $yourEntityObject, array(
'entity_manager'=>$em,
));
$form->handleRequest($request);
//..
}

Related

Silex + Doctrine2 ORM + Dropdown (EntityType)

I have a controller that renders a form that is suppose to have a dropdown with titles mapped against a client_user entity. Below is code I use in my controller to create the form:
$builder = $this->get(form.factory);
$em = $this->get('doctrine.entity_manager');
$form = $builder->createBuilder(new ClientUserType($em), new ClientUser())->getForm();
Below is my ClientUserType class with a constructor that I pass the entity manager on:
<?php
namespace Application\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
class ClientUserType extends AbstractType
{
protected $entityManager;
public function __construct($entityManager)
{
$this->entityManager = $entityManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', EntityType::class, array(
'class' => '\\Application\\Model\\Entity\\Title',
'em' => $this->entityManager
))
->add('name')
->add('surname')
->add('contact')
->add('email');
}
public function getName()
{
return 'client_user_form';
}
}
I keep on getting this catchable fatal error below and have no idea what I need to do in order to get a dropdown with titles from a database with doctrine.
Catchable fatal error: Argument 1 passed to Symfony\Bridge\Doctrine\Form\Type\DoctrineType::__construct() must be an instance of Doctrine\Common\Persistence\ManagerRegistry, none given, called in D:\web\playground-solutions\vendor\symfony\form\FormRegistry.php on line 90 and defined in D:\web\playground-solutions\vendor\symfony\doctrine-bridge\Form\Type\DoctrineType.php on line 111
Reading from that error I have no idea where I need to create a new instance of ManagerRegistry registry as it appears that the entity manager does not work. I am also thinking perhaps I need to get the ManagerRegistry straight from the entity manager itself.
Can someone please help explain the simplest way to get this to work? What could I be missing?
Seems that doctrine-bridge form component is not configured.
Add class
namespace Your\Namespace;
use Doctrine\Common\Persistence\AbstractManagerRegistry;
use Silex\Application;
class ManagerRegistry extends AbstractManagerRegistry
{
protected $container;
protected function getService($name)
{
return $this->container[$name];
}
protected function resetService($name)
{
unset($this->container[$name]);
}
public function getAliasNamespace($alias)
{
throw new \BadMethodCallException('Namespace aliases not supported.');
}
public function setContainer(Application $container)
{
$this->container = $container;
}
}
and configure doctrine-bridge form component
$application->register(new Silex\Provider\FormServiceProvider(), []);
$application->extend('form.extensions', function($extensions, $application) {
if (isset($application['form.doctrine.bridge.included'])) return $extensions;
$application['form.doctrine.bridge.included'] = 1;
$mr = new Your\Namespace\ManagerRegistry(
null, array(), array('em'), null, null, '\\Doctrine\\ORM\\Proxy\\Proxy'
);
$mr->setContainer($application);
$extensions[] = new \Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension($mr);
return $extensions;
});
array('em') - em is key for entity manager in $application
For others that may find this: If you want to use the EntityType and you're not using a framework at all, you need to add the DoctrineOrmExtension to your FormFactoryBuilder like so:
$managerRegistry = new myManagerRegistry(
'myManager',
array('connection'),
array('em'),
'connection',
'em',
\Doctrine\ORM\Proxy\Proxy::class
);
// Setup your Manager Registry or whatever...
$doctrineOrmExtension = new DoctrineOrmExtension($managerRegistry);
$builder->addExtension($doctrineOrmExtension);
When you use EntityType, myManagerRegistry#getService($name) will be called. $name is the name of the service it needs ('em' or 'connection') and it needs to return the Doctrine entity manager or the Doctrine database connection, respectively.
In your controller, try to call the service like that:
$em = $this->get('doctrine.orm.entity_manager');
Hope it will help you.
Edit:
Sorry, I thought you was on Symfony... I have too quickly read...

Symfony2 Form Type as a service with dynamic parameter

I'm really interested to know if there is a way to call a service with a dynamic parameter (a string for example)?
Actually I need this for a form type (define as a service), which makes it a little more complex.
The form type :
class MyFormType extends AbstractType
{
private $em;
private $parameter;
public function __construct(EntityManager $em, $parameter)
{
$this->em = $em;
$this->parameter = $parameter;
}
// ...
}
The service config
my.form_type:
class: My\Form\Type\Class
arguments: [ #doctrine.orm.entity_manager ]
tags:
- { name: form.type, alias: form_name }
Then when I need to use it in another form type:
class SecondFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('custom', 'my.form_type');
;
}
}
I would like to know how to set the "parameters" attribute in the first form type class.
If I was in a controller, I would be able to create some getter/setter methods but here I'm stuck in the form type.
I actually don't instantiate the form type myself, because I also need to inject it the entity manager, that's why I defined it as a service.

Extending form types in Symfony2 (aka creating a new form widget)

I'm trying to create/expand a form type in Symfony2, what i want to make is a category selector like in the following image. For that i was reading in symfony2 doc, The chapter: "How to create custom field type"(http://symfony.com/doc/current/cookbook/form/create_custom_field_type.html)
The table database for this this have the following aspect...
What i pretend it's in Symfony extend the hidden form type widget to create my own type, I can not find in the documentation of symfony how to access to the Entities data from the custom type, and also how to call to the custo type object methods in the twig file of the widget. (In the example the twig file is src/Acme/DemoBundle/Resources/views/Form/fields.html.twig )
I know that i have to do some ajax callings to auto load the subcategories every time somebody touch a category, i have done this in the controller, but firstly i want to know how to do what i wrote. Wish this widget be reusable for all :).
Thanks a lot guys!
You should declare your new type as service and inject the Entity Manager into it :
namespace Your\Bundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\EntityManager;
class NewExampleType extends AbstractType
{
protected $em;
public function __construct(EntityManager $entityManager)
{
$this->em = $entityManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
//your code
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
//your code
}
public function getParent()
{
return 'hidden';
}
public function getName()
{
return 'example_widget';
}
}
Then, declare new service in services.yml
services:
example.type:
class: Your\Bundle\Form\Type\NewExampleType
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: form.type, alias: "example_widget" }
Source: http://symfony.com/doc/current/cookbook/form/create_custom_field_type.html#creating-your-field-type-as-a-service
Then, you should take a look here : http://symfony.com/doc/current/cookbook/form/data_transformers.html

Access current user in Symfony 2.1 Abstract Type

I'm a bit confused about how to access the current user in Symfony 2. Currently I'm trying to display a variation of a form (AbstractType) depending on the ROLES of the current user.
A similar question has already been answered by Gremo: Access currently logged in user in EntityRepository
My question is: Is there a Symfony 2 native way to access the user inside my AbstractType class without using JMSDiExtraBundle? Thanks!
Here's my current code:
namespace Acme\DemoBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class Comment extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
//somehow access the current user here
$builder
->add('name')
->add('comment_text')
->add('comment_email')
// Add more fields depending on user role
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\DemoBundle\Entity\Comment'
));
}
public function getName()
{
return 'acme_demobundle_comment';
}
}
Edit: I'm looking for the currently logged in user (security.context)
Into your controller, do something like this
$form = $this->createForm(new CommentType($this->get('security.context')
->isGranted('ROLE_ADMIN')), $comment);
Where ROLE_ADMIN is the role for which you want to discriminate.
Now, into your Type you have to retrieve it into the following way
private $isGranted;
public function __construct($roleFlag)
{
$this->isGranted = $roleFlag;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('comment_text')
->add('comment_email');
if($this->isGranted) {
$builder
->add(....)
->add(....)
[....]
->add(....);
}
JMSDiExtraBundle provide (among other) annotations and shortcuts in order to define services, for example form types and doctrine listeners, that is just regular services but with particular tags. If i recall correctly the bundle is included in a standard Symfony 2.1 release, so why not using it?
Anyway to inject the user "the old way", use constructor injection for example:
class Comment extends AbstractType
{
private $context;
public function __construct(SecurityContext $context)
{
$this->context = $context;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$loggedUser = $this->context->getToken()->getUser();
/* ... */
}
}
And define it as a service with the form.type tag:
<service id="form.type.comment" class="Acme\DemoBundle\Form\Comment">
<argument type="service" id="security.context" />
<tag name="form.type" alias="comment" />
</service>
Why don't you inject the User as a ConstructorArgument:
$form = $this->createForm(new CommentType($user), $comment);
I'm new in Symphony so i hope this is not totaly wrong :-S
If the UserObject is part of the Comment Model you're working on with your form you'll be able to access it via:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$user = $builder->getData()->getUser();
....

Symfony2 Forms and Polymorphic collections

Im playing around with Symfony2 and Im abit unsure how Symfony2 handles Polymorphic collections in the View component. It seems that i can create an entity with a collection of AbstractChildren, but not sure how to what to do with it inside a Form Type class.
For example, I have the following entity relationship.
/**
* #ORM\Entity
*/
class Order
{
/**
* #ORM\OneToMany(targetEntity="AbstractOrderItem", mappedBy="order", cascade={"all"}, orphanRemoval=true)
*
* #var AbstractOrderItem $items;
*/
$orderItems;
...
}
/**
* Base class for order items to be added to an Order
*
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({
* "ProductOrderItem" = "ProductOrderItem",
* "SubscriptionOrderItem " = "SubscriptionOrderItem "
* })
*/
class AbstractOrderItem
{
$id;
...
}
/**
* #ORM\Entity
*/
class ProductOrderItem extends AbstractOrderItem
{
$productName;
}
/**
* #ORM\Entity
*/
class SubscriptionOrderItem extends AbstractOrderItem
{
$duration;
$startDate;
...
}
Simple enough, but when im create a form for my order class
class OrderType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('items', 'collection', array('type' => AbstractOrderItemType()));
}
}
I am unsure how to handle this situation where you effectively need a different Form Type for each class of item in the collection?
I recently tackled a similar problem - Symfony itself makes no concessions for polymorphic collections, but it's easy to provide support for them using an EventListener to extend the form.
Below is the content of my EventListener, which uses a similar approach to Symfony\Component\Form\Extension\Core\EventListener\ResizeFormListener, the event listener which provides the collection form type's normal functionality:
namespace Acme\VariedCollectionBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class VariedCollectionSubscriber implements EventSubscriberInterface
{
protected $factory;
protected $type;
protected $typeCb;
protected $options;
public function __construct(FormFactoryInterface $factory, $type, $typeCb)
{
$this->factory = $factory;
$this->type = $type;
$this->typeCb = $typeCb;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => 'fixChildTypes'
);
}
public function fixChildTypes(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
// Go with defaults if we have no data
if($data === null || '' === $data)
{
return;
}
// It's possible to use array access/addChild, but it's not a part of the interface
// Instead, we have to remove all children and re-add them to maintain the order
$toAdd = array();
foreach($form as $name => $child)
{
// Store our own copy of the original form order, in case any are missing from the data
$toAdd[$name] = $child->getConfig()->getOptions();
$form->remove($name);
}
// Now that the form is empty, build it up again
foreach($toAdd as $name => $origOptions)
{
// Decide whether to use the default form type or some extension
$datum = $data[$name] ?: null;
$type = $this->type;
if($datum)
{
$calculatedType = call_user_func($this->typeCb, $datum);
if($calculatedType)
{
$type = $calculatedType;
}
}
// And recreate the form field
$form->add($this->factory->createNamed($name, $type, null, $origOptions));
}
}
}
The downside to using this approach is that for it to recognize the types of your polymorphic entities on submit, you must set the data on your form with the relevant entities before binding it, otherwise the listener has no way of ascertaining what type the data really is. You could potentially work around this working with the FormTypeGuesser system, but that was beyond the scope of my solution.
Similarly, while a collection using this system still supports adding/removing rows, it will assume that all new rows are of the base type - if you try to set them up as extended entities, it'll give you an error about the form containing extra fields.
For simplicity's sake, I use a convenience type to encapsulate this functionality - see below for that and an example:
namespace Acme\VariedCollectionBundle\Form\Type;
use Acme\VariedCollectionBundle\EventListener\VariedCollectionSubscriber;
use JMS\DiExtraBundle\Annotation\FormType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\AbstractType;
/**
* #FormType()
*/
class VariedCollectionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Tack on our event subscriber
$builder->addEventSubscriber(new VariedCollectionSubscriber($builder->getFormFactory(), $options['type'], $options['type_cb']));
}
public function getParent()
{
return "collection";
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array('type_cb'));
}
public function getName()
{
return "varied_collection";
}
}
Example:
namespace Acme\VariedCollectionBundle\Form;
use Acme\VariedCollectionBundle\Entity\TestModelWithDate;
use Acme\VariedCollectionBundle\Entity\TestModelWithInt;
use JMS\DiExtraBundle\Annotation\FormType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\AbstractType;
/**
* #FormType()
*/
class TestForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$typeCb = function($datum) {
if($datum instanceof TestModelWithInt)
{
return "test_with_int_type";
}
elseif($datum instanceof TestModelWithDate)
{
return "test_with_date_type";
}
else
{
return null; // Returning null tells the varied collection to use the default type - can be omitted, but included here for clarity
}
};
$builder->add('demoCollection', 'varied_collection', array('type_cb' => $typeCb, /* Used for determining the per-item type */
'type' => 'test_type', /* Used as a fallback and for prototypes */
'allow_add' => true,
'allow_remove' => true));
}
public function getName()
{
return "test_form";
}
}
In the example you have give, you would have to create different form class for those ProductOrder and SubscriptionOrder
class ProductOrderType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
//Form elements related to Product Order here
}
}
and
class SubsciptionOrderType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
//Form elements related SubscriptionOrder here
}
}
In your OrderType form class you add both these forms, like this
class OrderType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('product',new ProductOrderType())
$builder->add('subscription',new SubsciptionOrderType())
//Form elements related to order here
}
}
Now this adds the two forms SubsciptionOrderType,ProductOrderType to the main form OrderType . So later in the controller if you initialize this form you will get all the fields of the subscription and product forms with that of the OrderType.
I hope this answers your questions if still not clear please go through the documentation for embedding multiple forms here. http://symfony.com/doc/current/cookbook/form/form_collections.html

Resources