[ZF2] Navigation dynamisch erweitern

[AdSense-A]

Einleitung

breadcrumbs1

Eine Navigation dynamisch erweitern ist manchmal recht hilfreich bei einigen Anwendungen. Sei es z.B. das sich der Kunde ein Breadcrumb-Menü wünscht welches bei der Bearbeitungen von bestimmten Ausprägungen von Produkten ebenfalls angezeigt wird. Ab diesem Punkt birgt die Konfiguration einer Navigation auch im Zend Framework 2 in erster Linie ein Problem.

Dieses Problem ergibt sich daraus, dass sich im ZF2 nur statische routen für die Navigation definieren lassen. Was also tun, wenn der jeweilige Navigationspunkt z.B. eine dynamische ID beinhalten kann und die Route dann wie folgt aussieht:

/user/12/edit

In diesem Fall muss ist die 12 die UserID. Was also tun, wenn dieses Breadcrumb-Menü individuell sein muss, also abhängig ist von der UserID und trotz dessen dargestellt werden muss?

Navigation dynamisch erweitern

In einem solchen Fall muss ein neues Controller-Plugin her. Warum ein Controller-Plugin ist vermutlich die Frage die Sie sich dabei stellen?  Im Grunde genommen lässt sich diese Frage simpel beantworten. Sie könnten Ihren Service dahingehend erweitern, dass Sie dynamisch Ihre Menüpunkte erzeugen. Dies würde bedeuten, dass Sie in jedem Service in dem Sie diese Logik verwenden wollen den gleichen Sourcecode implementieren müssten. Dies führt wiederum zu Redundanzen. Wir als Entwickler wissen, dass Redundanzen im Sourcecode vermieden werden sollten, denn dies ist nicht gerade der Zweck der Erweiterbarkeit im Sinne der objektorientierten Programmierung. Eine Navigation gehört im technischen Sinne eher auf die Präsentationsebene, aber im der eigentlichen Präsentationsebene hat wiederum eine rein logische Implementation nichts verloren. Also was steht am nähesten zu der Präsentationsschicht? Logischerweise der Controller. Aber auch nicht in jeder Action eines Controllers wird das Breadcrumb-Menü implementiert. Dahingehend wäre es schon ziemlich gut, wenn das notwendige Objekt für die Erweiterung der Navigation / Breadcrumb, nur dann zur Verfügung steht, sprich erzeugt wird, wenn es gebraucht wird.

Dahingehend eignet sich ein eigenständiges Controller-Plugin am besten, um dieses zu realisieren.

Material zur Realisierung des Plugins

Um die Navigation dynamisch erweitern zu können, brauchen Sie einerseits die eigentliche Navigation, wie aber auch eine Methode die dazu dient das Modul zu erweitern. Dies bedeutet im Grunde genommen, dass eine Klasse geschaffen werden muss, die eine direkte Abhängigkeit zu einer Navigation besitzt. Aus diesem Grunde benötigen Sie folgende Komponeten:

  • Pluginklasse
  • Factoryklasse

In meinem Falle ist die Pluginklasse benannt als “DynamicBreadcrumb” und die Factoryklasse als “DynamicBreadcrumbFactory” auf diese beiden Klassen werde ich im nächsten Schritt eingehen.

 

Praktische Implementation

Um nun die Navigation dynamisch erweitern zu können, komme ich hier zur eigentlichen Implementation des Sourcecodes.

Als erstes zur eigentlichen Controller-Plugin Klasse. Diese benötigt im Grunde genommen abgesehen vom Konstruktur drei weitere Methoden:

  • setNavigation
  • getNavigation
  • addBreadcrumb  oder je nachdem kann diese auch addNavigation heißen

Da die Navigation essentiell notwendig für diese Klasse ist, wäre es die saubere Art und Weise, wenn der Konstruktor ebenfalls eine Navigationsklasse erwartet.

 

Plugin-Klasse

<?php
/**
 * Description of DynamicBreadcrumb
 *
 * @author IBeliaev
 */

namespace Application\Controller\Plugin;

use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\Navigation\Navigation;

class DynamicBreadcrumb extends AbstractPlugin{

    protected $navigation;

    public function __construct(Navigation $nav){
        $this->setNavigation($nav);
    }

    public function setNavigation(Navigation $nav){
        $this->navigation = $nav;
    }

    public function getNavigation(){
        return $this->navigation;
    }

    public function addBreadcrumb($id, array $aPage, array $aParams = array(), $hide = false){
        /* @var $page \Zend\Navigation\Page\AbstractPage */
        $page = $this->getNavigation()->findOneById($id);

        if($page){
            $aPage["hide"] = $hide;
            $page->addPage($aPage);
            if(count($aParams) > 0){
                $page->set("params", $aParams);
            }
            $status = true;
        }else{
            $status = false;
        }
        return $status;
    }
}
  • Die AddBreadcrumb Methode erwartet vier Übergabeparameter. Der Erste ist der Identifier, dieser Identifier bestimmt den Punkt in der Navigationshierachie nach welchem Navigationspunkt sich der jeweilige übergebene Breadcrumb einklicken soll. Als grundlegendsBeispiel haben wir diese Navigation:
  • Home (ID: home)
  • Kunden verwalten (ID: customers)
    • Kunde 12 – dynamischer Navigationspunkt (ID: Customer_12_edit)

 

Die ID die diese Methode nun erwarten würde, wenn der Unterpunkt “Kunden verwalten” erweitert werden muss wäre die ID “customers”.

[AdSense-B]

Als weiteren Übergabeparameter erwartet die Methode ein Array. Dieses Array mit dem Übergabeparameternamen “aPage” erwartet wiederum konkrete Informationen zu diesem Navigationspunkt z.B. wie heißt das Label? Welche Route hat es? Welcher  Action-Parameter? Welcher Controller? Welche Id? Dies muss hier definiert werden, im Grunde genommen Routinginformationen abhängig von dem jeweiligen Typen der Navigation, ob nun MVC oder ähnliches muss hier bestimmt werden.  der Übergabeparameter Params, sind die gewöhnlichen Parameter für eine Navigation.

Der letzte Übergabeparameter “hide” definiert ob das jeweilige Attribut in der Standard-Navigation versteckt werden soll. In einer DefaultNavigation von Zend, würde dieses Attribut ignoriert werden. Dieses Attribut existiert in diesem Beispiel als ein Beispiel für eine customized Navigation, sprich eigenes Partial für das rendern der Navigation.

 

Factory-Klasse

Die Factoryklasse für das Controller-Plugin ist recht simpel strukturiert. Es wird in diesem Fall lediglich nur die Navigation übergeben:

 

<?php

/**
 * Description of DynamicBreadcrumbFactory
 *
 * @author IBeliaev
 */
namespace Application\Controller\Plugin;

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

class DynamicBreadcrumbFactory implements FactoryInterface{

    public function createService(ServiceLocatorInterface $pluginManager) {
        $serviceLocator = $pluginManager->getServiceLocator();

        /* @var $navigation \Zend\Navigation\Navigation */
        $navigation = $serviceLocator->get('default');
        $plugin = new DynamicBreadcrumb($navigation);

        return $plugin;
    }

}

Anschließend wird die Factoryklasse in der Modulkonfiguration verlinkt. Dies geschieht über die jeweilige Modulkonfiguration, in meinem Falle über das Modul “Application”, welches bekannt sein sollte aus der Skeleton-Applikation vom Zend Framework.

return array(
	'service_manager' => array(
		'factories' => array(
			'default' => 'Zend\Navigation\Service\DefaultNavigationFactory',
		),
	),
	'controller_plugins' => array(
        'factories' => array(
            'DynamicBreadcrumb' => 'Application\Controller\Plugin\DynamicBreadcrumbFactory',
        ),
    ),
);

Anschließend kann das Plugin über jeden Controller im Beispiel mit folgendem Sourcecode ausgeführt werden:

 $this->DynamicBreadcrumb()->addBreadcrumb("user_index", array(
            'uri' => $this->getRequest()->getRequestUri(),//current page URI
            'label' => "Kunden ".$customerID." verwalten",
            'active' => true,
        ));

Leave a Reply