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.
Related
Intro: Custom user implementation to be able to use and Wordpress users:
In our project, we have implemented a custom user provider (for Wordpress users - implements UserProviderInterface) with corresponding custom user (WordpressUser implements UserInterface, EquatableInterface). I have setup a firewall in the security.yml and implemented several voters.
# app/config/security.yml
security:
providers:
wordpress:
id: my_wordpress_user_provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
default:
anonymous: ~
http_basic: ~
form_login:
login_path: /account
Functional phpunit testing:
So far so good - but now the tricky part: mocking authenticated (Wordpress) users in functional phpunit tests. I have succeeded mocking the WordpressUserProvider so a mocked WordpressUser will be returned on loadUserByUsername(..). In our BaseTestCase (extends WebTestCase) the mocked WordpressUser gets authenticated and the token is stored to session.
//in: class BaseTestCase extends WebTestCase
/**
* Login Wordpress user
* #param WordpressUser $wpUser
*/
private function _logIn(WordpressUser $wpUser)
{
$session = self::get('session');
$firewall = 'default';
$token = new UsernamePasswordToken($wpUser, $wpUser->getPassword(), $firewall, $wpUser->getRoles());
$session->set('_security_' . $firewall, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
self::$_client->getCookieJar()->set($cookie);
}
The problem: losing session data on new request:
The simple tests succeed on the authentication part. Until tests with a redirect. The user is only authenticated one request, and 'forgotten' after a redirect. This is because the Symfony2 test client will shutdown() and boot() the kernel on each request, and in this way, the session gets lost.
Workarounds/solutions:
In a solution provided in question 12680675 only user ID should be used for the UsernamePasswordToken(..) to solve this. Our project needs the full user object.
In the solution provided in Unable to simulate HTTP authentication in functional test the basic HTTP authentication is used. In this case the full user object - including roles - cannot be used.
As suggested by Isolation of tests in Symfony2 you can persist instances by overriding the doRequest() method in the test client. As suggested I have created a custom test client and made an override on the doRequest() method.
Custom test client to 'store' session data between requests:
namespace NS\MyBundle\Tests;
use Symfony\Bundle\FrameworkBundle\Client as BaseClient;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Class Client
* Overrides and extends the default Test Client
* #package NS\MyBundle\Tests
*/
class Client extends BaseClient
{
static protected $session;
protected $requested = false;
/**
* {#inheritdoc}
*
* #param Request $request A Request instance
*
* #return Response A Response instance
*/
protected function doRequest($request)
{
if ($this->requested) {
$this->kernel->shutdown();
$this->kernel->boot();
}
$this->injectSession();
$this->requested = true;
return $this->kernel->handle($request);
}
/**
* Inject existing session for request
*/
protected function injectSession()
{
if (null === self::$session) {
self::$session = $this->getContainer()->get('session');
} else {
$this->getContainer()->set('session', self::$session);
}
}
}
Without the if statement holding the shutdown() and boot() calls, this method is working more or less. There are some weird problems where $_SERVER index keys cannot be found so I would like to properly re-instantiate the kernel container for other aspects of the system. While keeping the if statement, users cannot be authenticated, though the session data is the same before and during/after the request (checked by var_export to log).
Question(s):
What am I missing in this this approach that causes the authentication to fail? Is the authentication (and session check) done directly on/after kernel boot() or am I missing something else? Does anyone has another/better solution to keep the session intact so users will be authenticated in functional tests? Thank you in advance for your answer.
--EDIT--
In addition: the session storage for the test environment is set to session.storage.mock_file. In this way, the session should already be persisted between requests as describe by Symfony2 components here. When checked in the test after a (second) request, the session seems to be intact (but somehow ignored by the authentication layer?).
# app/config/config_test.yml
# ..
framework:
test: ~
session:
storage_id: session.storage.mock_file
profiler:
collect: false
web_profiler:
toolbar: false
intercept_redirects: false
# ..
My assumptions were close; it was not the session that was not persisted, the problem was in the case that mocked services are 'erased' by the kernel at a fresh request. This is the basic behaviour of functional phpunit testing...
I found out that this had to be the problem while debugging in the Symfony\Component\Security\Http\Firewall\AccessListener. There the token was found, and the (not anymore) mocked custom WordpressUser was there - empty. This explains why setting the username only instead of user object worked in the suggested workarounds stated above (no need of the mocked User class).
Solution
First of all, you don't need to override the Client as suggested in my question above. To be able to persist your mocked classes, you will have to extend the AppKernel and make some sort of kernel-modifier override with a closure as parameter. There is an explanation here on LyRiXx Blog. After injecting with a closure, you could restore the service mock after a request.
// /app/AppTestKernel.php
/**
* Extend the kernel so a service mock can be restored into the container
* after a request.
*/
require_once __DIR__.'/AppKernel.php';
class AppTestKernel extends AppKernel
{
private $kernelModifier = null;
public function boot()
{
parent::boot();
if ($kernelModifier = $this->kernelModifier) {
$kernelModifier($this);
};
}
/**
* Inject with closure
* Next request will restore the injected services
*
* #param callable $kernelModifier
*/
public function setKernelModifier(\Closure $kernelModifier)
{
$this->kernelModifier = $kernelModifier;
}
}
Usage (in your functional test):
$mock = $this->getMockBuilder(..);
..
static::$kernel->setKernelModifier(function($kernel) use ($mock) {
$kernel->getContainer()->set('bundle_service_name', $mock);
});
I still have to tweak the class and extended WebTestCase class, but this seems to work for me. I hope I can point someone else in the right(?) direction with this answer.
I am trying to understand how HWIOauthBUndle works. I can see how the initial authorization request to a resource owner is built and made.
I do not see however, how a callback made from a resource owner triggers any controller/action in my application (which it most obviously does, though).
When following the generally available instructions, the callback will be made to something like <path to my app>/check-[resourceOwner], e.g. http://www.example.com/oauth/check-facebook.
In my routing.yml file, I put
facebook_login:
pattern: /oauth/check-facebook
I don't see how any controller is associated with that route, so what actually happens when a callback is made to my application?
The authentication provider system is one of the more complicated features. You will probably want to read through here: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html
Callbacks are handled through a request listener. Specifically:
namespace HWI\Bundle\OAuthBundle\Security\Http\Firewall\OAuthListener;
use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
class OAuthListener extends AbstractAuthenticationListener
{
public function requiresAuthentication(Request $request)
{
// Check if the route matches one of the check paths
foreach ($this->checkPaths as $checkPath) {
if ($this->httpUtils->checkRequestPath($request, $checkPath)) {
return true;
}
}
return false;
}
protected function attemptAuthentication(Request $request)
{
// Lots of good stuff here
How checkPaths get's initialized and how all the calls are made would require a very long explanation. But the authentication provider chapter will get you going.
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.
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)
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.