Symfony2: How to access a service inside an extension's load() method? - symfony

Is it possible to get the doctrine service inside a bundle extension?
I can access the container, but can't get the doctrine service.
...
class UltroExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container){
$dm = $container->get('doctrine_mongodb');
...
}
}
I get this error: The service definition "doctrine_mongodb" does not exist.

The container hasn't been built/compiled yet - that's why you only get a ContainerBuiler object passed to the load() method.
you can't get a service object from the builder as it's not holding the services but only the service definitions at that point.
Use a compiler pass instead. More information can be found in the documentation chapter Compiling the container.
Maybe your problem can be solved using a service factory, too.

Related

Symfony VichUploaderBundle autowiring on DownloadHandler in a controller

I'm using Symfony 4 with VichUploaderBundle 1.9 and I'm having hard time injecting the DownloadHandler service in my controller in order to send file to the client.
I'm also using HashidsBundle in order to convert my entity ID to something like jFaJ in my URLs.
As stated in the VichUploaderBundle documentation, I'm injecting the service in my controller like this :
public function download(Wallpaper $wallpaper, DownloadHandler $downloadHandler)
{
return $downloadHandler->downloadObject($wallpaper->getMedia(), 'uploadedFile');
}
Here is the error I'm having:
Argument 2 passed to App\Controller\WallpapersController::download()
must be an instance of Vich\UploaderBundle\Handler\DownloadHandler,
integer given, called in
/mnt/c/Users/user/Documents/Dev/symfony/vendor/symfony/http-kernel/HttpKernel.php
on line 151
I also tried to manually call the service by adding the following line in my controller:
$this->get('vich_uploader.download_handler');
But it's still not working, I have this error now:
Service "vich_uploader.download_handler" not found: even though it exists in the app's container, the container inside "App\Controller\WallpapersController" is a smaller service locator that only knows about the "doctrine", "form.factory", "http_kernel", "parameter_bag", "request_stack", "router", "security.authorization_checker", "security.csrf.token_manager", "security.token_storage", "serializer", "session" and "twig" services. Try using dependency injection instead.
You can return the file using BinaryFileResponse.
public function download(Wallpaper $wallpaper): BinaryFileResponse
{
$file = new BinaryFileResponse($wallpaper->getMedia());
return $file;
}
For more info, check
https://github.com/aythanztdev/prbtcnccd/blob/master/src/Controller/MediaObject/ShowMediaObjectAction.php

Class does not receive parameters sent by a service. Symfony2

I have a couple of days something lost with the declaration of services, especially when passing parameters to custom classes.
I have several extensions and listeners declared as services, to which I pass parameters to the constructor and operating properly but do the same for a custom class and does not work.
The error is that the class does not receive parameters in the constructor.
Error:
Catchable Fatal Error: Argument 1 passed to Consolidador\PanelBundle\Controller\Integration::__construct() must be an instance of Doctrine\Bundle\DoctrineBundle\Registry, none given, called in /var/www/vhosts/consolidadordeviajes.com/httpdocs/src/Consolidador/PanelBundle/Controller/HotelsController.php on line 149 and defined
This is the service that stated in app/config/services.yml:
integration:
class: Consolidador\PanelBundle\Controller\Integration
arguments: ['#doctrine', '#session']
This is the custom class:
<?php
namespace Consolidador\PanelBundle\Controller;
use Doctrine\Bundle\DoctrineBundle\Registry as Doctrine;
use Symfony\Component\HttpFoundation\Session\Session;
class Integration
{
private $em;
private $session;
public function __construct(Doctrine $doctrine, Session $session)
{
$this->em = $doctrine->getEntityManager();
$this->session = $session;
}
...
When I call the custom class is when the error occurs, I think I'm stating the service and collecting the parameters correctly, if not, I hope that other eyes may see the error that I get not find.
A greeting and thanks.
SOLUTION
The problem was simply that instead of calling the custom class from e lcontendor dependency, trying instalanciarla manually. Now I know that the services have to call them:
$foo= $this->get('service_name');
$foo-Method();

Is it possible to mock our own service?

I want to mock a service that is required in a class constructor. I have an exception of PHPUnit : MyService is required, Mock_MyService_0afc7fc1 given.
But with the Request, EntityManager or other Symfony 2 component, I haven't this issue.
Here is my Class's construct :
use Acme\Bundle\Service\MyService;
use Symfony\Component\HttpFoundation\Request;
...
public function __construct(MyService $service, Request $request)
{
and my mock :
...
$service = $this->getMock('MyService');
$class = new Class($service, $request);
It's impossible to mock our own service ? Only Symfony 2 component ?
PS : If I delete MyServicelike that : public function __construct($service, Request $request), this works. But I want to define my variable with it :(
The issue is that PHPUnit at the time of the test execution can't find (or autoload) your MyService class.
That means that you'll probably run into the same issues with other Mocking libraries as all of them require the original class to exist to scan it and create the mock.
It happens because you need to tell PHPUnit the Fully-Qualified Class Name.
Change your code to $this->getMock("\Acme\Bundle\Service\MyService"); and it should work out.
(Still, give mockery a try. It's a nice library)

How to inject the #request into a service?

When I try to inject the #request into any of my services, I get this exception:
ScopeWideningInjectionException: Scope Widening Injection detected:
The definition "service.navigation" references the service "request"
which belongs to a narrower scope. Generally, it is safer to either
move "service.navigation" to scope "request" or alternatively rely on
the provider pattern by injecting the container itself, and requesting
the service "request" each time it is needed. In rare, special cases
however that might not be necessary, then you can set the reference to
strict=false to get rid of this error.
What is the best way to proceed? Should I try to set this strict=false and how, or should I NOT inject the request service, but rather pass it to the service through my controller each time I call functions I need?
Other possibility would be to inject the kernel and take it from there, but in my service I am using only #router and #request, so injecting the whole kernel would be irrational.
In Symfony 2.4, this has changed. Now, you can inject the 'request_stack' service.
For example:
use Symfony\Component\HttpFoundation\RequestStack;
class MyService
{
protected $request;
public function setRequest(RequestStack $request_stack)
{
$this->request = $request_stack->getCurrentRequest();
}
}
In your config.yml:
services:
my.service:
class: Acme\DemoBundle\MyService
calls:
- [setRequest, ["#request_stack"]]
Full documentation is here: http://symfony.com/blog/new-in-symfony-2-4-the-request-stack
I think there may have been some misunderstanding about what the official documentation says. In most cases you do want to inject the request directly with a scope="request" attribute on the service element. This makes the Scope Widening go away.
<service
id="zayso_core.openid.rpx"
class="Zayso\CoreBundle\Component\OpenidRpx" public="true" scope="request">
or in yml
zayso_core.openid.rpx:
class: Zayso\CoreBundle\Component\OpenidRpx
public: true
scope: request
It's only in specific special cases such as Twig extensions where you need to inject the container.
And kernel is not even mentioned in the page on scopes. Injecting the kernel is far worse (conceptually) than injecting a container.
UPDATE: For S2.4 and newer, use #Blowski's answer below.
NB: This answer was written back in 2012, when Symfony 2.0 was out and then it was the good way to do!
According to the official documentation it is usually not required to inject request into your services. In your service class you can pass kernel container (injecting it is not a big overhead, as it sounds), and then access request like this:
public function __construct(\AppKernel $kernel)
{
$this->kernel = $kernel;
}
public function getRequest()
{
if ($this->kernel->getContainer()->has('request')) {
$request = $this->kernel->getContainer()->get('request');
} else {
$request = Request::createFromGlobals();
}
return $request;
}
This code is also working fine when service is accessed in CLI (eg, during unit-testing).
The best way i found to make a service use the request service, not rely on the whole container and still not be required to have the request scope, was to make a RequestInjector service which takes the container. then you inject that into the service that wants to use the request object
class RequestInjector{
protected $container;
public function __construct(Container $container){
$this->container = $container;
}
public function getRequest(){
return $this->container->get('request');
}
}
class SomeService{
protected $requestInjector;
public function __construct(RequestInjector $requestInjector){
$this->requestInjector = $requestInjector;
}
}
for services.yml
request_injector:
class: RequestInjector
public: false
arguments: ['#service_container']
some_service:
class: SomeService
arguments: ['#request_injector']
The way I've found, and I'm sure it's probably not the best way (May not even be recommended), is to define the request service as synthetic.
Edit: Indeed, this is not recommended, because it disables the scope sanity checks.
This thread contains a good explanation of why Symfony is throwing that exception:
http://groups.google.com/group/symfony-devs/browse_thread/thread/a7207406c82ef07a/e2626c00f5cb9749
In your services.xml:
<service id="request" synthetic="true" />
<service id="my_service" class="......">
<argument type="service" id="request" />
</service>
Per the docs, it's better if you place your service in the request scope, or just inject the service container.
If you can't use RequestStack directly, you could create a factory service that returns the current request using RequestStack.
# services.yml
app.request:
class: Symfony\Component\HttpFoundation\RequestStack
factory: [ #request_stack, getCurrentRequest ]
Then you can access the current request using the app.request service.
another way to inject currentRequest directly:
setter injection:
calls:
- ['setRequest', ['#=service("request_stack").getCurrentRequest()']]
or constrauctor injection:
arguments:
$request: '#=service("request_stack").getCurrentRequest()'
I think it's more important to focus on getting the request instead of setting it. I would do something similar to #Blowski's solution, except using a getter. This is very similar to the documentation's example.
namespace Acme\HelloBundle\Newsletter;
use Symfony\Component\HttpFoundation\RequestStack;
class NewsletterManager
{
protected $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
protected function getRequest()
{
return $this->requestStack->getCurrentRequest();
}
public function foo()
{
$request = $this->getRequest();
// Do something with the request
}
}
And your services.yml config file.
services:
newsletter_manager:
class: Acme\HelloBundle\Newsletter\NewsletterManager
arguments: ["#request_stack"]
Now you're always sure that you're getting the correct request, and you don't have to worry about setting/re-setting the request.
As #simshaun states its best practice to place your service in the request scope. This makes the purpose of the service quite clear.
Note that this will make your service unavailable in other scopes such as the command line. However if your service relies upon the request, you should not be using it on the command line anyway (because there is no request available on the command line.

Circular Reference when injecting Security Context into (Entity Listener) Class

There was 2 questions here saying injecting the whole service container should solve this. But question ... see below (note difference between try 2 & 3) ...
Try 1
public function __construct(SecurityContext $securityContext) {
$this->securityContext = $securityContext);
}
Curcular Reference. Okay ...
Try 2
public function __construct(ContainerInterface $container) {
$this->securityContext = $container->get('security.context');
}
Circular Reference (Why?, I am injecting the container like in try 3 except I got the security context only)
Try 3
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
Works.
This happens because your security context depends on this listener, probably via the entity manager being injected into a user provider. The best solution is to inject the container into the listener and access the security context lazily.
I typically don't like injecting the entire container into a service, but make an exception with Doctrine listeners because they are eagerly loaded and should therefore be as lazy as possible.
As of Symfony 2.6 this issue should be fixed. A pull request has just been accepted into the master. Your problem is described in here.
https://github.com/symfony/symfony/pull/11690
As of Symfony 2.6, you can inject the security.token_storage into your listener. This service will contain the token as used by the SecurityContext in <=2.5. In 3.0 this service will replace the SecurityContext::getToken() altogether. You can see a basic change list here: http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements#deprecated-the-security-context-service
Example usage in 2.6:
Your configuration:
services:
my.listener:
class: EntityListener
arguments:
- "#security.token_storage"
tags:
- { name: doctrine.event_listener, event: prePersist }
Your Listener
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class EntityListener
{
private $token_storage;
public function __construct(TokenStorageInterface $token_storage)
{
$this->token_storage = $token_storage;
}
public function prePersist(LifeCycleEventArgs $args)
{
$entity = $args->getEntity();
$entity->setCreatedBy($this->token_storage->getToken()->getUsername());
}
}
The reason "2" fails and "3" does not is because in option 2 you are trying to access the security context immediately from the container when it is likely not populated yet.
As best I can tell, Symfony2 parses through the config and instantiates the service one after the other and then moves onto the handling the rest of the request.
This means you cannot necessarily access the various parts of the container because it may be loading them in a different order. So you have the memory pointer to the container, and store that, but then let the framework finish building the full container before you try to access parts of it. A notable exception to this is when you directly inject the service into another service, at which point the container is making sure it has that service loaded first.
You can see the effects of this by making two services. A and B. A is passed B, and B is passed A. Now you have a circular reference. If you instead passed the container into both A and B, you could not access A from B and B from A without a problem.
You should always try to avoid injecting container directly to your services.
I think the best possible solution to the «circular reference» problem as well as to possible performance issues, would be to use «Lazy Services» feature available starting from Symfony 2.3.
Just mark you dependency as lazy in your service container configuration and install ProxyManager Bridge (look for details in Lazy Services documentation above).
I hope that helps, cheers.

Resources