Shopware: Symfony2 Dependency Injection im Plugin nutzen

Sa, 22.11.2014 - 14:00 -- Daniel Espendiller

shopware symfony2 dependency injectionIn Shopware wird die Symfony2 Service Container Komponente genutzt. Die Core Services lassen sich allerdings nur bedingt beeinflussen, wie man in Kernel::buildContainer sehen kann.
So kann man eine eigene Container Datei in /Components/DependencyInjection/services_local.xml anlegen. Dies ist nicht unbedingt praktisch für Plugins. Auch aktuell (4.3.x) gibt es keine richtige Möglichkeit dies irgendwie zu umgeben. Somit muss man sich in Plugins einen eigenen Container zusammensetzen und die Shopware Core Services bei Bedarf übergeben.

Eigener Symfony2 Service Container

Wir setzen uns also zu erst einen eigenen ContainerBuilder zusammen. Hier geben wir beispielhaft ein paar Shopware Services mit. Zusätzlich setzen wir auch gleich ein Event, somit kann unser Container zumindest von anderen Plugins erweitert werden. Das ganze lassen wir über eine Singleton Instanz laufen.

// engine/Shopware/Plugins/Local/Frontend/FooPlugin/Container.php
 
class Shopware_Plugins_Frontend_FooPlugin_Container
{
 
    /**
     * @var \Symfony\Component\DependencyInjection\ContainerInterface
     */
    protected static $instance = null;
 
    /**
     * Get container singleton
     *
     * @return \Symfony\Component\DependencyInjection\ContainerBuilder
     */
    public static function Instance()
    {
        static $instance = null;
        if ($instance === null) {
            $instance = new \Symfony\Component\DependencyInjection\ContainerBuilder();
 
            $loader = new \Symfony\Component\DependencyInjection\Loader\XmlFileLoader($instance, new \Symfony\Component\Config\FileLocator(__DIR__));
            $loader->load('container.xml');
 
            $instance->set('models', Shopware()->Container()->get('models'));
            $instance->set('config', Shopware()->Container()->get('config'));
 
            Shopware()->Container()->get('events')->notify('Plugin_Container_Init', new Enlight_Event_EventArgs(array(
                'containerBuilder' => $instance,
            )));
 
            $instance->compile();
 
        }
 
        return $instance;
    }
 
    /**
     * empty class on for secure singletone
     */
    private function __construct()
    {
    }
 
    /**
     * empty class on for secure singletone
     */
    protected function __clone()
    {
    }
 
}

Container als XML

Der ContainerBuilder nutzt also die container.xml und setzt die Services zusammen. Shopware Core Services lassen sich nutzen soweit übergeben.

// engine/Shopware/Plugins/Local/Frontend/FooPlugin/container.xml
 
<?xml version="1.0" ?>
 
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
 
    <parameters>
        <parameter key="cache_lifetime">10</parameter>
    </parameters>
 
    <services>
        <service id="models" synthetic="true"/>
 
        <service id="foo_class" class="\Shopware_Plugins_Frontend_FooPlugin_Classes_BarClass">
            <argument type="service" id="models"/>
            <argument>%cache_lifetime%</argument>
        </service>
 
    </services>
 
</container>

CompilerPass: ContainerBuilder Event nutzen

Im Symfony2 full-stack gibt es Bundles und einen CompilerPass damit der Container erweitert werden kann. Wir sollen es bei unserer Implementierung nicht übertreiben und übergeben einfach den ContainerBuilder. Wie man sehen kann, ist es hier auch wieder möglich externe XML files nachladen zu lassen.

        $this->subscribeEvent(
            'Plugin_Container_Init',
            'onPluginContainerInit'
        );
 
    public function onPluginContainerInit(Enlight_Event_EventArgs $args)
    {
        /** @var \Symfony\Component\DependencyInjection\ContainerBuilder $instance */
        $instance = $args->get('containerBuilder');
 
        $instance->setParameter('plugin.dir', __DIR__);
        $instance->set('shopware.api.customer_resource', \Shopware\Components\Api\Manager::getResource('Customer'));
 
        $loader = new \Symfony\Component\DependencyInjection\Loader\XmlFileLoader($instance, new \Symfony\Component\Config\FileLocator(__DIR__));
        $loader->load('container.xml');
    }

Singleton in Bootstrap

Zuletzt definieren wir uns noch eine Proxy Methode um den Container über eine Singleton Instanz im Plugin nutzen zu können.

use Shopware_Plugins_Frontend_FooPlugin_Container as Container;
 
class Shopware_Plugins_Frontend_FooPlugin_Bootstrap extends Shopware_Components_Plugin_Bootstrap
{
    /**
     * Instance helper for dic container
     *
     * @return \Symfony\Component\DependencyInjection\ContainerBuilder
     */
    public static function Container()
    {
        return Container::Instance();
    }
 
}

Somit können wir dann überall im Plugin auf die Services zugreifen

Shopware_Plugins_Frontend_FooPlugin_Bootstrap::get('foo_class')

Disqus - noscript

it has a bug on the latest phpstorm version and it crashes everytime with "AWT events are not allowed inside write action: java.awt.event.InvocationEvent[INVOCATION_DEFAULT,runnable=com.intellij.openapi.util.ActionCallback$$Lambda$291/1302534651@18f3a1f6,notifier=null,catchExceptions=false,when=1490920643352] on sun.lwawt.macosx.LWCToolkit@27c5f8a
java.lang.Throwable"