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 Navigation - Tutorial 1

Zend Framework 2 Navigation - Tutorial 1

In diesem Zend Framework 2 Tutorial erkläre ich Step by Step, wie sich mit Zend\Navigation und dem passenden ViewHelper ein Navigationsmenü erzeugen lässt, das automatisch die aktive Seite erkennt und über die ZF2 Anwendungs-Konfiguration erstellt bzw. erweitert werden kann. Anpassungsmöglichkeiten, um z.B. ein Dropdown Menü für das Bootstrap CSS Framework vorzubereiten oder eine Breadcrumbs Navigation zu erzeugen, vervollständigen das Tutorial in einer Reihe von weiteren Artikeln.

Voraussetzung für das Tutorial

Um den Erläuterungen in diesem Tutorial folgen zu können, sollten Grundkenntnisse über das Zend Framework 2 vorhanden sein. Insbesondere das Arbeiten mit den ServiceManagern und dem ModuleManager bildet die Basis für alle weiteren Schritte.

Navigation im ZF2

Objektorientierung macht natürlich auch vor einer Webseiten-Navigation nicht halt, daher benötigen wir als erstes ein Objekt, mit dem die Seiten-Navigation aufgebaut werden kann. Zend\Navigation\Navigation bietet einen Standard-Container, der Zend\Navigation\Page\AbstractPage Objekte aufnehmen und in einer Hierarchie vorhalten kann. Kombiniert mit der Service-Factory Zend\Navigation\Service\DefaultNavigationFactory können wir eine Navigation komplett per Konfiguration abbilden. Dazu registrieren wir in der ServiceManager Konfiguration die DefaultNavigationFactory und vergeben einen Alias für den Navigation Container. In diesem Fall lautet der Alias app_navigation.

'service_manager' => array(
    'factories' => array(
        'app_navigation' 
            => 'Zend\Navigation\Service\DefaultNavigationFactory',
    ),
),

Den Alias benötigen wir später, um per ViewHelper auf die Navigation zuzugreifen oder aber das Navigation-Objekt vom ServiceManager zu beziehen, um manuell weitere Seiten hinzuzufügen.

Navigation-Factory

Zum Verständnis der Zusammenhänge lohnt sich ein Blick in den Sourcecode der Zend-Komponenten. Die Zend\Navigation\Service\DefaultNavigationFactory beinhaltet selbst nicht viel, aber weist uns den Weg.

/**
 * Default navigation factory.
 *
 * @category  Zend
 * @package   Zend_Navigation
 */
class DefaultNavigationFactory extends AbstractNavigationFactory
{
    /**
     * @return string
     */
    protected function getName()
    {
        return 'default';
    }
}

Es gibt eine getName()-Methode die nur default zurückgibt. Beim Blick in die parent Klasse AbstractNavigationFactory, die die eigentliche Logik zur Erzeugung eines Navigation-Containers beinhaltet, erklärt sich der Sinn dieser Methode.

/**
* @param ServiceLocatorInterface $serviceLocator
* @return \Zend\Navigation\Navigation
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
   $pages = $this->getPages($serviceLocator);
   return new Navigation($pages);
}

Die createService()-Methode wird vom Zend\ServiceManager\FactoryInterface vorgeschrieben, damit der ServiceManager das gewünschte Navigation-Objekt von der Factory erzeugen lassen kann. Intern wird die geschützte Methode getPages() aufgerufen. Dort schließt sich der Kreis und wir erkennen den Zusammenhang zwischen der DefaultNavigationFactory::getName()-Methode und der Logik in der AbstractNavigationFactory.

$configuration = $serviceLocator->get('Configuration');
...
$pages  = $this->getPagesFromConfig(
    $configuration['navigation'][$this->getName()]
);

Aus diesen beiden Zeilen lässt sich ableiten, wie wir die Navigation konfigurieren können. Die Factory holt sich vom ServiceManager die zusammengeführte Konfiguration aller Module plus der Konfigurationen, die unter APPLICATION_ROOT /config/autoload abgelegt sind. Innerhalb des Konfig-Arrays sucht die Factory nach dem Schlüssel navigation und unterhalb des Schlüssels nach einem weiteren Schlüssel, der den Namen aus der getName()-Methode trägt.

Alles verstanden? Falls nicht, wird es denke ich gleich klarer.

Seiten konfigurieren

Wir können innerhalb unserer Zend Framework 2 Konfiguration (z.B. APPLICATION_ROOT /Module/Application/config/module.config.php) einfach die folgenden Zeilen in das Konfig-Array einfügen und unsere Navigation wird automatisch mit den entsprechenden Seiten gefüllt.

//global config key for all navigation configurations
'navigation' => array(
     //name of the DefaultNavigation created by DefaultNavigationFactory
     'default' => array(
         //config of first page
         'home' => array(
             'label' => 'home',
             'route' => 'home',
         ),
         //config of another page
         'freelancer_profile' => array(
             'label' => 'freelancer',
             'route' => 'application/default',
             'controller' => 'freelancer',
             'action' => 'profile',
         ),
     ),
 ),

Ich hoffe, der Zusammenhang ist jetzt erkennbar. Ansonsten lest einfach weiter. Ich erläutere gleich noch, wie wir ein zweites Menü erstellen können. Aber vorher die Erklärungen zu den konfigurierten Seiten. Diese werden unterhalb des Navigationsnamens als Hash-Table angegeben. Das Zend Framework bringt zwei Arten von Seiten-Objekten mit: Zend\Navigation\Page\Mvc und Zend\Navigation\Page\Uri. Wenn der Typ der Page nicht explizit angegeben ist, versucht die AbstractNavigationFactory selbständig den Typ zu ermitteln. Eine Seite wird als Mvc-Page erkannt, wenn in der Konfiguration route, controller oder action angegeben ist. Mein erster Navigationspunkt führt auf die Startseite meiner Anwendung.

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

Die Seite bekommt den internen Namen home. Unter diesem Schlüssel kann ich nun das Page-Objekt konfigurieren. Der geübte Zend Framework Programmierer schaut an dieser Stelle in den Sourcecode der Klasse Zend\Navigation\Page\Mvc und in die parent Klasse Zend\Navigation\Page\Abstract, um alle Optionen zu ermitteln. Ich gehe an der Stelle nicht weiter auf die Optionen ein, denn diese sprechen für sich. Es gibt aber leider eine Stolperfalle, die etwas Nachbesserung erfordert. Was ich damit meine, erfahrt ihr im Teil 2 meiner Tutorial Reihe zu Zend\Navigation.

Navigation anzeigen

Unsere Konfiguration steht. Der Rest ist nun ganz simpel. Wir fügen den Aufruf eines ViewHelpers an der entsprechenden Stelle in unserer View ein, also z.B. im Application-Layout:

<div class="header">
    <?php echo $this->navigation('app_navigation')->menu(); ?>            
</div>

Ok, es sind eigentlich zwei ViewHelper im Spiel und ganz so simpel es auch nicht. Der erste ViewHelper ist Zend\View\Helper\Navigation. Diesem übergeben wir den Alias aus unserer ServiceManager Konfiguration, den wir dem Objekt zugeordnet haben, das durch Zend\Navigation\Service\DefaultNavigationFactory erzeugt wird. Also ein Zend\Navigation Objekt, das den Zugriff auf unsere Pages aus der Konfiguration ermöglicht. Versucht diesen Vorgang auf jeden Fall nachzuvollziehen. Er ist eine sehr wichtige Grundlage für den 2. Teil meiner Tutorial Reihe zu Zend\Navigation.
Der Navigation-ViewHelper hat eine Verbindung zum ServiceManager und holt sich intern über diesen das Zend\Navigation-Objekt mit dem Alias app_navigation. Über das Fluent Interface des ViewHelpers erfolgt dann etwas "PHP Magic", denn menu() ist nicht etwa eine Methode vom Navigation-ViewHelper, sondern ein eigener ViewHelper, nämlich Zend\View\Helper\Navigation\Menu. Das echo bewirkt den Aufruf einer __toString()-Methode. Diese ruft wiederum die render()-Methode des Menu-ViewHelpers auf, und (um es kurz zu machen) der ViewHelper iteriert rekursiv über das Zend\Navigation-Objekt und erzeugt eine ggf. verschachtelte ul in HTML. Aus den Mvc-Pages mit den Routing-Angaben wie route, controller und action werden automatisch URIs generiert und ins href-Attribut der Links eingefügt, konfigurierte Klassen gesetzt usw. Ein nettes Feature ist auch, dass die aktive Seite automatisch anhand des aktuellen Application-Routings ermittelt wird und das passende li-Element in der Navigation mit einer active-Klasse versehen wird. Wenn ihr die Startseite eurer Applikation aufruft, sieht das Ganze im HTML Quelltext dann so aus:

<ul class="navigation">
    <li class="active">
        <a href="/">home</a>
    </li>
    <li>
        <a href="/application/freelancer/profile">freelancer</a>
    </li>
</ul>

Ein zweites Menü erstellen

Eigentlich sollte das jetzt ohne meine Hilfe klappen. Falls es bei dem ein oder anderen noch nicht klick gemacht hat, hier das Vorgehen im Zend Framework 2 in Kurzfassung.
Wir haben gelernt, dass Zend\Navigation-Objekte über den ServiceManager und eine Factory erzeugt werden. Wir haben auch gesehen, dass die Arbeit von einer abstrakten Factory übernommen wird. Diese gibt der ableitenden Klasse die Möglichkeit, einen Namen für die Navigation zu vergeben. Also erstellen wir jetzt einfach eine eigene Factory:

<?php
/**
 * Sitemenu navigation factory
 * 
 * @category Demo Application
 * @package Application
 * @author Alexander Miertsch <contact@prooph.de>
 * @copyright (c) 2012, Alexander Miertsch
 */
namespace Application\Model\Navigation\Service;

use Zend\Navigation\Service\AbstractNavigationFactory;

class SitemenuNavigationFactory extends AbstractNavigationFactory
{
    /**
     * Returns config name of the navigation
     * 
     * @return string
     */
    public function getName()
    {
        return "sitemenu";
    }
}

Anschließend ergänzen wir die Seitenmenü-Navigation in der Konfiguration.
Das Rückgabe-Objekt unserer Factory wird auf den Alias site_navigation gemappt:

'service_manager' => array(
    'factories' => array(          
        'app_navigation' => 'Zend\Navigation\Service\DefaultNavigationFactory',
        'site_navigation' 
            => 'Application\Model\Navigation\Service\SitemenuNavigationFactory',
    ),
),

Und unserem Site-Menu müssen natürlich auch Seiten hinzugefügt werden. Hierfür kommt der in der Factory vergebene Name sitemenu zum Einsatz:

//global config key for all navigation configurations
'navigation' => array(
    //name of the DefaultNavigation created by DefaultNavigationFactory
    'default' => array(
        //config of first page
        'home' => array(
            'label' => 'home',
            'route' => 'home',
        ),
        //config of another page
        'freelancer_profile' => array(
            'label' => 'freelancer',
            'route' => 'application/default',
            'controller' => 'freelancer',
            'action' => 'profile',
        ),
    ),
    //sitemenu navigation
     'sitemenu' => array(
        'company_profile' => array(
            'label' => 'company profile',
            'route' => 'application/default',
            'controller' => 'company',
            'action' => 'profile',
        ),
        'project_site' => array(
            'label' => 'project site',
            'route' => 'application/default',
            'controller' => 'project',
            'action' => 'show',
        ),
    ),
),

Ausgeben lässt sich das Navigationsmenü über den bereits bekannten Weg:

<div class="sidebar">
    <?php echo $this->navigation('site_navigation')->menu(); ?>            
</div>

Als Ergebnis erhalten wir dieses HTML-Markup:

<ul class="navigation">
    <li>
        <a href="/application/company/profile">company profile</a>
    </li>
    <li>
        <a href="/application/project/show">project site</a>
    </li>
</ul>

Fazit

Zend\Navigation wirkt im ersten Moment komplex, bietet aber für wenig Aufwand viele nützliche Funktionen. Ein Navigationsmenü lässt sich per Konfiguration erstellen und die einzelnen Menüpunkte werden direkt mit dem Routing der Anwendung verknüpft. Somit müssen URIs nicht hardcodiert in die href-Attribute der Links geschrieben werden, sondern können bei Bedarf angepasst werden. Menüpunkte lassen sich "on the fly" ergänzen und die aktive Seite wird automatisch erkannt und kann über eine eigene Klasse per CSS im Navigationsmenü hervorgehoben werden.

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

Blog Artikel mit ähnlichen Themen