2016-04-21 8 views
4

Ich versuche, wiederverwendbare Logging-Bundle, die benutzerdefinierte Formatierung für Log-Nachrichten haben können, zu erstellen. Formatter ist in der Haupt-App-Konfigurationsdatei so eingestellt:PHP-Symfony-Anfrage-Stack nicht gefunden bei Bundle-Erweiterung laden

custom_logger: 
    formatter: AppBundle\Services\MessageFormatter 

Dann in der LoggerBundle/DependencyInjection/CustomLoggerExtension.php nachdem ich erhalten diese Konfiguration ich versuche Logger Service und setzen Formatierer

class CustomLoggerExtension extends ConfigurableExtension 
{ 

    public function loadInternal(array $mergedConfig, ContainerBuilder $container) 
    { 
     $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); 
     $loader->load('services.yml'); 
     $class = $mergedConfig['formatter']; 
     $obj = new $class; 
     $container->get('custom.logger')->setFormatter($obj); 

aber die bekommen Problem ist, dass Logger Anfragestapel

services: 
    custom.logger: 
     class: LoggerBundle\Services\Logger 
     arguments: ['@request_stack'] 

verwendet und so, wenn ich versuche Service zu erhalten in loadInternal Funktion scheint es, dass der Anfrage-Stack noch nicht initialisiert ist und ich Fehler erhalte: You have requested a non-existent service "request_stack"

Was ist die richtige Art, dies zu tun?

EDIT (2016-04-21 21:54:11)

Das ist seltsam. Selbst wenn ich den Anforderungsstapel lösche und erfolgreich den Formatierer in loadInternal einstelle, ist er nicht im Dienst, wenn ich ihn vom Controller in der Haupt-App bekomme. Ich muss etwas wirklich falsch machen :)

+0

Welche Version von Symfony2 verwenden Sie? –

+0

@IaroslavGashuk 2.8.4 –

+0

ist Ihr Bündel nach FrameworkBundle initialisiert? –

Antwort

0

Ok, ich denke, ich habe es herausgefunden.

Wenn load() oder wie in meinem Fall loadInternal() Methode aufgerufen wird, übergibt symfony dort komplett neue Container, die dann in den Hauptbereich zusammengeführt wird. Wenn ich also einen Dienst anfordere, der dort erstellt wird, wird er nirgendwo außer im temporären Container gespeichert und schließlich zerstört (nur Dienstdefinitionen werden an den Hauptdevice übergeben).

Da Dienste bei Bedarf geladen (erstellt) werden, ist es nicht richtig, sie beim Laden des Pakets zu instanziieren. Also, was wollen Sie stattdessen zu tun ist definitions

Hier zu konfigurieren ist, wie ich habe es funktioniert:

use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension; 
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 
use Symfony\Component\DependencyInjection\ContainerBuilder; 
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; 
use Symfony\Component\DependencyInjection\Reference; 
use Symfony\Component\DependencyInjection\Definition; 
use Symfony\Component\Config\FileLocator; 
use LoggerBundle\Formatter\MessageFormatterInterface; 

class LoggerBundleExtension extends ConfigurableExtension implements CompilerPassInterface 
{ 
    protected $customFormatter; 

    public function loadInternal(array $mergedConfig, ContainerBuilder $container) 
    { 
     $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); 
     $loader->load('services.yml'); 

     if (array_key_exists('formatter', $mergedConfig)) { 
      $this->customFormatter = $mergedConfig['formatter']; 
     } 
    } 

    public function process(ContainerBuilder $containerBuilder) 
    { 
     if ($this->customFormatter) { 
      $class = $this->customFormatter; 

      //if formatter is service id 
      $serviceUsed = $containerBuilder->has($this->customFormatter); 
      if ($serviceUsed) { 
       $class = $containerBuilder->getDefinition($this->customFormatter)->getClass(); 
      } 
      if (!class_exists($class) || !is_a($class, MessageFormatterInterface::class, true)) { 
       throw new \ErrorException('Invalid logger formatter'); 
      } 
      if ($serviceUsed) { 
       $containerBuilder 
        ->getDefinition('custom.logger') 
        ->addMethodCall('setFormatter', array(new Reference($this->customFormatter))); 
      } else { 
       $containerBuilder 
        ->getDefinition('custom.logger') 
        ->addMethodCall('setFormatter', array(new Definition($this->customFormatter))); 
      } 
     } 
    } 
} 

In loadInternal() ich überprüfen, ob Formatierer in Haupt-app config gesetzt und speichern. Aber Hauptsache passiert in process() Funktion (Sie müssen Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface implementieren, damit es ausgeführt wird).

Von der docs.

As process() is called after all extensions are loaded, it allows you to edit service definitions of other extensions as well as retrieving information about service definitions.

Also, was ich tue, ist:

  • prüft wird, ob formatter Optionswert ein Klassenname oder eine Service-ID ist (wenn das der Fall ist, rufe ich Klassennamen von Service-Definition)
  • validiere den Klassennamen (überprüfe, ob es eine gültige Klasse ist und stelle sicher, dass die erforderliche Schnittstelle implementiert wird)
  • Hinzufügen der Definition zu meinem Logger-Dienst, der den Container anweist, setFormatter mit bereitgestellten Argumenten aufzurufen, wenn der Logger-Dienst ist erstellt

Grundsätzlich $containerBuilder->getDefinition('custom.logger')->addMethodCall('setFormatter', array(new Reference('my_formatter')));

ist das gleiche wie erklärt in der Config (wenn es zunächst aus dem Behälter wiedergewonnen):

services: 
    custom.logger: 
     class: LoggerBundle\Services\Logger 
     arguments: ['@request_stack'] 
     calls: 
      - [setFormatter, ['@my_formatter']]