Shopware 5.3 Plugin 101 | Einfaches Plugin erstellen

In der ersten Reihe von vielen Tutorials, zeige ich euch wie Ihr euer erstes Plugin in Shopware (ab Version 5.2) erstellt. Optimalerweise habt Ihr bereits ein laufendes System auf eurem lokalen Rechner und Ihr habe eine IDE mit der Ihr flüssig arbeiten könnt (ich nutze PHPStorm)

Alte Ordnerstruktur

Früher wurden alle Plugins wie folgt abgelegt:

/var/www/shopware/engine/Shopware/Plugins

dieser Bereich war unterteilt in

Community

Alle Plugins aus dem Shopware Store

Default

Vorinstallierte Plugins von Shopware und Security Plugins

Local

Selbstprogrammierte Plugins und Plugins von Drittanbietern (die nicht im Store verfügbar sind)

 

Diese Bereiche werden noch einmal aufgeteilt in Frontend, Backend und Core um die Funktionsbereiche der Plugins abzustecken.

Ein kleiner Exkurs, falls Ihr doch noch externe Plugins aus dem Store installiert habt. Einige Pluginanbieter wechseln vermutlich erst, wenn Shopware diese Unterstützung irgendwann aus dem Core nimmt.

Neue Ordnerstruktur (ab SW 5.2.0)

Wir möchten uns aber mit der neuen Art der Pluginprogrammierung beschäftigen und die liegt unter

/var/www/shopware/custom/plugins

allein der Pfad ist wesentlich entspannter. Wenn man eine Weile mit SW arbeitet und Systeme hat in denen 30+ „alte“ Plugins installiert sind, kann man manches mal +1 Minute mit der Suche nach dem richtigen Pfad verbringen.

Namenskonvention

Der Pluginname setzt sich aus dem Entwicklerpräfix und Name des Plugins zusammen.

Entwicklerpräfix

In meinem Fall nutze ich dafür MiHo (Micha Hobert)

Pluginname

Hier nutzen wir DetailPageExtension da wir ganz einfach die Detailseite erweitern möchten

Plugin anlegen

Wir erstellen also einen Ordner in custom/plugins mit dem Namen MiHoDetailPageExtension und der File MiHoDetailPageExtension.php

Dort packen wir folgenden Code hinein:

namespace MiHoDetailPageExtension;

class MiHoDetailPageExtension extends \Shopware\Components\Plugin{


}

damit können wir das Plugin bereits im Backend einsehen.

Installation und Aktivierung des Plugins

Nun können wir das Plugin einfach über den PLUS Button installieren oder über die CLI 

php bin/console sw:plugin:refresh
php bin/console sw:plugin:install --activate MiHoDetailPageExtension

Das ist besonders dann praktisch, wenn man durch einen Bug nicht mehr ins Backend kommt und somit auch das Plugin nicht deaktivieren kann (oder aber direkt über die s_core_plugins table ).

Versionierung & Plugininformationen (plugin.xml)

Wie bereits vom alten Pluginsystem können wir im Root Verzeichnis des Plugins einfach eine plugin.png Datei ablegen.

Um nun Daten wie Changelog, Lizenz, Author usw. zu hinterlegen müssen wir noch eine plugin.xml Datei anlegen.

<?xml version="1.0" encoding="utf-8"?>
<plugin xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../../../engine/Shopware/Components/Plugin/schema/plugin.xsd">

    <label lang="de">Detailseiten Erweiterung</label>

    <version>1.0.1</version>
    <author>Micha Hobert</author>
    <copyright>(c) 2017 Micha Hobert | Shopware Entwickler</copyright>
    <license>Proprietär</license>
    <link>https://the-cake-shop.de/</link>
    <description>Diese Plugin erweitert die Artikeldetailseite um versch. Werte und Funktionen.</description>
    <compatibility minVersion="5.3.3" />

    <changelog version="1.0.0">
        <changes lang="en">Initial upload of plugin</changes>
        <changes lang="de">Initialer Upload des Plugins</changes>
    </changelog>
    <changelog version="1.0.1">
        <changes lang="en">Added plugin.xml file</changes>
        <changes lang="de">Plugin.xml Datei wurde hinzugefügt</changes>
    </changelog>
</plugin>

Danach sieht das ganze wie folgt aus:

Cache Invalidierung

Bevor wir uns an die ersten Funktionen und eigentlichen Anpassungen herantasten noch ein kurzer Exkurs zum Thema Cache – da dies gerade bei größer werdenden Setups oft ein großes Thema ist und in unserem Beispiel werden zum Beispiel template,config,router und proxy geleert. Das liegt an den default Werten der /engine/Shopware/Components/Plugin/Context/InstallContext.php Klasse.

  /**
     * pre defined list to invalidate simple caches
     */
    const CACHE_LIST_DEFAULT = [
        self::CACHE_TAG_TEMPLATE,
        self::CACHE_TAG_CONFIG,
        self::CACHE_TAG_ROUTER,
        self::CACHE_TAG_PROXY,
    ];

Das passiert im Default auch beim aktivieren und deaktivieren da die Klassen

DeactivateContext und ActivateContext

ebenfalls von dieser Klasse erben.

Hier möchten wir nur config und http leeren – deshalb binden wir die Klassen ein und nutzen die Funktion scheduleClearCache mit einem angepassten Array:

<?php
/**
 * Created by PhpStorm.
 * User: micha
 * Date: 14.10.17
 * Time: 18:27
 */

namespace MiHoDetailPageExtension;

use Shopware\Components\Plugin\Context\ActivateContext;
use Shopware\Components\Plugin\Context\DeactivateContext;
use Shopware\Components\Plugin\Context\InstallContext;
use Shopware\Components\Plugin\Context\UninstallContext;


class MiHoDetailPageExtension extends \Shopware\Components\Plugin{

	public function install(InstallContext $context)
	{
		$context->scheduleClearCache(
			[
				InstallContext::CACHE_TAG_CONFIG,
				InstallContext::CACHE_TAG_HTTP
			]);

		parent::install($context);

	}


	public function uninstall(UninstallContext $context)
	{
		$context->scheduleClearCache(
			[
				InstallContext::CACHE_TAG_CONFIG,
				InstallContext::CACHE_TAG_HTTP
			]);

		parent::uninstall($context);
	}

	public function activate(ActivateContext $context)
	{
		$context->scheduleClearCache(
			[
				InstallContext::CACHE_TAG_CONFIG,
				InstallContext::CACHE_TAG_HTTP
			]);

		parent::install($context);
	}

	public function deactivate(DeactivateContext $context)
	{
		$context->scheduleClearCache(
			[
				InstallContext::CACHE_TAG_CONFIG,
				InstallContext::CACHE_TAG_HTTP
			]);

		parent::install($context);
	}



}

da wir faul sind exportieren wir das ganze natürlich in eine Funktion (oder Konstante).

<?php
/**
 * Created by PhpStorm.
 * User: micha
 * Date: 14.10.17
 * Time: 18:27
 */

namespace MiHoDetailPageExtension;

use Shopware\Components\Plugin\Context\ActivateContext;
use Shopware\Components\Plugin\Context\DeactivateContext;
use Shopware\Components\Plugin\Context\InstallContext;
use Shopware\Components\Plugin\Context\UninstallContext;


class MiHoDetailPageExtension extends \Shopware\Components\Plugin{

	public function install(InstallContext $context)
	{
		$context->scheduleClearCache($this->getCacheArray());
		parent::install($context);

	}

	public function uninstall(UninstallContext $context)
	{
		$context->scheduleClearCache($this->getCacheArray());
		parent::uninstall($context);
	}

	public function activate(ActivateContext $context)
	{
		$context->scheduleClearCache($this->getCacheArray());
		parent::install($context);
	}

	public function deactivate(DeactivateContext $context)
	{
		$context->scheduleClearCache($this->getCacheArray());
		parent::install($context);
	}


	/**
	 * Get caches to clear
	 *
	 * @return array
	 */
	private function getCacheArray()
	{
		return [
			InstallContext::CACHE_TAG_CONFIG,
			InstallContext::CACHE_TAG_HTTP
		];
	}

}

 

Shopware Events

Shopware hat verschiedene Events (Globale Events, Application Events oder Hooks) in unserem ersten Beispiel nutzen wir ein globales Event.

Events werden automatisiert erstellt und geben Aufschluss über das wann und wo – für uns interessant sind erstmal nur die Pre- und Postdispatch Events.

Predispatch Event: Wird vor der Controller-Action Verarbeitung ausgeführt.

Postdispatch Event: Wird nach der Controller-Action Verarbeitung ausgeführt.

Um unser Event zu registrieren erweitern wir die Methode

getSubscribedEvents()

 

public static function getSubscribedEvents() {
		return [
			'Enlight_Controller_Action_PostDispatch_Frontend_Detail' => 'onFrontendDetail'
		];
	}

Enlight_Controller_Action_PostDispatch_Frontend_Detail

PostDispatch – nach der Verarbeitung der Action Frontend Detail.

Wir erweitern hier also die Detailseite nachdem der eigentliche Code bereits ausgeführt wurde.

Nun packen wir unseren eigentlichen Code in onFrontendDetail()

public function onFrontendDetail(\Enlight_Event_EventArgs $arguments){

		$view = $arguments->get('subject')->View();


		$qb = $this->container->get('dbal_connection')->createQueryBuilder();

		$qb->select('name')
			->from('s_articles')
			->where('s_articles.id = :id')
			->setParameter('id', 2);

		$single = $qb->execute()->fetch();
		$view->assign('mein_produkt', $single);

	}

Wir holen uns den View des Frontend Detail Controllers und übergeben diesem einen Wert ($view->assign(‚mein_produkt‘, $single); ) In diesem Fall machen wir eine simples SELECT Query auf ein bestimmtes Produkt via ID.

Datenbankabfrage

Nun möchten wir von unserer Datenbank etwas zurückbekommen, dazu nutzen wir den Dependency Container

$qb = $this->container->get('dbal_connection')->createQueryBuilder();

Doctrine Query Builder

QueryBuilder class is responsible to dynamically create SQL queries.
Important: Verify that every feature you use will work with your database vendor. SQL Query Builder does not attempt to validate the generated SQL at all.
The query builder does no validation whatsoever if certain features even work with the underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements even if some vendors such as MySQL support it.

Unser Query bauen wir uns also mit dem Builder zusammen, das ganze sieht dann so aus:

$qb->select('name')
			->from('s_articles')
			->where('s_articles.id = :id')
			->setParameter('id', 2);

Man kann das ganze auch anders machen, allerdings empfehle ich euch den QB zu nutzen (mehr dazu HIER)

Viewvariablen auslesen / debuggen

Um zu prüfen ob unser Wert auch auf der Detailseite gelandet ist können wir entweder den Wert in einem Template ausgeben:

{$mein_produkt}

oder zum Beispiel in das data.tpl ein {debug} packen (natürlich nur lokal oder auf einem Staging System)

Das {debug} öffnet ein Smarty Fenster auf der Detailseite und zeigt alle verfügbaren Werte an.

Das war die erste kleine Einführung in die Pluginentwicklung in Shopware 5.2+. Im zweiten Teil geht es um Controller und wie man sich diesen individuell baut.

 

15 Antworten auf „Shopware 5.3 Plugin 101 | Einfaches Plugin erstellen“

  1. ->where(’s_articles.id = :id‘)
    ->setParameter(‚id‘, 2);

    würde in jeder Detailansicht die Bezeichnung aus „name“ mit der ID 2 aus „id“ anzeigen. Allerdings klappt das bei mir in 5.3.31 nicht mit dieser Anleitung.

    Von welchem Attributfeld attr1 sprichst du? Ich sehe hier keines.

  2. $view->assign(‚mein_produkt‘, $single);
    und
    {$mein_product} sind zwei völlig verschiedene Bezeichner. Entweder mit k oder mit c. Mir liefert es allerdings ein Array im auf dem Template zurück. Mir ist es ein Rätsel, wie du ein Objekt zurück bekommen hast.
    $article_name Smarty_Variable Object (3)
    ->value = “ -> Array“
    ->nocache = null

    $mein_product.name gibt auch kein einzelnes Element zurück, was wohl am fetch() liegen dürfte.
    Der Symfony Debugger liefert mit unter „Smarty“ „article_name“ => “ Array“ zurück (Ich habe die Variabel bei mir eingeenglischt)

    1. Hallo Jenny,
      danke für den Hinweis – hier habe ich mich verschrieben. Ich habe {$mein_product} angepasst zu {$mein_produkt} – im Smarty Debug Console Fenster ist das richtige Smarty Object.

  3. Doctrine schlägt für Single results folgendes vor
    $this->queryBuilder->execute()->fetch(PDO::FETCH_COLUMN);

    Ich habe den Query nochmal dahingehend umgeschrieben,

    $query->select(’s_articles.name‘)
    ->from(’s_articles‘)
    ->where(’s_articles.id = :id‘)
    ->setParameter(‚id‘, $articleId);
    $single = $query->execute()->fetchColumn();
    $view->assign(‚article_name‘, $single);

    damit die Ausgabe wie folgt und nicht das Array als response auf dem Template erscheint, wenn man im Template auf foreach loops verzichten will, um den Wert auzulesen.

    Order number: SW10117
    Lagerbestand 23
    Produktbezeichnung TITANIUM CARBON GS

    Vielleicht gibt es eine smartere Lösung mit $article.name. Aber so wie es jetzt beschrieben ist, ist es unbrauchbar.

    1. Hi Jenny,
      in diesem Beispiel geht es nicht wirklich um eine spezielle Ausgabe – lediglich ein Beispiel wie man schnell Ergebnisse bekommt und diese über ein Event ans Frontend überträgt.

      Ich gehe in späteren Beiträgen sicher noch einmal speziell auf den Querybuilder ein.

  4. zum Beispiel hier.

    $qb->select(’name‘)
    ->from(’s_articles‘)
    ->where(’s_articles.id = :id‘)
    ->setParameter(‚id‘, 2);

    Ich hätte dann gern den Produktnamen immer zum entsprechenden ausgewählten Artikel und nicht die ID 2 bei allen Artikeln. Wie ich das in Symfony übergebe, weiß ich. Bin aber in Shopware noch neu.

    1. Ah, okay. Ich hoffe zu glauben was du meinst – leider schlägt das Shopware Plugin in Phpstorm den getter auf die Enlight_Event_EventArgs Events nicht vor. Etwas ärgerlich – die Daten holst du dir über das Subject deiner subscribed Events und dessen Arguments.

      In Deinem Plugin hast du ja die statische Funktion public static function getSubscribedEvents() in der du Deine eigenen Methoden „subscribest“.

      public static function getSubscribedEvents()
      {
      return [
      'Enlight_Controller_Action_PostDispatch_Frontend_Detail' => 'onFrontendDetail'
      ];
      }

      In

      public function onFrontendDetail(\Enlight_Event_EventArgs $arguments){}

      kannst du dann einfach auf das Subject zugreifen.


      $view = $arguments->getSubject()->View();
      var_dump($view->sArticle);
      die();

      Die neue Doku verweist aber auf $arguments->get(’subject‘)->View();

      https://developers.shopware.com/developers-guide/plugin-quick-start/#subscriber-classes

      Dort hast du dann alle Daten, die du zum aktuellen Produkt brauchst.

  5. Hallo,

    ich habe mal etwas Zeit gefunden.
    Die neue Methode $view = $arguments->get(’subject‘)->View();
    hast du doch schon in der Anleitung stehen. Wieso verweist du nochmal auf die Alte?

    Ich lese meine Artikel ID nun über
    $articleId = $view->sArticle[‚articleID‘] aus
    und übergebe die gespeicherte ID an doctrine.

  6. Hallo und Danke für das Tutorial. Ich bin (wie zu erwarten) auch neu in Shopware Plugin-Development. In einem Shopware 5.4.6 erzeugt einfügen des tags {$mein_produkt} in die Datei /frontend/detail/content/header.tpl leider keine Ausgabe. Auch das von Dir angesprochene tag {debug} öffnet kein Smarty Fenster. Ich denke das sind alles Anfängerfehler von mir. Aber es wäre trozdem schön wenn im Tutorial ein Hinweis darauf wäre was noch als Grundlage benötigt wird (wie zB. beim smarty debug).

    1. Hi Rob,
      das kann in diesem Fall sehr viele Gründe haben 😉

      Schau dir am besten mal das hier an:
      https://developers.shopware.com/developers-guide/plugin-quick-start/

      Mein ersten 3 Vermutungen wären:
      1. Falsche Ordnerstruktur (entweder direkt in Resources oder im frontend Ordner)
      2. Falsches Event genutzt
      3. Deine Änderung wird von einem Plugin überschrieben oder du hast das „parent“ beim extenden von Smarty vergessen

      Es gibt hier wirklich 30234145 Möglichkeiten – geh nochmal alle Steps durch und prüfe Pfade und Dateiinhalte 🙂

    2. ist {$mein_produkt} und die zu assignende Variable $view->assign(‚mein_produkt‘, $single); auch identisch?.
      Das Debug Fenster wird nicht immer bei allen Browsern geöffnet, die Popuppfenster unterbinden. Versuch doch mal mit $view->assign(‚mein_produkt‘, „Hallo Welt“); ob die Ausgabe Hallo Welt im Frontend erscheint. Du musst die Ausgabe auch innerhalb der Blockanweisung stehen haben.

  7. Vielen Dank für das gute Tutorial! Finde ich für den Einstieg deutlich besser, als die offiziellen Beispiele aus der Shopware-Dokumentation, weil hier schneller zum Punkt gekommen wird 🙂

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert