2012-02-17 16 views
47

Quando provo a iniettare il @request in uno qualsiasi dei miei servizi, ottengo questa eccezione:Come iniettare @request in un servizio?

ScopeWideningInjectionException: progressivo ampliamento iniezione rilevato: La definizione "service.navigation" fa riferimento al servizio di "richiesta" che appartiene a un ambito più ristretto. Generalmente, è più sicuro spostare "service.navigation" su scope "request" o fare affidamento su il modello del provider iniettando il contenitore stesso e richiedendo il servizio "richiesta" ogni volta che è necessario. In rari casi speciali tuttavia potrebbe non essere necessario, quindi è possibile impostare il riferimento a strict = false per sbarazzarsi di questo errore.

Qual è il modo migliore per procedere? Dovrei provare a impostare questo strict=false e come, o NON dovrei iniettare il servizio di richiesta, ma piuttosto passarlo al servizio attraverso il mio controller ogni volta che chiamo funzioni di cui ho bisogno?

Un'altra possibilità sarebbe quella di iniettare il kernel e portarlo da lì, ma nel mio servizio sto usando solo @router e @request, quindi iniettare l'intero kernel sarebbe irrazionale.

Grazie!

risposta

30

Penso che possa esserci stato qualche malinteso su ciò che dice la documentazione ufficiale. Nella maggior parte dei casi, si desidera iniettare la richiesta direttamente con un attributo scope="request" sull'elemento del servizio.Questo fa sparire l'ampliamento dell'oscilloscopio.

<service 
    id="zayso_core.openid.rpx" 
    class="Zayso\CoreBundle\Component\OpenidRpx" public="true" scope="request"> 

o in yml

zayso_core.openid.rpx: 
    class: Zayso\CoreBundle\Component\OpenidRpx 
    public: true 
    scope: request 

E 'solo in casi particolari specifici come le estensioni di Twig in cui è necessario iniettare il contenitore.

E il kernel non è nemmeno menzionato nella pagina sugli ambiti. Iniettare il kernel è molto peggio (concettualmente) dell'iniettare un contenitore.

AGGIORNAMENTO: per S2.4 e versioni successive, utilizzare la risposta di @ Blowski di seguito.

+0

Sembra interessante, mi dispiace, ma potresti riscriverlo in YAML? Ho provato un paio di dichiarazioni del genere (trovate su altri siti), ma non riesco a farlo bene. –

+6

Che funziona meravigliosamente, in YAML sarebbe qualcosa di simile: 'service.sample: class: Company \ SiteBundle \ Services \ Esempi di esempio: [@request] public: true scope: request' –

+0

ci sono altri valori per scope? cercando di eseguire un servizio dalla CLI –

5

Il modo in cui ho trovato, e sono sicuro che probabilmente non è il modo migliore (potrebbe anche non essere raccomandato), è quello di definire il servizio di richiesta come sintetico.

Modifica: In effetti, questo non è consigliato, poiché disabilita i controlli di integrità dell'ambito. Questo thread contiene una buona spiegazione del perché Symfony sta gettando tale eccezione: http://groups.google.com/group/symfony-devs/browse_thread/thread/a7207406c82ef07a/e2626c00f5cb9749

Nel vostro services.xml:

<service id="request" synthetic="true" /> 

<service id="my_service" class="......"> 
    <argument type="service" id="request" /> 
</service> 

Per il docs, è meglio se si posiziona il servizio nell'ambito della richiesta o semplicemente iniettare il contenitore del servizio.

+0

A questo punto, questo potrebbe essere un commento migliore di una risposta. – Starx

5

NB: Questa risposta è stata scritta nel 2012, quando Symfony 2.0 era fuori e quindi era il modo migliore per farlo! Per favore, non più a valle :)


Oggi ho subito lo stesso problema, quindi ecco i miei 5 centesimi. Secondo lo official documentation di solito non è necessario iniettare request nei tuoi servizi. Nella tua classe di servizio è possibile passare kernel contenitore (l'iniezione non è una grande testa, come sembra), e quindi accedere request come questo:

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; 
} 

Questo codice è anche lavorando bene quando il servizio si accede a CLI (ad es. durante i test unitari).

+3

Sì, funziona. Solo una piccola modifica: poiché 'kernel -> getContainer()' restituisce un contenitore_servizio, è meglio iniettarlo al posto del kernel (come indicato nella documentazione). Ho già fatto 'argomenti: [@service_container]' e funziona perfettamente - non c'è bisogno di passare attraverso il kernel. –

+0

Hai ragione. Grazie per il consiglio! –

+0

Abbastanza sbagliato, con Symfony 2.1.x e un comando CLI, 'has' restituisce' true', e il successivo 'get ('request')' genera un'eccezione. – gremo

-1

Come @simshaun afferma la sua migliore pratica per inserire il servizio nell'ambito della richiesta. Questo rende lo scopo del servizio abbastanza chiaro.

Nota che ciò renderà il servizio non disponibile in altri ambiti come la riga di comando. Tuttavia, se il tuo servizio si basa sulla richiesta, non dovresti comunque utilizzarlo sulla riga di comando (

+0

Qualche idea su come farlo? –

6

Il modo migliore in cui ho trovato un servizio utilizza il servizio di richiesta, si basa su tutto il contenitore e non essere ancora tenuti ad avere la portata richiesta, era quello di rendere un servizio RequestInjector che prende il contenitore. allora si inietta che nel servizio che vuole utilizzare l'oggetto di richiesta

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; 

    } 
}  

per services.yml

request_injector: 
    class: RequestInjector 
    public: false 
    arguments: ['@service_container'] 

some_service: 
    class: SomeService 
    arguments: ['@request_injector'] 
+5

Non mi piace questo approccio, in pratica si inietta ancora l'intero contenitore ad un certo punto e si usa l'anti-pattern di Service Locator, semplicemente non lo si conosce più. Non vedo un vantaggio in questo! –

93

In Symfony 2.4, questo è cambiato. Ora puoi inserire il servizio 'request_stack'.

Ad esempio:

use Symfony\Component\HttpFoundation\RequestStack; 

class MyService 
{ 

    protected $request; 

    public function setRequest(RequestStack $request_stack) 
    { 
     $this->request = $request_stack->getCurrentRequest(); 
    } 

} 

Nel vostro config.yml:

services: 
    my.service: 
     class: Acme\DemoBundle\MyService 
     calls: 
      - [setRequest, ["@request_stack"]] 

documentazione completa è qui: http://symfony.com/blog/new-in-symfony-2-4-the-request-stack

+2

+1 per quella soluzione e link al post del blog di symfony appropriato. – sergekv

+0

Il problema qui è che questo viene eseguito dopo il costruttore, quindi se è necessaria una richiesta all'interno del costruttore questa soluzione non è completa –

+3

@ K.Weber è possibile iniettare 'request_stack' in un'azione di costruzione, o effettivamente prima che il costruttore sia eseguito (dì, in un ascoltatore 'kernel.request'). –

4

Se non è possibile utilizzare direttamente RequestStack, è possibile creare un servizio di fabbrica che restituisce la richiesta corrente utilizzando RequestStack.

# services.yml 
app.request: 
    class: Symfony\Component\HttpFoundation\RequestStack 
    factory: [ @request_stack, getCurrentRequest ] 

Quindi è possibile accedere alla richiesta di corrente utilizzando il servizio app.request.

+0

Sei un vero MVP, amico! – niconoe

1

Penso che sia più importante concentrarsi su come ottenere la richiesta invece di impostarla. Vorrei fare qualcosa di simile alla soluzione di @ Blowski, tranne che usando un getter. Questo è molto simile all'esempio di documentation.

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 
    } 
} 

e il file di configurazione services.yml.

services: 
    newsletter_manager: 
     class:  Acme\HelloBundle\Newsletter\NewsletterManager 
     arguments: ["@request_stack"] 

Ora siete sempre sicuri che stai ricevendo la richiesta corretta, e non si deve preoccupare di impostare/re-impostare la richiesta.

Problemi correlati