Hello World Contao Plugin erstellen: Front- & Backendentwicklung

Geschrieben am von rd

Plugin Darstellung im Frontend

Aller Anfang ist schwer. Selbst für erfahrene Entwickler wie wir ist der Einstieg in neue Technologien oft eine Herausforderung. Die Konzepte hinter der Entwicklung eines Contao-Plugins zu verstehen und sie zu nutzen, um die eigene Contao-Website mit eigenen innovativen Plugins zu erweitern, erfordert Zeit. Die Dokumentation von Contao bietet zwar einen recht guten Überblick, aber nur bis zu einem gewissen Grad. Je spezifischer die Fragen werden, desto weniger wird man dort fündig. Aus diesem Grund haben wir uns dazu entschieden, einen Artikel zu schreiben, wie man ein „Hello World“ Contao Plugin selbst erstellen kann. Der Artikel ist einfach geschrieben und versorgt euch mit interessantem Hintergrundwissen, das ihr braucht, um eigene Plugins in Contao zu entwickeln. In diesem Artikel erfahrt ihr, wie man die Basis eines Plugins aufsetzt, wie man einen "Hello World"-Text im Frontend anzeigt, wie die Datenstruktur sein muss und wie man eine Datenbankanbindung erstellen kann. Am Ende sollte das Ganze ein "Hello World"-Plugin sein, das zusätzlich Nachrichten anzeigt, die man im Backend definiert hat.


Das Plugin ist in Github unter diesem Link verfügbar. Ihr könnt diesen Code als Basis und zu Testzwecken verwenden.

Die Dateistruktur des Plugins

Die Contao Plugin-Dateistruktur muss unbedingt eingehalten werden, damit der Contao Manager das Contao Plugin als solches erkennt und lädt.

Die Dateistruktur des Plugins dient als Orientierung für den Aufbau des Plugins

Contao Plugin: Basis Aufbau

Im ersten Step, muss man den Plugin soweit aufbauen, dass Contao es auch als Plugin ansieht und man es über den Contao Manager installieren kann.
Hierzu müssen folgende Klassen aufgebaut und angepasst werden:

Aufbau vom Composer

Contao nutzt PHP Composer für Module, Extensions und Plugins. Daher benötigt unser Plugin ebenfalls eine Composer-Konfigurationsdatei, die sich im Stammverzeichnis des Plugins befindet. Wenn du das Beispiel-JSON des Composers verwenden möchtest, musst du unbedingt die Werte, die mit einem '#' gekennzeichnet sind, anpassen.

./composer.json

{
  "name": "time4digital/dylans-hello-world-bundle",               #Name vom Plugin
  "description": "Dylan's Hello World Plugin",                    #Beschreibung vom Plugin
  "license": "LGPL-3.0-or-later",
  "type": "contao-bundle",
  "version": "0.0.4",                                             #Version vom Plugin
  "authors": [
    {
      "name": "Ribeiro de Serra Dylan",
      "homepage": "https://www.time4digital.lu"                   #Authoren vom Plugin
    }
  ],
  "homepage": "https://contao.org",                               #OPTIONAL: Deine Webseite                    
  "support": {
    "issues": "https://github.com/ridy01-backup/contao-plugins/issues",   #OPTIONAL: Git Verlinkung.
    "source": "https://github.com/ridy01-backup/contao-plugins"
  },
  "require": {
    "php": "^8.1",
    "contao/core-bundle": "^4.13 || ^5.0"                                   #Deine Dependencies musst du hier setzen.
  },
  "require-dev": {
    "bamarni/composer-bin-plugin": "^1.5",
    "contao/manager-plugin": "^2.0",
    "phpunit/phpunit": "^9.5",
    "symfony/phpunit-bridge": "^6.1"
  },
  "conflict": {
    "contao/manager-plugin": "<2.0 || >=3.0"                                #Mögliche Konflikte kommen hier.
  },
  "autoload": {
    "psr-4": {
      "Time4digital\\DylansHelloWorldBundle\\": "src/"                      #Der Pfad welches Wired wird, Format: "VendorName\\BundleName\\": "PFAD"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "Time4digital\\DylansHelloWorldBundle\\Tests\\": "tests/"             #Der Pfad für die Tests, Format: "VendorName\\BundleName\\Tests": "PFAD"
    }
  },
  "config": {
    "allow-plugins": {
      "bamarni/composer-bin-plugin": true,
      "contao-components/installer": true,
      "contao/manager-plugin": true
    }
  },
  "extra": {
    "bamarni-bin": {
      "bin-links": false,
      "target-directory": "tools"
    },
    "contao-manager-plugin": "Time4digital\\DylansHelloWorldBundle\\ContaoManager\\Plugin"    #Plugin Verlinkung, Format:"VendorName\\BundleName\\ContaoManager\\Plugin
  },
  "scripts": {
    "all": [
      "@unit-tests",
      "@ecs",
      "@phpstan"
    ],
    "ecs": "@php tools/ecs/vendor/bin/ecs check src tests --config ecs.php --fix --ansi",
    "phpstan": "@php tools/phpstan/vendor/bin/phpstan analyze --ansi",
    "unit-tests": "@php vendor/bin/phpunit --colors=always"
  }
}

Aufbau der Bundle Klasse

Der Contao Bundle ist dafür da, deine Ressourcen zu bündeln. Diese Klasse selbst erfordert keine weiteren Spezifikationen; es reicht einfach aus, die Klasse vom Symfony Bundle zu erweitern.

./src/DylansHelloWorldBundle.php

<?php
namespace Time4digital\DylansHelloWorldBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class DylansHelloWorldBundle extends Bundle
{
    
}

Aufbau der Plugin Klasse

Die Plugin-Klasse dient dazu, den Bundle zu laden, damit der Contao Manager weiß, dass es sich um ein Contao Plugin handelt.

./src/ContaoManager/Plugin.php

<?php

declare(strict_types=1);

namespace Time4digital\DylansHelloWorldBundle\ContaoManager;

use Contao\ManagerPlugin\Bundle\BundlePluginInterface;
use Contao\ManagerPlugin\Bundle\Config\BundleConfig;
use Contao\ManagerPlugin\Bundle\Parser\ParserInterface;
use Contao\CoreBundle\ContaoCoreBundle;
use Time4digital\DylansHelloWorldBundle\DylansHelloWorldBundle;

class Plugin implements BundlePluginInterface
{
    /**
     * {@inheritdoc}
     */
    public function getBundles(ParserInterface $parser)
    {
        return [
            BundleConfig::create(DylansHelloWorldBundle::class)
                ->setLoadAfter([ContaoCoreBundle::class]),
        ];
    }
}

Aufbau der Dependency Klasse

Die Dependency-Klasse dient dazu, die Konfigurations- und Services-Dateien zu laden.

./src/DependencyInjection/DylansHelloWorldExtension.php

<?php
declare(strict_types=1);

namespace Time4digital\DylansHelloWorldBundle\DependencyInjection;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

class DylansHelloWorldExtension extends Extension
{
    /**
     * {@inheritdoc}
     */
    public function load(array $mergedConfig, ContainerBuilder $container)
    {
        $loader = new YamlFileLoader(
            $container,
            new FileLocator(__DIR__.'/../Resources/config')
        );

        $loader->load('services.yml');
    }
}

Die Config und Services Datei

Die Config- und Services-Dateien dienen dazu, die Klassen, die man innerhalb des Bundles schreibt, zu verknüpfen, damit sie beim Installieren geladen und verwendet werden können.

./src/Resources/config/config.yml

imports:
    - { resource: services.yml }

./src/Resources/config/services.yml

services:
    _defaults:
        autowire: true
        autoconfigure: true

Und somit wäre die Basis des Plugins aufgebaut. Nun könnte man das Plugin zippen, über den Contao Manager hochladen und installieren.
Doch das allein reicht noch nicht aus, um 'Hello World' im Frontend anzuzeigen.

Contao "Hello World" Frontend-Modul Aufbau

In diesem Abschnitt erstellen wir ein Frontend-Modul, das im Frontend "Hello World" anzeigen soll.
Das Modul lädt außerdem zusätzlich eine CSS- und JS-Datei.

Aufbau der Frontend-Modul Klasse

Die Modul Klasse dient dazu, ein Modul im Backend zu erstellen, welches dann in einem Artikel oder Ähnlichem geladen werden kann.

./src/Module/DylanHelloWorldModule.php

<?php

namespace Time4digital\DylansHelloWorldBundle\Module;

use Contao\Module;

//Hier werden die CSS- und JS-Dateien gesetzt. Format: bundles/bundleName/dateiName.dateExtension
$GLOBALS['TL_CSS'][] = 'bundles/dylanshelloworld/styles.css';
$GLOBALS['TL_JAVASCRIPT'][] = 'bundles/dylanshelloworld/scripts.js';

class DylanHelloWorldModule extends Module
{
    // Hier wird der Name des Templates festgelegt.
    // Der Name muss mit dem Template übereinstimmen, das sich unter ./src/Resources/contao/templates befindet.
    protected $strTemplate = 'mod_helloWorld'; 

    protected function compile()
    {
        // Mit $this->Template->nameVariable erstellst du Variablen, die dann im Template verwendet werden können.
        $this->Template->message = 'Hello World!';
    }
}

Frontend-Modul ins Backend bringen

Die Config PHP Datei dient dazu, um Modulen ins Backend zu bringen.

./src/Resources/contao/config/config.php

<?php
use Time4digital\DylansHelloWorldBundle\Module\DylanHelloWorldModule;

// Frontend modules
// Unter "miscellaneous" sollte ein neuer Reiter namens "Hello World Plugin" erstellt werden, welches dann unser Frontend-Modul lädt.
$GLOBALS['FE_MOD']['miscellaneous']['Hello World Plugin'] = DylanHelloWorldModule::class;

Template-Datei für das Frontend-Modul

Die Template-Datei wird sozusagen als Container für das Modul verwendet. Dort bindest du die Daten ein, die du im Modul definierst, und baust dir somit eine HTML-Struktur auf.

./src/Resources/contao/templates/modules/mod_helloWorld.html5

<?php $this->extend('block_searchable'); ?>

<?php $this->block('content'); ?>

<!-- Hier verwenden wir den Wert der message-Variable -->
<div class="dylan-hello-world-container">
    <?= $this->message; ?>
</div>

<?php $this->endblock(); ?>

CSS und JS ins Contao-Modul einbinden.

Du kannst eigene CSS- und JS-Dateien in das Contao-Modul einbinden, wie im Modul oben definiert. Du musst dazu nur folgende Dateistruktur einhalten (der Name der Datei spielt dabei keine Rolle):

./src/Resources/public/scripts.js

./src/Resources/public/styles.css

... und voilà, schon kannst du im Backend unter Themes > Frontend Module ein neues Modul erstellen und wählst dann einfach als Modultyp das Hello World Plugin aus!

Dann gehst du auf irgendeinen Artikel oder Ähnliches und wählst dort das erstellte Modul aus.

Demonstration der Erstellung eines Frontend-Moduls aus dem programmierten Frontend-Modul
Im Contao Backend den Frontend-Module in einem Artikel zuweisen

Puh, das war schon eine Menge Arbeit... doch das reicht noch nicht ganz aus! Zwar würde unser Frontend-Modul ausreichen, um im Frontend etwas Schönes anzuzeigen, das mit PHP-Funktionen ausgestattet wäre... doch was ist, wenn wir zusätzlich Daten aus der Datenbank laden und über das Contao-Backend verwalten möchten?

Im nächsten Abschnitt gehen wir genau auf diesem Punkt im Detail ein.

Contao Backend-Modul: Aufbau und Datenbankeinbindung

In diesem Abschnitt erstellen wir ein Backend-Modul, das Daten aus einer Tabelle namens tl_messages lädt und zusätzlich bearbeitet werden kann. Das Ganze sollte im Contao-Backend unter einem Menüpunkt sichtbar sein.
Außerdem erweitern wir das Frontend-Modul, damit es auch die Daten aus dem Backend liest.

Datenbanktabellenerstellung mit DCA

Mit der DCA-PHP-Datei kannst du Contao mitteilen, dass du eine neue Datenbanktabelle erstellen möchtest.
Damit konfigurierst du die Struktur der Tabelle und definierst, wie die Daten angezeigt werden sollen.

./src/Resources/contao/dca/tl_messages.php

<?php
use Contao\DC_Table;

// Der Name der Tabelle; die PHP-Datei sollte entsprechend benannt werden.
$GLOBALS['TL_DCA']['tl_messages'] = [

    // Hier wird festgelegt, was im Contao-Backend angezeigt werden soll:
    'palettes' => [
        'default' => '{messages_legend},message;'
    ],

    // Die SQL-Felder werden hier definiert:
    'fields' => [
        // Dieses Feld ist obligatorisch.
        'id' => [
            'sql' => "int(10) unsigned NOT NULL auto_increment",
        ],
        // Dieses Feld ist obligatorisch.
        'tstamp' => [
            'sql' => "int(10) unsigned NOT NULL default '0'",
            'label' => 'TS',
        ],
        'message' => [
            'inputType' => 'text',
            'eval' => ['tl_class' => 'w50', 'maxlength' => 255],
            'sql' => "varchar(255) NOT NULL default ''"
        ]
    ],
    
    // Hier werden die Keys und weitere Attribute festgelegt.
    'config' => [
        'dataContainer' => DC_Table::class,
        'sql' => [
            'keys' => [
                'id' => 'primary'
            ]
        ]
    ],

    // Hier wird auch definiert, wie das Ganze im Contao-Backend dargestellt wird.
    'list' => [
        'sorting' => [
            'mode' => 1,
        ],
        'operations' => [
            'edit',
            'delete',
        ],
        'label' => [
            'fields'  => ['id','message'],
            'showColumns' => true,
        ]
    ],
];

Übersetzungen der Felder

Mit der Übersetzungs-XLF-Datei kannst du die Labels und Beschreibungen der Felder für verschiedene Sprachen definieren.

./src/Resources/contao/languages/en/tl_messages.xlf

<?xml version="1.0" ?><xliff version="1.1">
<!-- Format: contao/languages/SPRACHE/TABELLE.php-->
  <file datatype="php" original="contao/languages/en/tl_messages.php" source-language="en">
    <body>
        <!-- Legendenbezeichnung im Contao-Backend-->
        <trans-unit id="tl_messages.messages_legend">
            <source>Messages</source>
        </trans-unit>
        <!-- Bezeichnung des Feldes "Nachricht"-->
        <trans-unit id="tl_messages.message.0">
            <source>Message</source>
        </trans-unit>
        <!-- Beschreibung des Feldes "Nachricht"-->
        <trans-unit id="tl_messages.message.1">
            <source>Your individual message.</source>
        </trans-unit>
    </body>
  </file>
</xliff>

Backend-Modul ins Contao-Backend bringen

Die Config-PHP-Datei muss nun wie beim Frontend-Modul angepasst werden.

./src/Resources/contao/config/config.php

<?php
use Time4digital\DylansHelloWorldBundle\Module\DylanHelloWorldModule;

// Frontend modules
// Unter "miscellaneous" sollte ein neuer Reiter namens "Hello World Plugin" erstellt werden, welches dann unser Frontend-Modul lädt.
$GLOBALS['FE_MOD']['miscellaneous']['Hello World Plugin'] = DylanHelloWorldModule::class;

// Backend modules
// Unter der Menükategorie "content" sollte nun ein neuer Menüeintrag namens "Messages" erscheinen, der die Tabelle tl_messages verwaltet.
$GLOBALS['BE_MOD']['content']['Messages'] = [
    'tables' => ['tl_messages']
];

Backend-Daten ins Frontend anzeigen

Dazu müssen das Template und das Frontend-Modul angepasst werden.

./src/Module/DylanHelloWorldModule.php

<?php

namespace Time4digital\DylansHelloWorldBundle\Module;

use Contao\Module;

//Hier werden die CSS- und JS-Dateien gesetzt. Format: bundles/bundleName/dateiName.dateExtension
$GLOBALS['TL_CSS'][] = 'bundles/dylanshelloworld/styles.css';
$GLOBALS['TL_JAVASCRIPT'][] = 'bundles/dylanshelloworld/scripts.js';

class DylanHelloWorldModule extends Module
{

    // Hier wird der Name des Templates festgelegt.
    // Der Name muss mit dem Template übereinstimmen, das sich unter ./src/Resources/contao/templates befindet.
    protected $strTemplate = 'mod_helloWorld'; 

    protected function compile()
    {
       
        // Mit $this->Template->nameVariable erstellst du Variablen, die dann im Template verwendet werden können.
        $this->Template->message = 'Hello World!';
        
        // Die Module Klasse gibt uns die Möglichkeit, Datenbankeinträge schnell über die Contao Database-Klasse zu laden.
        // Hier rufe ich die Daten über eine SQL-Abfrage ab. Diese Abfrage extrahiert Datenbankeinträge aus der Datenbank und speichert sie in einem Array.
        try {
            $objEntries = $this->Database->execute("SELECT * FROM tl_messages");
            $this->Template->entries = $objEntries->fetchAllAssoc();
        } catch (\Exception $e) {
        // Falls das nicht funktioniert, wird einfach ein leerer Array dem Template zugewiesen.
            $this->Template->entries = [];
        }
    }
}

./src/Resources/contao/templates/modules/mod_helloWorld.html5

<?php $this->extend('block_searchable'); ?>

<?php $this->block('content'); ?>

<!-- Hier verwenden wir den Wert der message-Variable -->
<div class="dylan-hello-world-container">
    <?= $this->message; ?>
</div>

<!-- Hier durchlaufen wir das entries-Array und entnehmen für jedes Element den Wert der Nachricht -->
<div class="dylans-hello-world-live-container">
    <ul>
        <?php foreach ($this->entries as $entry): ?>
        <li><?= $entry["message"]; ?></li>
        <?php endforeach; ?>
    </ul>
</div>

<?php $this->endblock(); ?>

Finally, we got it! Nach vielen Höhen und Tiefen haben wir den Gipfel erreicht. Zeit zur Freude! ;-)

Nach der Neuinstallation des Plugins sollte nun ein neuer Menüpunkt namens "Messages" vorhanden sein, über den neue Nachrichten erstellt werden können. Anschließend sollte unser Frontend-Modul all diese Nachrichten anzeigen.

Im Contao-Backend befindet sich nun der Menüreiter 'Messages', der dazu dient, Nachrichten zu erstellen

Wir hoffen, dass ihr durch diesen Blog-Artikel mehr über die Contao-Plugin-Entwicklung gelernt habt.

Wichtig anzumerken ist, dass es mehrere Wege gibt, dieses Plugin umzusetzen, und dieser Artikel lediglich ein Beispiel darstellt. Es steht Ihnen komplett frei, wie Sie Ihr Hello World Plugin entwickeln, solange die Basis richtig ist und der Contao Manager es als Plugin erkennt und installiert.

Weitere Informationen findest du hier.

Außerdem kannst du das Plugin auch direkt über diesen Link herunterladen und natürlich auch weiterentwickeln.

Vielen Dank für das Lesen dieses Blog-Artikels! Ich hoffe du hattest spaß und konntest etwas lernen. Weiterhin viel Spaß beim Entwickeln!

Der Artikel kann hier auch auf englisch gelesen werden.