9

The story

Let's take this request https://nike.awesomedomainname.com/ as an example. I need a service that can fetch the nike.


What I currently have

I've a service that fetches a router key from the @request_stack. Let's use company as router key.

My main route as followed;

<import host="{company}.domain.{_tld}" prefix="/" schemes="https" resource="@ExampleBundle/Resources/config/routing.xml">
    <default key="_tld">%app_tld%</default>
    <requirement key="_tld">com|dev</requirement>
    <requirement key="company">[a-z0-9]+</requirement>
</import>

So I wrote this service to fetch the key and search for the company;

...

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;

class CompanyContextRequest {

    protected $request;
    protected $companyRepository;

    public function __construct(RequestStack $requestStack, CompanyRepository $companyRepository) {
        $this->request = $requestStack->getMasterRequest();
        $this->companyRepository = $companyRepository;
    }

    public function getCompany()
    {
        if (null !== $this->request)
        {
            $company = $this->request->attributes->get('company');
            if (null !== $company)
            {
                return $this->companyRepository->findOneBy([
                    'key' => $company
                ]);
            }
        }
    }

    ....

}

And the service xml definition;

<service id="app.company_context_request" class="AppBundle\Request\CompanyContextRequest">
    <argument type="service" id="request_stack"/>
    <argument type="service" id="orm_entity.company_repository"/>
</service>
<service id="app.current_company" class="AppBundle\Entity\Company">
    <factory service="app.company_context_request" method="getCompany"/>
</service>

Now my problem is that in some cases the service app.current_company doesn't return a Company object instead a null is given.

For example I have a custom UserProvider that has the CompanyContextRequest as a dependency to check if the user belongs to the company but I can't do so because it returns null.

But it works perfectly in controllers and most other DI places.

Any suggestions? Is there something like priority for services like for event subscribers?


What I've tried

I've set the scope app.company_context_request into request and tried to fetch it from the container. With false results. According to the Symfony docs this should work.


I run the latest stable version of Symfony v2.7.3


Edit: My goal is to have a service at all times that can tell me the current company that is based ofcourse on the subdomain.

Edit 2: This example is almost what I need except that in this example they use the users company and I need the company(key) from the subdomain.

Katch
  • 170
  • 1
  • 20
  • Don't you need instead to create a dependency between UserProvider and app.current_company which would be the current company according to the request, and then check if your user is linked to the current company ? Also, did you check if, while being in your USerProvider business logic, you had the right parameters (e.g. company and tld) in your request ? – MeuhMeuh Jul 30 '15 at 07:47
  • @MeuhMeuh that is what I'm doing with `app.current_company` is an instance of Company entity. But since the container is compiled before the request is made the `company` attribute in the current request is `null`. That's why I need an solution regarding my dependency on the request. – Katch Jul 30 '15 at 09:20
  • when does it return null? – Andrew Atkinson Jul 30 '15 at 09:52
  • Isn't it a scope problem ? Did-you try : – j-guyon Jul 30 '15 at 10:04
  • Is your company is `NULL` when you call the service in a sub request by calling PHP or Twig `render(controller('FooBar'))` ? – Divi Jul 30 '15 at 10:41
  • @AndrewAtkinson when the `UserProvider` is called by the firewall. @J-Mose No that didn't solve it and my other services would also required to be from the same scope `request` or an narrower scope or you'll get a `ScopeWideningException`. @Divi calling the `app.current_company` in the controller/twig works. – Katch Jul 30 '15 at 11:13
  • have you try to debug the lifecycle of app.company_context_request service ? Try to find out (in case when you get null) where your class CompanyContextRequest access in that nested if conditions – vardius Aug 01 '15 at 10:22
  • @Vardius I did `dump` the masterRequest in the services constructor and received empty attributes, request and query. – Katch Aug 01 '15 at 11:57
  • So im pretty sure its the scope problem. As @J-Mose said it before. You are invokeing your service before request is even ready. As symfony docs says `Outside the handling of a request, $requestStack->getCurrentRequest() returns null.` -> http://symfony.com/blog/new-in-symfony-2-4-the-request-stack – vardius Aug 01 '15 at 14:30
  • @Vardius that did sound completely logical indeed! As so I did change my service's scope into `request` leaving me also with a disappointing result: `null`. Any chance of a working example or another solution? – Katch Aug 02 '15 at 18:36

2 Answers2

3

The problem that your service app.company_context_request is synthetic by nature - you cannot compile cause there is no request data at this moment.

How to Inject Instances into the Container

Why does it work in some places?

It works for cases when you call the services in the scope: after synthetic service Request was inserted into Service Container and scope request was activated. request scope setup and RequestStack filling happen into \Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel::handle method and its parent one.

UserProvider doesn't work cause its initialization happens out of request scope: before Request service is activated or after the app left the scope.

How to prevent it?

  1. Save RequestStack service to the service property and get current request not in the constructor but in the getCompany method - at the moment of calling constructor Request could be undefined;

    public function __construct(RequestStack $requestStack, CompanyRepository $companyRepository) 
    {
        // You cannot use request at this point cause at initialization of the service there could be no request yet
        $this->requestStack = $requestStack;
        $this->companyRepository = $companyRepository;
    }
    ...
    public function getCompany()
    {
        $request = $this->requestStack->getMasterRequest();
        if (null !== $request)
    ...
    
  2. Add to service definition scope request and get it with via container inside another services: it will autogenerate exceptions when any services will try to get the one out of scope; There some constraints you should be aware: How to Work with Scopes This way is used in FOS bundles, for instance, FOSOauthServer.

If you will change code as in 1 then you can left service definition as it is

<service id="app.company_context_request" class="AppBundle\Request\CompanyContextRequest">
    <argument type="service" id="request_stack"/>
    <argument type="service" id="orm_entity.company_repository"/>
</service>

But current_company should have scope. request to throw exception if Request is not defined at the moment of initialization of the current_company. Or you can use scope=prototype (it will return new instance on every call) if you want to get current_company in the moments when it is set and get null in the moments when Request doens't exist (out of scope, cli calls).

<service id="app.current_company" class="AppBundle\Entity\Company" scope="request">
    <factory service="app.company_context_request" method="getCompany"/>
</service>

UPD It is not related to service priorities at all. It is related with container compilation mechanism. Compilation of the certain service happens on first call of this service: on explicit call or on compilation of dependent service.

With UserProvider routes compilation of your service (so calling of the __construct) happens at the moment when Request object is not yet in RequestStack and in the Container.

origaminal
  • 2,065
  • 1
  • 11
  • 19
2

I don't really understand you question but if you want to set priority in YML is done like this

app.locale_listener:
    class: Erik\AppBundle\Service\LocateListener
    arguments: ["%kernel.default_locale%"]
    tags:
        - { name: kernel.event_subscriber, priority: 17 }

--- Edit ---

As first fix your ssl setup "This Connection is Untrusted" under linux firefox there are online tools to check if ssl is configured correctly.

As second the answer to your question you can set up some service or event listener that will read subdomain name, in case of service just use $this->get("serviceName")->getSubdomain()

how to get subdomain PHP function to get the subdomain of a URL

Community
  • 1
  • 1
  • I need a service to know the "current company" for ex: you go to `https://nike.superawesomedomain.com/` I want the subdomain name from the router so I can check it up against the database and return an `Company` (entity) object. So no events/listeners. – Katch Aug 05 '15 at 10:50