Gehen wir von einem eigenen Module namens CRM (Customer Relationship Management), welches wir via Zend Studio oder den ZFTools erstellen.
Unseren initialer Controller nennen wir Index(Controller).
Da ein CRM aus verschiedenen Bereichen besteht, in unserem Beispiel aus Kunde und Lieferant, möchte man nun gerne unterhalb vom src Verzeichnis noch weitere “Namespaces” Customer und Supplier anlegen um damit innerhalb unseres CRM Modules weitere logisch abgetrennte Bereiche zu definieren:
Folgendes “Tutorial/Beispiel” zeigt, wie dies im Zend Framework 2 zu bewerkstelligen ist:
Die entstehende Verzeichnisstruktur ist folgende:
- module - CRM - config - src - CRM - Controller - IndexController.php - view - crm - index - index.phtml
Namespaces/Directories
Unsere neue Verzeichnisstruktur sieht nun also folgendermaßen aus:
- module - CRM - config - src - CRM - Controller - IndexController.php - Customer - Controller - CustomerController.php - Supplier - Controller - SupplierController.php - view - crm - index - index.phtml - customer <-- Namespace - customer <-- Controller - edit.phtml <-- Action - supplier - supplier - add.phtml
Innerhalb des CRM Namespaces haben wir einen IndexController, der später unsere Startseite/Dashboard darstellen könnte. Für den Customer Namespace/Controller haben wir beispielhaft eine edit Action, sowie für den Supplier Controller eine add Action im Controller angelegt (daher die Verzeichnisnamen unterhalb von View).
Routing
Machen wir uns Gedanken über das Routing zu den verschiedenen Namespaces innerhalb des CRM Modules.
Beispielsweise möchten wir über folgende URLs auf die verschiedenen CRM Bereiche Zugreifen:
domain.de/crm/ => Startseite
domain.de/crm/customer/edit => Kunde editieren
domain.de/crm/supplier/add => Lieferant hinzufügen.
Wir haben nun also ModuleName/Namespace/Action definiert.
Module Config
Werfen wir nun mal einen Blick in die module.config.php und ändern diese wie folgt ab:
'router' => array( 'routes' => array( 'crm/' => array( 'type' => 'Literal', 'options' => array( 'route' => '/crm', 'defaults' => array( '__NAMESPACE__' => 'CRM\Controller', 'controller' => 'Index', 'action' => 'index', ), ), 'may_terminate' => true ), [...]
In Zeile 3 haben wir die Route als "crm/" benannt. Dies ist sicherlich Geschmacksache, hilft uns gleich aber bei einer komplexeren Routen Definition schneller den Überblick zu behalten.
Unsere eigentliche Route (Zeile 6) definieren wir als /crm und ordnen nun den CRM\Controller\Index Controller, für unsere Startseite, zu.
Ein Aufruf im Browser sollte nun also auch funktionieren. Die restlichen URLs fügen wir wie folgt hinzu:
'router' => array( 'routes' => array( 'crm/' => array( 'type' => 'Literal', 'options' => array( 'route' => '/crm', 'defaults' => array( '__NAMESPACE__' => 'CRM\Controller', 'controller' => 'Index', 'action' => 'index', ), ), 'may_terminate' => true ), // Hier kommen die weiteren Routen dazu: 'crm/customer' => array( 'type' => 'Literal', 'options' => array( 'route' => '/crm/customer', 'defaults' => array( '__NAMESPACE__' => 'Customer\Controller', 'controller' => 'Customer', 'action' => 'index', ), ), 'may_terminate' => true, 'child_routes' => array( 'default' => array( 'type' => 'Literal', 'options' => array( 'route' => '/edit', 'defaults' => array( '__NAMESPACE__' => 'Customer\Controller', 'controller' => 'Customer', 'action' => 'edit', ), ), ), ), // Es folgt die Route für den Supplier! [...]
Wir haben nun in Zeile 18 eine neue Route als "crm/customer" benannt. Damit wissen wir nun, es handelt sich um das CRM Module und darin den Customer Namespace. Sicherlich sind hier auch andere Ansätze gut.
Unsere Route in Zeile 21 definieren wir als /crm/customer mit der Zuordnung zum CustomerController und der Standard Action "Index" (hier könnte eine Übersicht aller Kunden erfolgen).
In Zeile 29 werden nun Child Routes hinzugefügt. (Diese Child Routes erweitern immer die Parent Route!)
Unsere Route (Zeile 33) definiert "/edit" (inkl. der Parent Route ergibt sich nun also /crm/customer/edit als vollständige Route) die Route zum Customer\Controller\Customer auf die Action edit.
Test & Fix
Wie wir sehen werden, funktioniert das nun überhaupt nicht! Wieso?
Wenn wir uns die Meldung im Browser (ZendSkeletonApplication) anschauen, sehen wir folgende Meldung:
A 404 error occurred Page not found. The requested controller could not be mapped to an existing controller class. Controller: Customer\Controller\Customer(resolves to invalid controller class or alias: Customer\Controller\Customer) No Exception available
Das Framework ist scheinbar nicht in der Lage einen passenden Controller zu finden.
Zurück in der module.config.php haben wir die neuen Controller noch nicht verfügbar gemacht!
Folgender Code macht dem Framework unsere neuen Controller Customer und Supplier bekannt.
'controllers' => array( 'invokables' => array( 'CRM\Controller\Index' => 'CRM\Controller\IndexController', 'Customer\Controller\Customer' => 'Customer\Controller\CustomerController', 'Supplier\Controller\Supplier' => 'Supplier\Controller\SupplierController', ), ),
Browser neuladen!
Nun ist es noch schlimmer geworden:
Fatal error: Class 'Customer\Controller\CustomerController' not found in ....\vendor\zendframework\zendframework\library\Zend\ServiceManager\AbstractPluginManager.php
Woran liegt das? Das Framework kann den Controller Namen Customer/Controller/CustomerController zwar nun auflösen, scheitert aber beim Autoloading dieser Klasse. Daher ist diese Klasse beim instanziieren nicht verfügbar = Fatal Error!
Um das zu korrigieren, müssen wir uns einmal die Module.php Datei unseres CRM Modules anschauen.
public function getAutoloaderConfig() { return array( 'Zend\Loader\ClassMapAutoloader' => array( __DIR__ . '/autoload_classmap.php', ), 'Zend\Loader\StandardAutoloader' => array( 'namespaces' => array( __NAMESPACE__ => __DIR__ . '/src/' . str_replace('\\', '/' , __NAMESPACE__), ), ), ); }
... und in Zeile 8 entdecken wir den Grund für das fehlgeschlagene Autoloading. Daher erweitern wir das 'namespaces' Array um die Einträge für Customer und Supplier. Anschließend sollte diese Funktion so aussehen:
public function getAutoloaderConfig() { return array( 'Zend\Loader\ClassMapAutoloader' => array( __DIR__ . '/autoload_classmap.php', ), 'Zend\Loader\StandardAutoloader' => array( 'namespaces' => array( __NAMESPACE__ => __DIR__ . '/src/' . str_replace('\\', '/' , __NAMESPACE__), 'Customer' => __DIR__ . '/src/Customer/', 'Supplier' => __DIR__ . '/src/Supplier/', ), ), ); }
Dazu sollte gesagt werden, das dies auch auf mehreren anderen Wegen hätte erreicht werden können.