I am in the process of migrating an application from Symfony 2.8 to Symfony 3.4
The services are now private and therefore instead of making a direct call to the services from the container, we must use dependency injection as a workaround.
For parameters, is $this->getParameter() is an anti pattern ? If yes, how can we get them in controller and of course we must always respecting the good practices ?
For information, there is a solution in Symfony 4.1, using the bind in services.yml
This the following script and this the environment as an example :
class CmsController extends Controller
{
/**
* #param Request $request
*
* #return Response
*/
public function importExportAction(Request $request): Response
{
$adminPool = $this->get('sonata.admin.pool');
$env = $this->getParameter('environment');
return new Response('OK');
}
}
Controllers are now seen as services.
You can look at this anwser, to know how configure services with parameters.
Related
I am in the process of migrating an application from Symfony 2.8 to Symfony 3.4
The services are now private and therefore instead of making a direct call to the services from the container, we must use dependency injection as a workaround.
So this is the following script and i'd like to check the existence and after that call profiler service using dependency injection :
<?php
namespace DEL\Bundle\ApiBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Class EstimatePDFController
*
* #package DEL\Bundle\ApiBundle\Controller
*/
class EstimateController extends Controller
{
/**
*
* #param Request $request Request object.
*
* #return Response A Response instance
*/
public function sendAction(Request $request)
{
// disable debug env outputs
if ($this->container->has('profiler')) {
$this->container->get('profiler')->disable();
}
return new Response('OK');
}
}
As far as I know, this is not possible using autowiring. But the documentation provides an alternative:
add the profiler to your controller as a property
add a setter like setProfiler(Profiler $profiler) that sets the property
add a conditional setter to your service definition:
calls:
- [setProfiler, ['#?profiler']]
check whether $this->profiler is null or not in your sendAction method
Checking the existence means the Profiler exists before using it right? So you can autowire the Profiler with a default value and if it is not null, it exists. Something like this:
/**
* #param Request $request Request object.
* #param Profiler $profiler The Profiler if the service exists
*
* #return Response A Response instance
*/
public function sendAction(Request $request, Profiler $profiler = null): Response
{
// disable debug env outputs
if ($profiler !== null) {
$profiler->disable();
}
return new Response('OK');
}
This is the default behaviour by the way. It tries to resolve the argument, but if it fails, it skips it. And if you have no default value, then PHP fails.
I would like to know how I could avoid setter injection under the following use case.
I have a collection of tagged services:
interface EmailFormatter
class CvEmailFormatter implements EmailFormatter
class RegistrationEmailFormatter implements EmailFormatter
class LostPasswordEmailFormatter implements EmailFormatter
I use a CompilerPass to inject these into a mailer service by invoking its addEmailFormatter() method.
Each EmailFormatter instance requires some configuration that must be retrieved at run time from the current user:
/**
* #var FormatterConfig[]
* #ORM\OneToMany(targetEntity="AppBundle\Entity\FormatterConfig", mappedBy="user", cascade={"persist"}, orphanRemoval=true)
*/
protected $formatterConfigs;
Right now I am checking to see if the user has created a configuration for a particular EmailFormatter inside the mailer service's addEmailFormatter() method. If they have then setConfig() injects the configuration object. If no config exists yet the EmailFormatter does not get added at all.
public function addEmailFormatter(EmailFormatter $formatter, $alias)
{
$formatterConfig = $this->user->getFormatterConfig($alias);
if (null !== $formatterConfig) {
$this->formatters[$alias] = $formatter;
$formatter->setConfig($formatterConfig);
}
}
Could this be a legitimate use case given I am working with a mutable collection of services?
I have services that require the #request_stack to fetch parameters.
Now, I want to expose certain functionality to console commands callable via ./app/console//. Yet in the context of an ./app/console, there is no #request_stack, yet one can input arguments.
In order to resolve this issue, I am now creating basically two services, one basic, only waiting for the params, and one being able to use the #request_stack.
Yet I dislike that there are two ways for the data to be fetched in the request-based flow and via the app/console.
Hence I am wondering, as I am simply want the data that comes per default via the request to also be able to be inputted via console arguments:
Can I setup a custom request_stack to simulate a request during a console command?
When I was investigating this issue, I stumbled across request stack push method, where a warning was already in place in the doc block:
/**
* Pushes a Request on the stack.
*
* This method should generally not be called directly as the stack
* management should be taken care of by the application itself.
*/
public function push(Request $request)
{
$this->requests[] = $request;
}
So while it would be possible to do it this way, I decided against the approach of my original question and to refactor my application instead.
I have created a context value object which just holds the parameter data:
/**
* Context
**/
class Context
{
/**
* #var string
*/
private $countryCode;
/**
* Context constructor.
* #param string $countryCode
*/
public function __construct($countryCode = '')
{
$this->countryCode = $countryCode;
}
/**
* #return string
*/
public function getCountryCode()
{
return $this->countryCode;
}
}
And a ContextFactory that creates the context with by the request stack:
class ContextFactory extends RequestAwareService
{
/**
* ContextFactory constructor.
* #param RequestStack $stack
*/
public function __construct(RequestStack $stack)
{
$this->setRequestStack($stack);
}
/**
* #return Context
*/
public function create()
{
return new Context($this->request->getCountryCode());
}
}
(The RequestAwareService is just a helper class to more easily parse the request.)
I then defined the services in my Bundle services.yml:
context.factory:
class: Kopernikuis\MyBundle\Service\Config\ContextFactory
arguments:
- '#request_stack'
context:
class: Kopernikuis\MyBundle\Service\Config\Context
factory:
- '#context.factory'
- create
Instead of injecting the #request_stack, I am now injecting my #context value object, which also had the benefit of reducing the hierarchy as now only one service parses the request_stack once, and I also noticed that certain functionality got much simpler as I could remove parameters from method calls, as they were all provided by the context object instead.
And in my custom commands, I can just replace my context
protected function execute(InputInterface $input, OutputInterface $output)
{
// #todo: use variable from InputInterface
$context = new Context('fnordfoo');
$this->getContainer()->set('context', $context);
}
With the newly gained knowledge, I strongly disagree with my original intent of trying to manually set the #request_stack.
Refactoring the code base to not necessarily require the #request_stack was a more solid choice.
I have seen the request object being passed to the controller action method as a parameter like this:
public function addAddressAction(Request $request)
{
...
}
I have also seen it within the action method where it is gotten from the container:
public function addAddressAction()
{
$request = $this->getRequest();
...
}
Which one is better? Does it matter?
If you take a deeper look at the Symfony2 Base Controller code, you may notice that getRequest() is marked as deprecated since version 2.4 and will be removed in 3.0.
/*
* ...
* #deprecated Deprecated since version 2.4, to be removed in 3.0. Ask
* Symfony to inject the Request object into your controller
* method instead by type hinting it in the method's signature.
*/
public function getRequest()
{
return $this->container->get('request_stack')->getCurrentRequest();
}
Introduced by the following evolution,
[FrameworkBundle] use the new request_stack service to get the
Request object in the base Controller class.
And, here's the upgrade from 2.x to 3.0 documentation.
Upgrade from 2.x to 3.0 - FrameworkBundle
Conclusion,
Your Request should then be part of your action's signature.
As far as I know there's no difference. It doesn't interrupt affect much either way. Even if you want to specify required parameters in your action. E.g.
/**
* #Route("/edit/{id}", name="edit")
*/
public function editAction(Request $request, $id)
{
// Both $request and $id are available
}
I'm using a YAML configuration to wire my dependencies, and I need to provide some runtime information to get a useful object back. I was going to run a setter method from my code once the object has been injected, but I Was wondering if there was a better way of doing it (or if there's something I'm missing).
This is the gist of my configuration:
services:
example_object : "myObject"
arguments : ["%object_parameter1%"]
parameters:
object_parameter1 : Some Static Data
object_parameter2 : #Rutime info required
For retrieving the current logged in user in any service, inject the security.context. In this case I use setter injection to simply user mock injection.
namespace Acme\ExampleBundle\Foo;
use Symfony\Component\Security\Core\SecurityContextInterface;
class MyService
{
private $param;
private $user;
public function __construct($param)
{
$this->param = $param;
}
/**
* Retrieve the current logged in user from the security context.
*/
public function setUserFromContext(SecurityContextInterface $context)
{
$this->user = $context->getToken()->getUser();
}
/**
* Set any user object.
*
* Usefull for testing, to inject a simple user mock.
*/
public function setUser($user)
{
$this->user = $user;
}
public function doSomething()
{
// do something with the user object
}
}
Define the service:
services:
my_service:
class: Acme\ExampleBundle\Foo\MyService
arguments: ["%object_parameter1%"]
calls:
- [ setUserFromContext, [#security.context] ]
You should not try to add dynamic values directly into the DI configuration. Symfony services configuration is reflected by compiled DI container and recompilation is very heavy operation.
If you do not want to couple your service with Symfony's security system directly, you can add your custom "user provider" service as a dependency. Then you will need to rewrite this service if the source of information will change. It may be also easily mocked.
You can also use a factory to inject a user object instead of user provider service.