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.
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.
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.
I have a little problem with the creation of my services in symfony 4.1
I use Factories to create my services, and to force the Factory to have the expected method I made an Interface
<?php
namespace App\Service\Factory\Interfaces;
use App\Service\Interfaces\BaseModelServiceInterface;
use Doctrine\ODM\MongoDB\DocumentManager;
/**
* Interface ModelServiceFactoryInterfaces
* #package App\Service\Factory\Interfaces
*/
interface ModelServiceFactoryInterfaces
{
/**
* Create the Model related Service
*
* #return BaseModelServiceInterface
*/
public function createService(DocumentManager $dm);
}
I get the DocumentManager from autowired services to create the Repository in the Factory and pass it to the service, like this
/**
* Class ChapterServiceFactory
* #package App\Service\Factory
*/
class ChapterServiceFactory implements ModelServiceFactoryInterfaces
{
/**
* #param DocumentManager $dm
* #return ChapterService|BaseModelServiceInterface
*/
public function createService(DocumentManager $dm)
{
$chapterRepository = $dm->getRepository(Chapter::class);
/**
* #var $chapterRepository ChapterRepository
*/
return new ChapterService($chapterRepository);
}
}
The problem with that is, if I want to have another service in my ChapterService I can't autowire it in the Factory because of the Interface, but I don't want to delete the Interface either.
Is there a way to have "dynamic arguments" with an Interface, or another way than the Interface to force the Factories to have the createService method ?
There's no workaround for this: if you declare an interface as the argument, then you need to provide an explicit implementation for that interface. This means that you cannot declare two default implementations for the same interface. Only thing you can do in this case is to declare explicitly the service and all its arguments.
By the way, as you're writing about Doctrine Repositories, I suggest you to take a look at ServiceEntityRepository: extending this class from one of your repos will make automatically the report a service that you can inject where needed.
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 am doing some file operations in S3 and need to perform a few actions after a temporary file has been successfully copied following an API request. For example, update the filesize stored in the DB.
I'd like to dispatch an event in the cases where this action needs to occur but any subscriber is going to need a few services like the filesystem and an entity manager and I cannot figure out how to inject services into the EventSubscriber since it needs to be created and added to the EventDispatcher as a subscriber in a class that is not aware of the filesystem or doctrine, or the container.
I've attempted to use the ContainerAwareEventDispatcher. Here's my event being dispatched in that way:
class PendingFile implements SourceFile
{
/**
* #var string
*/
private $filename;
/**
* #var PendingFileService
*/
private $pendingFileService;
/**
* #param string $filename
* #param PendingFileService $pendingFileService
*/
public function __construct($filename, PendingFileService $pendingFileService)
{
$this->filename = $filename;
$this->pendingFileService = $pendingFileService;
}
/**
* #param string $targetFilename
* #param Media $media
*/
public function process($targetFilename, Media $media)
{
$this->pendingFileService->copyFile($this->filename, $targetFilename);
$event = new PendingFileCopyEvent($media);
$eventDispatcher = new ContainerAwareEventDispatcher(new ContainerBuilder());
$eventDispatcher->addSubscriberService(
'acme.media.event_subscriber.pending_file_copy',
'Acme\MediaBundle\EventSubscriber\PendingFileCopySubscriber'
);
$eventDispatcher->dispatch(PendingFileCopyEvent::EVENT_NAME, $event);
}
}
Unfortunately, this container is empty and does not recognise my service. I can't inject these services into the entity itself. And if I could, why would I use an event subscriber anyway...
Question: How can I properly build my subscriber as a service with the dependencies it needs?
In general you should only use one instance of an event dispatcher. Symfony has a #event_dispatcher service. You should use it instead of instantiate a new one. So in your PendingFile Class, add a EventDispatcherInterface $eventDispatcher in your constructor, then just dispatch an event like you do.
For the subscriber, you need to create the class, then declare it as a service with a tag kernel.event_subscriber like explained in the doc to automatically register the event subscriber in the event dispatcher (so you can removed the addSubscriberService line of your code).
And Voila!
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.