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

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

Related

Symfony2 Container Aware Form Type

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);
//..
}

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.

How to access other services inside a Symfony FormType?

I try to access a service from a FormType extended by AbstractType. How can I do that?
Thanks!
As a complete answer based on previous answers/comments:
In order to access to a service from your Form Type, you have to:
1) Define your Form Type as a service and inject the needed service into it:
# src/AppBundle/Resources/config/services.yml
services:
app.my.form.type:
class: AppBundle\Form\MyFormType # this is your form type class
arguments:
- '#my.service' # this is the ID of the service you want to inject
tags:
- { name: form.type }
2) Now in your form type class, inject it into the constructor:
// src/AppBundle/Form/MyFormType.php
class MyFormType extends AbstractType
{
protected $myService;
public function __construct(MyServiceClass $myService)
{
$this->myService = $myService;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->myService->someMethod();
// ...
}
}
Just inject services you want through constructor to the form type.
class FooType extends AbstractType
{
protected $barService;
public function __construct(BarService $barService)
{
$this->barService = $barService;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->barService->doSomething();
// (...)
}
}
Look at this page in the sympfony docs for a description of how to declare your form type as a service. That page has a lot of good documentation and example.
Cyprian is on the right track, but the linked page takes it a step further by creating your form type as a service and having the DI container inject the service automatically.

Symfony2: how to get config parameters in Form classes

If I am inside a controller, I can easily read the config parameters using:
$this->container->getParameter('profession');
But when I am in some other class, say a Form type, how can I get hold of the config parameters?
$container = new Container();
$container->getParameter('profession');
The above code shouldn't and doesn't work.
Another similar solution is make your form type a service and inject the needed parameters. Then all your controller needs to do is to grab the service. Surround the parameter name with percent signs.
In services.xml
<service
id = "zayso_area.account.create.formtype"
class = "Zayso\AreaBundle\Component\FormType\Account\AccountCreateFormType"
public = "true">
<argument type="service" id="doctrine.orm.accounts_entity_manager" />
<argument type="string">%zayso_core.user.new%</argument>
</service>
And if you really wanted to then you could inject the complete container though that is discouraged.
Now you can use ContainerAwareInterface:
class ContactType extends AbstractType implements ContainerAwareInterface
{
use ContainerAwareTrait;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('choice_field', ChoiceType::class, [
'choices' => $this->container->get('yourservice')->getChoices()
]);
}
}
in services.yml:
app.contact_type:
class: AppBundle\Form\ContactType
calls:
- [setContainer, ['#service_container']]
tags:
- { name: form.type, alias: 'container_aware' }
One easy solution is to give your Type a new variable where you store the value of your config parameter. You can either make it public (not recommended), add a constructor parameter or use a setter:
class MyType extends AbstractType{
private $profession;
public function __construct($profession){
$this->profession = $profession;
}
// ...
}
You would use this in your controller like this:
$myType = new MyType($this->container->getParameter('profession'));
// use mytype with form
After all, the form should not know about the container at all as you would tie them together making it hard to test or exchange the container. This would be against the whole idea of the container.
On the other hand, using a constructor/setter to inject parameters is rather nice, as you don't need to know where they come from when testing, can change their source anytime you want and, as said, don't have a dependency to the container.
in Symfony 4 you should first define your form as a Service then in config/services.yaml pass your proper parameter to it
parameters:
locale: 'en'
upload_dir: '%kernel.project_dir%/public/uploads/avatars'
services:
App\Form\FilemanagerType:
arguments: ['%upload_dir%']
tags: [form.type]
and inside your form class get parameter (here upload dir) like this
class FilemanagerType extends AbstractType
{
private $upload_dir;
function __construct(string $upload_dir)
{
$this->upload_dir= $upload_dir;
}
}
I hope it helps
In Symfony 4.1, you just need to add ParameterBagInterface to the Form constructor:
public function __construct(ParameterBagInterface $parameterBag)
{
$this->parameterBag = $parameterBag;
}
Then to get your parameter:
$profession = $this->parameterBag->get('profession');
You can also use a Setter Injection. From http://symfony.com/doc/current/book/service_container.html#optional-dependencies-setter-injection :
If you have optional dependencies for a class, then "setter injection" may be a better option. This means injecting the dependency using a method call rather than through the constructor. The class would look like this:
namespace AppBundle\Newsletter;
use AppBundle\Mailer;
class NewsletterManager
{
protected $mailer;
public function setMailer(Mailer $mailer)
{
$this->mailer = $mailer;
}
// ...
}
Injecting the dependency by the setter method just needs a change of syntax:
# app/config/services.yml
services:
app.mailer:
# ...
app.newsletter_manager:
class: AppBundle\Newsletter\NewsletterManager
calls:
- [setMailer, ['#app.mailer']]
In Symfony3, It can be done like this -
At Controller
$form = $this->createForm(FormType::class, $abc, array('firstargument' => $firstargumentvalue, 'second' => $secondvalue));
At FormType
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array('data_class' => abc::class, 'firstargument' => null, 'second' => null));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$first = $options['firstargument'];
$second = $options['second'];
}
You can use the above values in the form
In Symfony 4.1
services:
# ...
_defaults:
bind:
$projectDir: '%kernel.project_dir%'
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class MessageGenerator
{
private $params;
public function __construct(ParameterBagInterface $params)
{
$this->params = $params;
}
public function someMethod()
{
$parameterValue = $this->params->get('parameter_name');
// ...
}
}
Please refer this https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service

Fetching data through a custom repository in a Twig extension

I'd like to display new notifications on every page of my symfony 2 webapplication.
I was advised to use a Twig Extension for this. I've created a function getFriendRequests in that extension, but I don't know if it's good practice to get data through my custom repository in the twig extension: Right now it's giving me the error, that it can't find the getDoctrine method.
<?php
namespace Tennisconnect\DashboardBundle\Extension;
class NotificationTwigExtension extends \Twig_Extension
{
public function getFriendRequests($user)
{
$users = $this->getDoctrine()
->getRepository('TennisconnectUserBundle:User')
->getFriendRequests();
return count($users);
}
public function getName()
{
return 'notification';
}
public function getFunctions()
{
return array(
'getFriendRequests' => new \Twig_Function_Method($this, 'getFriendRequests'));
}
}
I don't think it is so bad to fetch your data directly from your twig extension. After all, if you don't do it here, you will need to fetch those records before and then pass them to the extension for display anyway.
The important point is to do the DQL/SQL stuff in the repository like you are already doing. This is important to separate database statements from other part of your project.
The problem you having is that the method getDoctrine does not exist in this class. From what I understand, you took this code from a controller which extends the FrameworkBundle base controller. The base controller of the FrameworkBundle defines this method.
To overcome this problem, you will have to inject the correct service into your extension. This is based on the dependency injection container. You certainly defined a service for your twig extension, something like this definition:
services:
acme.twig.extension.notification:
class: Acme\WebsiteBundle\Twig\Extension\NotificationExtension
tags:
- { name: twig.extension }
The trick is now to inject the dependencies you need like this:
services:
acme.twig.extension.notification:
class: Acme\WebsiteBundle\Twig\Extension\NotificationExtension
arguments:
doctrine: "#doctrine"
tags:
- { name: twig.extension }
And then, in you extension, you define a constructor that receives the doctrine dependency:
use Symfony\Bridge\Doctrine\RegistryInterface;
class NotificationTwigExtension extends \Twig_Extension
{
protected $doctrine;
public function __construct(RegistryInterface $doctrine)
{
$this->doctrine = $doctrine;
}
// Now you can do $this->doctrine->getRepository('TennisconnectUserBundle:User')
// Rest of twig extension
}
This is the concept of dependency injection. You can see another question I answered sometime ago about accessing services outside controller: here
Hope this helps.
Regards,
Matt
The same but with mongo:
in config.yml
services:
user.twig.extension:
class: MiProject\CoreBundle\Twig\Extension\MiFileExtension
arguments:
doctrine: "#doctrine.odm.mongodb.document_manager"
tags:
- { name: twig.extension }
and in your Twig\Extensions\MiFile.php
<?php
namespace MiProject\CoreBundle\Twig\Extension;
use Symfony\Component\HttpKernel\KernelInterface;
class MiFileExtension extends \Twig_Extension
{
protected $doctrine;
public function __construct( $doctrine){
$this->doctrine = $doctrine;
}
public function getTransactionsAmount($user_id){
return $results = $this->doctrine
->createQueryBuilder('MiProjectCoreBundle:Transaction')
->hydrate(false)
->getQuery()
->count();
}
Rest of mi code ...
}

Resources