Plugin quick start guide

This document will give you a brief overview of Shopware and everything you need to start developing your first plugin.

Directory structure

On the left you can see Shopware's default directory structure, as you will find in GitHub checkouts or release packages. Let's start with a quick overview:

_sql: (Not in release packages) Contains various deltas and migrations to set up Shopware or migrate the database of an old Shopware version to the database of a new versions.

bin: Contains the console command which can be executed to run the Shopware command line tools

build: (Not in release packages) Contains our ant-based build engine with various scripts to e.g. install dependencies, reset the database etc. When installing Shopware from GitHub you want to run ant build-unit first. If you use the install package, you can just use our web-based installer

cache: Contains various caches and generated files like smarty compile cache, proxy caches, HTML cache etc.

engine/Library: Some libraries / dependencies which are not available in composer.

engine/Shopware: The actual Shopware application with subdirectories for our bundles, services, models and plugins.

files: Files for ESD products or generated documents are stored here

logs: Our logging directory. Whenever an exception occurs in the Shopware stack, you'll find the stack trace and some context in these log files.

media: Files uploaded via the Shopware MediaManager are stored in here (images, thumbnails and other uploads)

recovery: Our web-based update and install tool can be found here.

snippets: Contains Shopware snippets for frontend and backend in a simple INI format. Snippets will automatically be deployed to database during installation. (Not in release packages)

templates: The Shopware 4 template base (emotion)

tests: PHPUnit and Mink tests

themes: The Shopware 5 template base (bare, responsive)

vendor: Dependencies from composer. Shipped with the release package, git users will need to run composer install or ant build-unit

web: The concatenated and minified javascript and CSS templates

MVC

Shopware makes use of the MVC design pattern. For this reason, the representational aspect of the application (view), the controlling and user input (controller) as well as the data layer and business logic (model) are decoupled throughout the application.

View

Shopware uses Smarty as template engine. When using the new responsive template base, you will find all templates in themes. The old Shopware 4 template can still be found in templates.

Model

Doctrine is used as ORM and database abstraction layer. You will find the various Doctrine models in engine/Shopware/Models. The models are grouped by domain, so you will find article related models in the Article directory, customer related models in the Customer directory and so on. The business logic of Shopware can be found in Core, Components or Bundle - depending how tight the service in question is coupled to Shopware itself.

Controller

Controllers take care of user input of any kind, process the input in the model and respond with an answer which is usually generated by the template.

Shopware's controllers can be found in engine/Shopware/Controllers and are separated by module, which can be one of the following:

  • frontend (default)
  • widgets (for our ESI system)
  • backend (for the Shopware administrative panel)
  • api (for our REST API)

In Shopware, any request will hit a controller, depending on the type of the request. Therefore, three decisions are made:

  • which module is requested (default: frontend)
  • which controller is requested (default: Index)
  • which controller action should be called (default: index)

If module, controller or action are not explicitly defined, Shopware will fall back to the defaults mentioned above. For this reason, a call the the shop's root directory will be dispatched to the frontend module, index controller and index action. This is equivalent to the call to http://my-shop.com/frontend/index/index. In practice, however, these "technical" URLs are usually hidden by the SEO engine.

Inside a controller you have easy access to the Request and Response object, as well as to the DI container, so you can call other components and services.

Technologies

Shopware as an open source shopping system uses many well known libraries. We use Symfony components like the dependency injection container, the console tools and some other and are HTTP compliant with the Symfony HTTP kernel. Other well known libraries are also used and included in Shopware, like Guzzle HTTP client, Doctrine, Smarty, Monolog and Phpunit, so that most developers should feel quite comfortable regarding the used technologies. The actual HTTP stack of Shopware is currently powered by Zend Framework which Shopware uses with a thin layer called Enlight on top. As we plan to move towards Symfony step by step, Enlight might come in handy as a transitional framework.

Hooking into the system

Shopware uses plugins to extend the base systems. Changes in the core are never required and never recommended. Extensions of the core system can basically be separated into logical extensions, data extensions and template extensions.

Some global events are very powerful when it comes to extending Shopware's behaviour:

  • Enlight_Controller_Action_PreDispatch_*
  • Enlight_Controller_Action_PostDispatchSecure_*

These events will be emitted whenever a controller action is about to be called ("PreDispatch") or after the controller action was called ("PostDispatch"). This way it is very easy to get notified any time a certain controller is executed to perform some extensions:

$this->subscribeEvent(
    'Enlight_Controller_Action_PostDispatchSecure_Frontend_Detail',
    'myDetailCallback'
)

This snippet will call the callback function myDetailCallback any time the detail controller (and therefore: the detail page) was requested. In your callback you could now load template extensions or modify view assignments.

If you want to read more about the events in Shopware you can take a look at the events article.

Extending the database

Your plugin is free to create its own table in the Shopware database in order to store additional data. But there is even a more convenient way: The Shopware attribute system. Shopware attributes are basically tables in a OneToOne relation to important Shopware entities. So, for every entry in s_user (the customer table), there is also an entry in s_user_attributes. In Shopware, it is very easy to add a new column to this table and to automatically add it to the customer doctrine attribute model. Whenever Shopware reads a customer, article or another entity from database, it will also read the attributes, so that you can make use of attributes in many places and modules.

Extending the template

In many cases you might want to modify the template. Shopware makes use of the Smarty block system for that. Blocks are basically named areas inside the template that you can prepend, append or even replace. Shopware's default frontend theme has more than 1.500 blocks - so more than 1.500 extension points for you as a plugin developer.

A smarty block will usually look like this:

    {block name="Shopware_frontend_checkout_cart"}
        <div>
            Some HTML content
        </div>
    {/block}

Writing our first little plugin

The following example will show how to write a very simple plugin, which extends the frontend and adds a little "slogan" to the page.

The plugin Bootstrap

The main entry point of every plugin is the Bootstrap.php file in your plugin directory. This is placed in engine\Shopware\Plugins\Local\Frontend\SwagSloganOfTheDay.

We recommend developing plugins in the Local directory. There is no functional difference between this and the Community directory, so you could also place it there. The Frontend part of the path could also be Backend or Core - again there is no functional difference, its just for sorting and orientation. As we want to write a plugin which mainly extends the frontend, we will put it to the Frontend directory. SwagSloganOfTheDay finally is your plugin - the name needs to have at least a developer prefix (Swag) and a name (SloganOfTheDay).

In the Bootstrap.php file, we create a simple class:

 <?php

 class Shopware_Plugins_Frontend_SwagSloganOfTheDay_Bootstrap extends Shopware_Components_Plugin_Bootstrap
 {

 }

As you can see, the path of your Bootstrap file is reflected in the class name Shopware_Plugins_Frontend_SwagSloganOfTheDay_Bootstrap. Only the Local and engine subdirectory are not in the class name, anything else is included.

It's important to extend the Shopware_Components_Plugin_Bootstrap class, as it holds a lot of helper functions you will need for plugin development.

Now we add the first methods to the Bootstrap class:

    public function getVersion()
    {
        return '1.0.0';
    }

    public function getLabel()
    {
        return 'Slogan of the day';
    }

    public function install()
    {
        $this->subscribeEvent(
            'Enlight_Controller_Action_PostDispatchSecure_Frontend',
            'onFrontendPostDispatch'
        );

        $this->createConfig();

        return true;
    }

The getVersion method just needs to return your plugin version as a string. If you later upload your plugin to the store, Shopware will be able to offer customers updates of your plugin depending on the version.

getLabel should return the name of your plugin in a human readable way - SwagSloganOfTheDay might be a bit technical, so we choose Slogan of the day.

These are the only "metadata" methods your actually should implement, everything else is optional.

The install method

As we want our plugin to actually do something, we should add the install method in the next step. This method will be executed once during installation:

 public function install()
 {
     $this->subscribeEvent(
         'Enlight_Controller_Action_PostDispatchSecure_Frontend',
         'onFrontendPostDispatch'
     );

     $this->createConfig();

     return true;
 }

3 things happen here:

  • we subscribe to an event
  • we call the createConfig method
  • we return a success flag

In order to subscribe to an event, we use the $this->subscribeEvent helper method from the bootstrap base class. The first parameter is the event to subscribe to, in this case Enlight_Controller_Action_PostDispatchSecure_Frontend. As mentioned above, this event will be triggered every time after a controller in the frontend was executed. The second parameter is the callback function that should be called - onFrontendPostDispatch

The createConfig method will create the configuration for our plugin. We will have a look at it later.

Finally return true; will tell Shopware that everything went fine and no problems occurred. If you return false or just nothing, Shopware will not finish the plugin installation.

The update method

It is necessary to explain that files, removed in update versions, will not be automatically removed from file system while updating. This can cause problems with templates and rendering smarty. So if a template file is removed in a newer plugin version, it is necessary to remove the deleted template during the update process manually.

A working example could be like what we did in our SwagBundle plugin.

/**
     * Update function of the bundle plugin
     *
     * @param string $oldVersion
     * @return bool|void
     * @throws Exception
     */
    public function update($oldVersion)
    {
        ...

        // 5.2+ specific: manually delete old files, which are not included in this version anymore.
        if (version_compare($oldVersion, '3.2.0', '<')) {
            $this->unlinkOldFiles($oldVersion);
        }

       ...
    }

    /**
     * Unlinks old files which are not available for this version anymore.
     * @param string $oldVersion
     */
    private function unlinkOldFiles($oldVersion)
    {
        $filesToDelete = [];

        //Initial version for the 5.2 Plugin version.
        if (version_compare($oldVersion, '3.2.0', '<')) {
            $filesToDelete[] = __DIR__ . '/Subscriber/ControllerPath.php';
            $filesToDelete[] = __DIR__ . '/Views/responsive';
            $filesToDelete[] = __DIR__ . '/Views/emotion';
        }

        //Iterate through all provided file paths and unlink every file.
        foreach ($filesToDelete as $filePath) {
            if (!file_exists($filePath)) {
                continue;
            }

            //Delete file from filesystem.
            unlink($filePath);
        }
    }

Event callback

Now the event callback function should be implemented:

public function onFrontendPostDispatch(Enlight_Event_EventArgs $args)
{
    /** @var \Enlight_Controller_Action $controller */
    $controller = $args->get('subject');
    $view = $controller->View();

    $view->addTemplateDir(
        __DIR__ . '/Views'
    );

    $view->assign('slogan', $this->getSlogan());
    $view->assign('sloganSize', $this->Config()->get('font-size'));
    $view->assign('italic', $this->Config()->get('italic'));

}

public function getSlogan()
 {
     return array_rand(
         array_flip(
             array(
                 'An apple a day keeps the doctor away',
                 'Let’s get ready to rumble',
                 'A rolling stone gathers no moss',
             )
         )
     );
 }

As defined in $this->subscribeEvent the callback is called onFrontendPostDispatch. It takes an Enlight_Event_EventArgs object as parameter, which holds context about the event - in this case the context of the controller that has been executed.

First of all we can extract the reference of the executed frontend controller using $controller = $args->get('subject');. Using this controller we can get a reference of the view instance: $view = $controller->View().

Now we want to inject our own template directory to Shopware using the addTemplateDir method. It takes a path of a template directory as parameter. Whenever rendering a template, Shopware will now automatically check your View directory for extensions and load them dynamically.

Finally we want to assign some configuration to the template - in this case a slogan, a flag that indicates if the slogan should be italic or not and the font size of the slogan. For assignments $view->assign('name', 'value') is used - this way we will be able to access it in the Smarty template using {$name}.

The slogan itself is randomly selected in the getSlogan method. This could easily be moved to a plugin configuration text field, so that the shop owner can enter his slogans line by line. But as seen above, we already picked the best ones.

The configuration

The calls to $this->Config()->get('name') will return our plugin configuration value for 'name'. In order to create the configuration, we will need to implement the createConfig method we called in the install method:

private function createConfig()
{
    $this->Form()->setElement(
        'select',
        'font-size',
        array(
            'label' => 'Font size',
            'store' => array(
                array(12, '12px'),
                array(18, '18px'),
                array(25, '25px')
            ),
            'value' => 12
        )
    );

    $this->Form()->setElement('boolean', 'italic', array(
        'value' => true,
        'label' => 'Italic'
    ));
}

In this case two config elements are added - a select element with the name "font-size" which will draw a combobox in the plugin configuration. The content of the combobox is defined in the "store" array element. Additionally we are able to have a label for the configuration using the "label" key and a default value using the "value" key.

As a second config element we add a boolean element. It is called "italic", enabled by default and has the label "Italic".

This will result in a configuration form like this:

The plugin configuration

If you want to read more about plugin configuration start reading Plugin configuration article.

Template extension

So far we have set up the bootstrap - but where does the actual template extension come from?

We already registered the View directory and can now create the template in the Views/frontend/index/index.tpl template. This template is actually an extension of Shopware's default frontend/index/index.tpl which can be found in themes/Frontend/Bare/frontend/index/index.tpl. This template defines the whole default structure of the Shopware responsive template - and is a perfect place for global extensions. As we created a file with the same name, the template manager of Shopware will automatically load this template file, when the default index.tpl is loaded.

Now our plugin's index.tpl might look like this:

{extends file="parent:frontend/index/index.tpl"}

{block name="frontend_index_navigation_categories_top_include"}

    <style>
        .slogan-box {
            width:100%;
            text-align:center;
        }
        .slogan {
            {if $italic}font-style:italic;{/if}
            font-size:{$sloganSize}px;
        }
    </style>


    <div class="slogan-box">
        <span class="slogan">{$slogan}</span>
    </div>
    
    {$smarty.block.parent}
{/block}

The directive {extends file="parent:frontend/index/index.tpl"} will tell Shopware to not completely replace the default index.tpl file - but to extend from it. Now we can overwrite single or multiple blocks in the default index.tpl using the block directive:

{block name="frontend_index_navigation_categories_top_include"}
    ...
    
    {$smarty.block.parent}
{/block}

This tells Smarty to prepend the content of our block to the default content of the block frontend_index_navigation_categories_top_include. That block is the block, which defines the top category navigation in the default template - a nice place to show a profound slogan to the customer!

Within the block we can use default Smarty / HTML. In the example above, we define some style sheets first:

<style>
   .slogan-box {
       width:100%;
       text-align:center;
   }
   .slogan {
       {if $italic}font-style:italic;{/if}
       font-size:{$sloganSize}px;
   }
</style>

As you can see, we use Smarty syntax here to change the style dynamically corresponding to the configuration we assigned to the template in the onFrontendPostDispatch callback method. So the italic style will only be set if the italic option is set. The same way the font size is set depending on the config.

Now the only thing left to do is show the slogan:

<div class="slogan-box">
    <span class="slogan">{$slogan}</span>
</div>

Installing and result

You can now install the plugin with Shopware's plugin manager. After installation, the plugin configuration can be opened by clicking the "pencil" symbol.

After clearing the cache, your frontend might look like this:

Slogan of the day in the frontend

You can find a installable ZIP package of this plugin here.