Hier geben wir Einblicke in das Unternehmen, Gedanken, How-Tos, Wissenswertes und News, die sich aus der Programmierung und Projekten ergeben. Auch unsere Open Source Aktivitäten begleiten wir hier.

Corporate Blog der prooph software GmbH

Zend Framework 2 dynamische Navigation - Tutorial 4

Zend Framework 2 dynamische Navigation - Tutorial 4

Die Möglichkeiten von Zend Navigation sind recht umfangreich, doch kommt man im Laufe der Umsetzung eines Projekts nicht selten trotzdem an einen Punkt, wo es mit den Boardmitteln, die ich in den ersten Teilen dieser Tutorial-Reihe behandelt habe, allein nicht mehr weitergeht. Der flexible Aufbau des gesamten Zend Framework 2 bietet einfache Zugriffs- und Anpassungsmöglichkeiten aller Komponenten. Auch eine Navigation lässt sich über verschiedene Wege erstellen und in Abhängigkeit von unterschiedlichen Zuständen einer Anwendung dynamisch anpassen, beispielsweise ein erweitertes Menü für authentifizierte User nach dem Login oder ein Warenkorb, den ein Kunde mit Produkten füllt, die einzeln auf die Produktbeschreibung verlinken.

Navigation über Module erweitern

Bisher haben wir unser Menü immer über die Konfiguration unserer Anwendung erstellt. Damit lassen sich augenscheinlich nur statische Menüs definieren. Ein großer Vorteil gegenüber dem Zend Framework 1 ist jedoch, dass sich ein Menü durch Module bereits auf Konfigurationsebene erweitern lässt. Ich definiere im folgenden Beispiel eine Navigation mit nur einer Seite, die auf die Startseite meiner Anwendung verweist.

'navigation' => array(
    'application' => array(
        'home' => array(
            'label' => 'home',
            'route' => 'home',
        ),
    ),
),

Neben meinem Application-Modul habe ich ein User-Modul aktiviert, das Benutzerfunktionalitäten bereitstellt. In der Modul-Konfiguration zum User-Modul wird der Navigation ein weiterer Menüpunkt (Login) angefügt:

'navigation' => array(
    'application' => array(
        'user_login' => array(
            'label' => 'login',
            'route' => 'user/login',
            'order' => 10,
        ),
    ),
),

Über den Konfigurationsschlüssel order lässt sich die Reihenfolge der Menüpunkte steuern. Ist der Schlüssel bei einer Seite nicht angegeben, wird Null als Wert verwendet. Die Menüpunkte werden aufsteigend sortiert. Da die home-Seite keine order Angabe besitzt, erscheint sie als erster Menüpunkt, gefolgt vom neu hinzugekommenen login.

<ul class="navigation">
    <li class="active">
        <a href="/">home</a>
    </li>
    <li>
        <a href="/user/login">Login</a>
    </li>
</ul>

Menüpunkte dynamisch hinzufügen

Noch dynamischer ist ein Menü, das von unterschiedlichen Zuständen der Anwendung abhängt. Ein weiterer Menüpunkt soll auf das User-Profil führen und nur erscheinen, wenn sich ein User erfolgreich eingeloggt hat. Eine Möglichkeit wäre, die Navigation mit einem Berechtigungskonzept zu verbinden. An dieser Stelle schauen wir uns eine andere Möglichkeit an. Mein User-Modul registriert den eingeloggten User über ein eigenes Objekt im ServiceManager. Die Vorgänge dazu schauen wir uns nicht genauer an. Interessant für uns ist nur, dass das User-Objekt über den ServiceManager bezogen werden kann und wir über die Methode isLogedIn() des User-Objekts den Status des aktuellen Besuchers abrufen. Um unsere Navigation nun in Abhängigkeit des Status mit einem Menüpunkt zu erweitern, registrieren wir einen Listener beim EventManager des Application-Objekts. Am einfachsten geht das über die Module-Klasse des User-Moduls. Dort können wir eine onBootstrap() Methode definieren, die automatisch als Listener des Bootstrap MvcEvents registriert wird.

<?php
/**
 * User Module
 * 
 * @author Alexander Miertsch contact@prooph.de
 */
namespace User;

use Zend\Mvc\MvcEvent;
use Zend\Navigation\Page\Mvc as MvcPage;
use Zend\Navigation\AbstractContainer;

class Module
{
    public function onBootstrap(MvcEvent $e) 
    {
        [...]
    }
    
    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }

    public function getAutoloaderConfig()
    {
        return array(
            'Zend\Loader\StandardAutoloader' => array(
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ 
                    . '/src/' . __NAMESPACE__,
                ),
            ),
        );
    }
}

Die onBootstrap()-Methode bekommt das MvcEvent als Parameter übergeben, womit wir Zugriff auf alle wichtigen Komponenten haben, um weitere Listener für spätere Events im MVC-Prozess zu registrieren. Der Listener für die Anpassung der Navigation muss nach dem Dispatch-Prozess ausgeführt werden, also nach der Ausführung des Controllers – welcher auch immer für den aktuellen Request zuständig war. Denn wenn dies der User-Controller war, der gerade ein erfolgreiches Login bzw. Logout durchgeführt und den Status des User-Objekts damit geändert hat, bekommt der Listener anschließend den aktuellen Status übermittelt. Lassen wir unseren Listener mal auf das Render-Event des Application-Objekts lauschen.

$e->getApplication()
->getEventManager()
->attach(
    MvcEvent::EVENT_RENDER, 
    function(MvcEvent $e) {
        [...]
    },
    100
);

In diesem Beispiel wird eine anonyme Funktion als Listener registriert. Die Funktionalität lässt sich – zur besseren Ordnung – auch in eine Klasse auslagern, für uns reicht jedoch diese Variante aus. Ich habe eine hohe Priorität für den Listener gewählt, damit er noch ausgeführt wird, bevor die Navigation im View-Script gerendert wird. Im Listener selbst holen wir uns über das MvcEvent und den ServiceManager das User-Objekt und prüfen, ob der aktuelle User eingeloggt ist und wir die Navigation somit anpassen müssen.

$user = $e->getApplication()
    ->getServiceManager()
    ->get('user');

if ($user->isLogedIn()) {
    [...]
}

Liefert User::isLogedIn() TRUE zurück, erstellen wir händisch ein Zend\Navigation\Page\Mvc Objekt und befüllen es mit Werten, die wir sonst über die Konfiguration angeben würden.

$myProfilePage = new MvcPage();
$myProfilePage
    ->setRouteMatch($e->getRouteMatch())
    ->setRouter($e->getRouter())
    ->setRoute('user/my_profile')
    ->setLabel('mein Profil')
    ->setOrder(20);

Neben den Werten für Route, Label und Order wird auch das RouteMatch-Objekt und der Router aus dem MvcEvent an das Page-Objekt übergeben. Diese Schritte übernimmt sonst die Navigation-Factory, bevor die Page-Objekte an den Navigation-Container übergeben werden, den die Factory anhand unserer Konfiguration erstellt. Die MvcPages benötigen die beiden Instanzen für die Generierung der Links und die Prüfung auf die aktive Seite innerhalb der Navigation. Um die festen Abhängigkeiten zu vermeiden, wäre es an dieser Stelle eleganter, entweder die Instanzierung einer MvcPage über eine Factory zu realisieren, die das RouteMatch-Objekt und den Router selbstständig injiziert oder wir müssten Zend\Di entsprechend konfigurieren und das Page-Objekt darüber abrufen. Zur Veranschaulichung der Abhängigkeiten soll uns die einfache Variante hier aber genügen. Abschließend müssen wir den neuen Menüpunkt im gewünschten Navigation-Container bekannt geben. Da der Navigation-Container über den ServiceManager abrufbar ist, stellt uns das vor keine große Aufgabe:

* @var $appNavi AbstractContainer */
$appNavi = $e->getApplication()
    ->getServiceManager()
    ->get('app_navigation');       

$appNavi->addPage($myProfilePage);

Bestehende Menüpunkte anpassen

Wir können nicht nur neue Menüpunkte zur Navigation hinzufügen, sondern auch bestehende anpassen oder löschen. Die Möglichkeiten zur Manipulation lassen sich aus der Klasse Zend\Navigation\AbstractContainer ableiten. Exemplarisch zeige ich hier die Selektion einer Seite anhand ihres Labels und die anschließende Anpassung der Route und des Labels, damit aus dem Menüpunkt Login der Menüpunkt Logout wird.

$loginPage = $appNavi
    ->findOneByLabel('login');

$loginPage->setRoute('user/logout')
    ->setLabel('logout');

Und hier nun die gesamte Module-Klasse des User-Moduls im Überblick:

<?php
/**
 * User Module
 * 
 * @author Alexander Miertsch contact@prooph.de
 */
namespace User;

use Zend\Mvc\MvcEvent;
use Zend\Navigation\Page\Mvc as MvcPage;
use Zend\Navigation\AbstractContainer;

class Module
{
    public function onBootstrap(MvcEvent $e) 
    {
        $user = new User();
        
        $e->getApplication()
            ->getServiceManager()
            ->setService('user', $user);
        
        $e->getApplication()
            ->getEventManager()
            ->attach(
                MvcEvent::EVENT_RENDER, 
                function(MvcEvent $e) {
                    $user = $e->getApplication()
                        ->getServiceManager()
                        ->get('user');
                    
                    if ($user->isLogedIn()) {
                        $myProfilePage = new MvcPage();
                        $myProfilePage
                            ->setRouteMatch($e->getRouteMatch())
                            ->setRouter($e->getRouter())
                            ->setRoute('user/my_profile')
                            ->setLabel('mein Profil')
                            ->setOrder(20);
                           
                        
                        /* @var $appNavi AbstractContainer */
                        $appNavi = $e->getApplication()
                            ->getServiceManager()
                            ->get('app_navigation');       
                        
                        
                        $appNavi->addPage($myProfilePage);
                        
                        $loginPage = $appNavi
                            ->findOneByLabel('login');
                        
                        $loginPage->setRoute('user/logout')
                            ->setLabel('logout');
                    }
                },
                100
            );  
    }
    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }

    public function getAutoloaderConfig()
    {
        return array(
            'Zend\Loader\StandardAutoloader' => array(
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ 
                    . '/src/' . __NAMESPACE__,
                ),
            ),
        );
    }
}

Sie suchen einen Software Dienstleister für Ihr Projekt: Projektanfrage stellen

Blog Artikel mit ähnlichen Themen