Symfony2: how to get config parameters in Form classes - symfony

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

Related

Symfony 4 Accessing Swift_Mailer in Service

I have been looking at the Symfony 4.1 documentation on using the Swift_mailer. However, it appears the documentation is only assumed it being used in the Controller classes. I'm trying to create a Service with some reusable functions that send email.
I created a EmailService.php file in my service directory. When creating a new instance of this service, it quickly throws and error:
"Too few arguments to function
App\Service\EmailService::__construct(), 0 passed in
*MyApp\src\Controller\TestController.php on line 33
and exactly 1 expected"
I'm not sure how to pass \Swift_Mailer $mailer into the __construct correctly? I have auto wiring enabled in the services.yaml, so i'm not sure what I need to do differently?
class EmailService
{
private $from = 'support#******.com';
private $mailer;
public function __construct(\Swift_Mailer $mailer)
{
$this->mailer = $mailer;
}
How do I pass the \Swift_Mailer into this EmailService construct?
I tried adding this to my config\services.yaml with no success:
App\Service\EmailService:
arguments: ['#mailer']
As mentioned by dbrumann in a comment, I needed to follow the proper way of injecting services.
First, I needed to add the services to config/services.yaml
#config/services.yaml
emailservice:
class: App\Service\EmailService
arguments: ['#swiftmailer.mailer.default', '#twig']
public: true
Second, I need to setup the service to accept both the mailer, and twig for rendering the template.
#App/Service/EmailService.php
<?php
namespace App\Service;
class EmailService
{
private $from = 'support#*****.com';
private $mailer;
private $templating;
public function __construct(\Swift_Mailer $mailer, \Twig\Environment $templating)
{
$this->mailer = $mailer;
$this->templating = $templating;
}
public function userConfirmation(string $recipient, string $confCode) : bool
{
$message = (new \Swift_Message())
->setSubject('Some sort of string')
->setFrom($this->from)
->setTo($recipient)
->setBody(
$this->templating->render(
'email/UserConfirmation.html.twig',
array('confCode' => $confCode)
),
'text/html'
)
/*
* If you also want to include a plaintext version of the message
->addPart(
$this->renderView(
'emails/UserConfirmation.txt.twig',
array('confCode' => $confCode)
),
'text/plain'
)
*/
;
return $this->mailer->send($message);
}
}
Third, to call it from the controller, make sure your controller is extending Controller and not the AbstractController! Crucial step!! Here is an example based on the parameters I require in my service:
public function userConfirmation()
{
$emailService = $this->get('emailservice');
$sent = $emailService->userConfirmation('some#emailaddress.com', '2ndParam');
return new Response('Success') //Or whatever you want to return
}
I hope this helps people. AbstractController does not give you the proper access to the service containers.
#config/services.yaml
App\Service\EmailService
arguments: ['#swiftmailer.mailer.default']
public: true
And in your controller :
public function userConfirmation(EmailService $emailService)
{
$sent = $emailService->userConfirmation('some#emailaddress.com', '2ndParam');
return new Response('Success') //Or whatever you want to return
}
Use FQCN "App\Service\MyService" to declare services in services.yaml and a proper legacy_aliases.yaml file to declare legacy aliases like "app.service.my.service" it helps keep your services.yaml clean...

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.

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

Use Twig Extension in controller

I have a slugify method in an Twig Extension which i would like to use in some cases in a controller, f.e with redirects.
Is there an easy way for this?
How could i access functions from Twig Extensions in the controller?
Or do i have to make the slugify method somewere as a helper in order to use it in the code and in twig?
Access function / logic from twig and a controller
I think there are two solutions for this, both should use the Twig_Function_Method class.
1
The first solution gilden already posted, is to encapsulate the logic into a service and make a wrapper for the Twig Extension.
2
Another solution is to use only the Twig Extension. The Twig Extensino is already a service, you have to define it as service with the special <tag name="twig.extension" />.
But it's also a service, which instance you can grab by the service container. And it's also possible to inject other services:
So you have your Twig Extension / Service:
class MyTwigExtension extends \Twig_Extension
{
private $anotherService;
public function __construct(SecurityService $anotherService= null)
{
$this->anotherService = $anotherService;
}
public function foo($param)
{
// do something
$this->anotherService->bar($param);
}
public function getFunctions()
{
// function names in twig => function name in this calss
return array(
'foo' => new \Twig_Function_Method($this, 'foo'),
);
}
/**
* Returns the name of the extension.
*
* #return string The extension name
*/
public function getName()
{
return 'my_extension';
}
}
The services.xml looks like this
<service id="acme.my_extension" class="Acme\CoreBundle\Twig\Extension\MyTwigExtension">
<tag name="twig.extension" />
<argument type="service" id="another.service"></argument>
</service>
To acccess to the service from your controller you only have to use this:
$this->container->get('acme.my_extension')
Notice The only difference to a normal service is, that the twig extension is not lazy loaded (http://symfony.com/doc/current/cookbook/templating/twig_extension.html#register-an-extension-as-a-service)
I would advise creating a general service and injecting it to the Twig extension. The extension would act just as a wrapper to the service.
namespace Acme\Bundle\DemoBundle\...;
class MyService
{
public function myFunc($foo, $bar)
{
// some code...
}
// additional methods...
}
EDIT - as mentioned by Squazic, the first argument must implement Twig_ExtensionInterface. An inelegant solution would be to add methods to MyTwigExtension, that in turn call out respective methods in the service.
namespace Acme\Bundle\DemoBundle\Twig\Extension;
class MyTwigExtension extends \Twig_Extension
{
protected $service;
public function __construct(MyService $service)
{
$this->service = $service;
}
public function getFunctions()
{
return array(
'myTwigFunction' => new \Twig_Function_Method($this, 'myFunc'),
'mySecondFunc' => new \Twig_Function_Method($this, 'mySecondFunc'),
);
}
public function myFunc($foo, $bar)
{
return $this->service->myFunc($foo, $bar);
}
// etc...
}
Or another way is to get it via twig... (this is on Symfony 2.7)
$twigExt = $this->container->get('twig')->getExtension(TwigExtensionClassName::class);
So if your Twig extension class is called 'MyFabulousTwigExt', then you'd call
$twigExt = $this->container->get('twig')->getExtension(MyFabulousTwigExt::class);
This worked for me when the above didn't (our extension wasn't also a service)
I've found this to be the best way of calling the extension directly (tested in Symfony 4.4):
use Twig\Environment;
private Environment $twig;
public function __construct(Environment $twig)
{
$this->twig = $twig;
}
public function foo()
{
$extensionOutput = $this->twig
->getExtension(YourExtension::class)
->yourExtensionFunction(
$this->twig,
$value
);
...
}
Useful if you don't want to (or can't) break the logic out of the Twig extension.

Resources