Über unsMediaKontaktImpressum
Ralf Eggert 17. November 2015

Zend Framework 2 - Best Practices

Das Zend Framework 2 (kurz ZF2) zählt zu den am häufigsten eingesetzten PHP-Frameworks weltweit. Das Framework wird bereits seit 10 Jahren entwickelt und ist Basis vieler Webanwendungen auch im Enterprise-Umfeld. In diesem Artikel möchte ich Ihnen Tipps und Anregungen geben, wie Sie aus dem ZF2 das Beste für Ihr Projekt herausholen.

Einstieg in das Zend Framework

Das Zend Framework ist ein typischer Vertreter der im PHP-Umfeld beliebten MVC-Frameworks. Es bietet Ihnen eine Vielzahl an Komponenten, mit denen Sie nahezu alle denkbaren Aspekte einer Webanwendung umsetzen können. Der Modul-Manager ermöglicht es Ihnen, Ihre Anwendung in sinnvolle Blöcke zu unterteilen und diese Teile in anderen Projekten wiederzuverwenden. Der Service-Manager ist das Mittel der Wahl, um die Abhängigkeiten Ihrer Klassen mithilfe von Dependency Injection transparent zu gestalten. Der Event-Manager ermöglicht Ihnen zudem, Ihre Anwendung anhand einer ereignisgesteuerten Architektur zu implementieren. Die MVC-Komponente zur sinnvollen Trennung Ihrer Anwendung in Model, View und Controller, eine robuste Formularkomponente, die Unterstützung der beliebtesten Datenbanksysteme sowie die Authentifizierung und Autorisierung können Sie direkt mit ZF2-Komponenten in Ihre Anwendung integrieren. Zudem runden weitere Komponenten für Themen wie Caching, Konfiguration, Internationalisierung, Validierung und Filterung von Eingabedaten, Logging, Navigation, Paginierung und vieles mehr das umfangreiche Angebot des Zend Framework 2 ab.

Für einen schnellen Einstieg in das Zend Framework 2 können Sie die offizielle Skeleton-Application installieren. Dabei handelt es sich um ein Projektgerüst, das alle notwendigen Verzeichnisse und Dateien enthält und auf dem Sie aufbauen können. Wie so viele PHP-Projekte heutzutage können Sie auch das ZF2 ganz bequem mit dem Composer installieren [1]. Sollten Sie den Composer noch nicht kennen und installiert haben, dann sollten Sie dies umgehend nachholen.

Die Installation der Skeleton-Application erfolgt mit diesem einfachen Kommando. Dabei müssen Sie den Pfad angeben, unter dem die Anwendung installiert werden soll.

composer create-project zendframework/skeleton-application /path/to/install

Details zur Einrichtung eines Virtual Hosts finden Sie in der README.md zum Projekt [2]. Nach der erfolgreichen Installation sollte die Skeleton-Application in Ihrem Browser so ähnlich wie in Abb.1 aussehen.

Nach der Installation können Sie mit der eigentlichen Arbeit beginnen und Ihre Anwendung implementieren. Dabei sollten Sie die Best Practices in den folgenden Abschnitten beachten, damit Sie sich die Entwicklung nicht unnötig erschweren.

Module

Zend Framework 2-Module sind von Haus aus eigenständig und darauf ausgelegt, dass sie wiederverwendbar sind. Dennoch gibt es einige Fallstricke, die Sie beachten sollten.

  • Ein Modul kann eine eigene Bibliothek darstellen, deren Klassen von anderen Modulen verwendet werden können.
  • Ein Modul kann auch voll funktionsfähig sein und die komplette MVC-Struktur beinhalten und damit Features wie ein Blog, eine Bildergalerie oder einen Warenkorb bereitstellen.
  • Ein Modul A kann von Modul B abhängig sein. Das Modul B darf aber nicht im Umkehrschluss auch von Modul A abhängig sein.
  • Damit die Funktionalitäten eines Moduls von anderen verwendet werden können, sollten Sie Service-Klassen schaffen. Alternativ können Sie für den Einsatz in Controllern andere Module auch Controller-Plugins oder für den View entsprechende View-Helper bereitstellen.
  • Damit die Templates in Ihren anderen Projekten wiederverwendbar bleiben, sollten Sie dieselbe Template-Engine verwenden. Ein Modul mit Twig-Templates lässt sich nur schwer in einem anderen Projekt verwenden, dass PHP für das Rendering einsetzt.

Wenn Sie Ihre Module sinnvoll kapseln und zudem über ein Repository bereitstellen, können Sie diese auch per Composer installierbar machen. Tipp: Die Kombination von GitLab [3] und Satis [4] nimmt Ihnen dabei viel Arbeit ab.

Model-View-Controller

Beachten Sie bitte, dass eine Trennung Ihrer Anwendung anhand des Model-View-Controller-Entwurfsmusters nur dann sinnvoll ist, wenn Sie die zugrunde liegende Philosophie auch tatsächlich beherzigen.

  • Im Prinzip sollte der View nur die Präsentationslogik enthalten, die Sie benötigen, um die Daten des Views ausgeben zu können. Das können einfache Fallunterscheidungen und Schleifen sein, um die Daten passend auszugeben.
  • Der Controller ist nur für die Steuerung der Anwendung zuständig und sollte demzufolge nur Steuerungslogik abbilden. Im Wesentlichen geht es darum, dass der Controller Parameter entgegennimmt, z. B. aus einem POST Request oder dem Routing, und an das Model übergibt. Die Rückgabe des Models wird dann entsprechend verarbeitet, indem die zurückgegeben Daten an den View übergeben werden oder ein Redirect ausgeführt wird.
  • Die restliche Geschäftslogik sollte im Model landen. Das können Berechnungen sein, das Lesen von Daten aus der Datenbank oder das Persistieren der Daten. Auch das Versenden von E-Mails sollten Sie z. B. eher in einem Infrastruktur-Service kapseln, als direkt im Controller auszuführen.

Der Controller sollte somit dem Prinzip "Thin Controller, Fat Models" folgen und so schlank wie möglich bleiben. Wenn Sie dies beherzigen, erhöhen Sie die Wiederverwendbarkeit Ihrer Business-Logik und können neben einer reinen Webanwendung relativ mühelos auch eine Konsolenanwendung oder einen RESTful-Webservice implementieren.

Service-Manager

Das Thema Dependency Injection gehört heutzutage in der PHP-Welt zum Standard bei der Entwicklung. Dabei geht es darum, dass Abhängigkeiten zwischen einzelnen Klassen nicht hart kodiert werden, sondern von außen injiziert werden. Ein Beispiel für einen CustomerController mit einer hart kodierten Abhängigkeit zur Klasse CustomerService. Möchten Sie die Klasse CustomerService austauschen oder einen Unit-Test für diesen Controller schreiben, ist dies nicht so ohne weiteres möglich. Sollte die Klasse CustomerService weitere Abhängigkeiten haben, wird es noch komplizierter.

namespace Customer\Controller;

use Customer\Service\CustomerService;
use Zend\Mvc\Controller\AbstractActionController;

class CustomerController extends AbstractActionController 
{
    public __construct()
    {
        $this->customerService = new CustomerService();
    }
}

Sinnvoller ist es, die Abhängigkeit von außen zu injizieren, wie das nächste Beispiel zeigt. Hier wird eine Instanz von CustomerService in den Konstruktor vom CustomerController injiziert. Damit wird die direkte Abhängigkeit aufgelöst und auch das Schreiben von Unit-Tests vereinfacht.

namespace Customer\Controller;

use Customer\Service\CustomerService;
use Zend\Mvc\Controller\AbstractActionController;

class CustomerController extends AbstractActionController
{
    public __construct(CustomerService $customerService)
    {
        $this->customerService = $customerService;
    }
}

Jetzt fragen Sie sich vielleicht, wie Sie die Abhängigkeit von außen injizieren können und da kommt der Service-Manager ins Spiel. Dieser ist in der Lage, mithilfe einer CustomerControllerFactory die erwähnte Abhängigkeit zu injizieren, wie das nächste Beispiel zeigt.

namespace Customer\Controller;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class CustomerControllerFactory implements FactoryInterface 
{
    public function createService(ServiceLocatorInterface $controllerManager)
    {
        $serviceLocator = $controllerManager->getServiceLocator();

        $customerService = $serviceLocator->get('Customer\Service\Customer');

        $controller = new CustomerController($customerService);

        return $controller;
    }
}

Die Konfiguration des Service-Managers erfolgt über die Konfigurationsdatei des entsprechenden Moduls und könnte wie folgt aussehen. Jeder Service wird durch einen sogenannten Identifier ausgezeichnet, der projektweit einmalig sein muss. Dieser verweist auf die konkrete Klasse oder Factory, die ausgeführt werden soll, sobald dieser Service angefordert wird. Sollte unser CustomerService nun selber Abhängigkeiten haben, können wir dafür ebenfalls eine entsprechende Factory erstellen und müssen weder den CustomerController noch deren Factory ändern.

return [
    'service_manager' => [
        'invokables' => [
            'Customer\Service\Customer' => 'Customer\\Service\\CustomerService',
        ],
    ],
    'controllers' => [
        'factories' => [
            'Customer' => 'Customer\\Controller\\CustomerControllerFactory',
        ],
    ],
];

Wir konnten an dieser Stelle nur einen kleinen Einblick in die Möglichkeiten des Service-Managers bekommen. Mehr erfahren Sie in der Dokumentation [5]. Dennoch sollten Sie beim Einsatz des Service-Managers immer folgende Best Practices beherzigen:

  • Legen Sie nicht alle Services im Service-Manager ab, sondern nutzen Sie auch die spezialisierten Service-Manager. So könnten Sie Ihre Formulare zwar auch im Service-Manager konfigurieren. Besser ist jedoch der Einsatz des speziellen Form-Element-Managers. Einen Überblick über die wichtigsten spezialisierten Service-Manager finden Sie in Abb.2.
  • Injizieren Sie niemals den Service-Manager in eine Klasse, um sich den Aufwand zu sparen, jede einzelne Abhängigkeit injizieren zu müssen. Sie sollten immer jede Abhängigkeit direkt injizieren. Nur so behalten Sie den Überblick, ob Ihre Klasse zu viele Abhängigkeiten hat. Wenn Sie merken, dass eine Klasse fünf und mehr Abhängigkeiten hat, ist dies immer ein Zeichen dafür, dass diese Klasse zu viel macht und refaktoriert werden sollte.
  • Setzen Sie die Initializer und AbstractFactories eher sparsam ein, da diese bei zu intensivem Gebrauch auf die Performance Ihrer Anwendung drücken können.

Application-Management

Unter dem Begriff des Application-Management ist das Management des Lebenszyklus einer Anwendung zu verstehen. Jede Webanwendung hat in der Regel mehrere Stufen:

  • Development für die Entwicklung
  • Testing zum Ausführen von automatisierten Tests
  • Alternativ: Acceptance für Akzeptanztests der Fachabteilungen
  • Production für den Live-Betrieb der Anwendung

Sie sollten vermeiden, dass Sie in Ihren Konfigurationsdateien Weichen einbauen, welche Besonderheiten auf den einzelnen Stufen beachten. So könnten Sie Module haben, die Sie nur für die Entwicklung benötigen. Oder Sie möchten im Live-Betrieb das Caching der Konfiguration aktivieren, dass bei der Entwicklung zu Problemen führen könnte. Sinnvoller als Weichen in Ihren Konfigurationsdateien sind entsprechende Weichen in Ihrem Front-Controller. Dies ist bei einer Zend Framework 2-Anwendung die Datei /public/index.php im öffentlichen Verzeichnis.

define(
    'APPLICATION_ENV', 
    $_SERVER['SERVER_NAME'] == 'project.local' ? 'development' : 'production')
);
define('APPLICATION_ROOT', realpath(__DIR__ . '/..'));

[...]

switch (APPLICATION_ENV) {
    case 'production':
        $configFile = APPLICATION_ROOT . '/config/production.config.php';
        break;
    case 'development':
    default:
        $configFile = APPLICATION_ROOT . '/config/development.config.php';
        break;
}

Zend\Mvc\Application::init(include $configFile)->run();

In dem Beispiel wird die Stufe anhand des Servernamens definiert und es gibt nur zwei Stufen. Dies können Sie durch weitere Stufen oder andere Kriterien zur Identifizierung wie die IP-Adresse oder andere Umgebungsvariablen erweitern.

Performance

Selbstverständlich ist eine Anwendung, die ein PHP-Framework einsetzt, immer langsamer, als wenn Sie Ihre Anwendung auf optimierte einzelne Skripte aufbauen, denen jegliche Struktur fehlt. Mit dem Einsatz eines Framework können Sie aber in der Regel die Entwicklungszeit Ihrer Anwendung deutlich verkürzen, wenn sich die Entwickler mit dem Framework gut auskennen.Dennoch gibt es einige Tipps beim Einsatz vom Zend Framework 2, die Sie beachten sollten, um Ihre Anwendung performant zu halten:

  • Cachen Sie die Modul-Konfiguration [6].
  • Setzen Sie Class Maps für Ihre Module ein [7] und lassen Sie sich diese mit dem Class Map Generator automatisiert erstellen.
  • Setzen Sie Template Maps für Ihre Views ein [8] und nutzen Sie auch dort den entsprechenden Template Map Generator.
  • Verwenden Sie möglichst wenige parallele Routen, sondern arbeiten Sie eher mit hierarchischen Routen, um die Anzahl der Prüfungen beim Routing zu minimieren.
  • Setzen Sie Fremdmodule zur Performance-Optimierung ein:
    • EdpSuperluminal  [9]
    • SpiffyNavigation [10]
    • OcraCachedViewResolver [11]
  • Und der wichtigste Tipp: die meisten Performance-Bremsen liegen nicht direkt im Einsatz eines Frameworks, sondern meistens in Ihrer Business-Logik. Dazu zählen langsame Datenbankabfragen, aufwändige Berechnungen oder das Laden zu vieler Daten für den jeweiligen Zweck.

Ausblick auf das Zend Framework 3

Das Zend Framework 2 wurde im Jahr 2012 veröffentlicht. Derzeit (November 2015) arbeitet das Zend Framework-Team am nächsten Major-Release. Das Zend Framework 3 wird sich vom Vorgänger im Wesentlichen durch folgende Punkte unterscheiden:

  • Die einzelnen Komponenten werden unabhängiger voneinander, liegen in eigenen Repositories und bekommen einen eigenen Release-Zyklus und ein eigenes Team. Sie müssen nicht mehr alle Komponenten in Ihrem Projekt integrieren, wenn Sie nur einen Teil nutzen möchten.
  • Die Performance von grundlegenden Komponenten wird deutlich verbessert. So wird der Service-Manager bis zu 4x schneller und der Event-Manager bis zu 15x schneller je nach Anwendungsfall.
  • Viele inkonsistente Kleinigkeiten (verschiedene Parameteranordnungen oder -bezeichner) werden vereinheitlicht. Methodensignaturen werden entschlackt und der Einsatz der Komponenten vereinfacht.
  • Mit Zend\Expressive [12] steht eine performante Alternative zum klassischen MVC bereit. Mit dieser neuen Komponente können Sie PSR-7 [13] kompatible Middleware-Anwendungen implementieren, die sich vor allem durch eine deutlich einfachere Struktur und eine schnellere Verarbeitung von Anfragen auszeichnet.

Das Zend Framework 3 wird voraussichtlich in den nächsten Monaten erscheinen. Wenn Sie sich nun fragen, ob es überhaupt sinnvoll ist, zum jetzigen Zeitpunkt noch mit einer ZF2-Anwendung zu beginnen, gibt es darauf eine eindeutige Antwort: auf jeden Fall! Denn das ZF3 wird Ihre ZF2-Module ebenfalls ausführen können. Brüche in der Abwärtskompatibilität werden in einem Migration-Guide dokumentiert, so dass Sie eine Schritt-für-Schritt-Anleitung für die Migration vom ZF2 zum ZF3 an die Hand bekommen.

Fazit

Ich hoffe, ich konnte Ihnen einen ersten guten Einblick in das Zend Framework 2 bieten und vielleicht sogar Ihr Interesse wecken, einen neue Webanwendung auf Basis vom ZF2 zu implementieren. Wenn Sie die in diesem Artikel genannten Best Practices beachten, spart Ihnen das in Zukunft viel Zeit und Geld, da Sie sich unnötige Refaktorierungen ersparen.

Ich wünsche Ihnen viel Erfolg bei Ihren Projekten!

Autor

Ralf Eggert

Ralf Eggert ist Geschäftsführer der Travello GmbH, Diplom-Wirtschaftsinformatiker (FH) und Autor mehrerer Bücher über das Zend Framework. Er arbeitet mit dem Zend Framework, schreibt regelmäßig für Fachmagazine und ist als Trainer…
>> Weiterlesen

Publikationen

Das könnte Sie auch interessieren
Kommentare (0)

Neuen Kommentar schreiben