Nach einer kleinen Pause geht es heute mit dem dritten Teil der Entwicklungs-Tipps für Magento weiter. In Teil 2 habe ich gezeigt, dass der Code von Magento nach dem MVC-Architektur-Pattern organisiert ist. Auf gut Deutsch heißt das: der Code für die Geschäftslogik (Model), die Darstellung (View) und die grundlegenden Abläufe im Shop (Controller) wird so gut wie möglich getrennt.
Heute sehen wir uns an, wie man in Magento die Controller-Schicht erweitert, ohne die Updatefähigkeit zu beeinträchtigen.
[toc levels=3 title=“Inhalt“]
6. Anpassen von Controllern & Actions
Die Controller-Schicht muss in der Regel dann erweitert werden, wenn man neue Funktionen in den Onlineshop einführt, die zuweilen auch eigene Seiten im Shop erfordern. Manchmal handelt es sich um etwas völlig Neues (z.B. einen eigenen Webshopbereich, in dem Veranstaltungstipps angezeigt werden), manchmal wird ein bestehendes Modul um neue Fähigkeiten erweitert (z.B. die Möglichkeit, über ein Bestellformular viele verschiedene Produkte zugleich zu bestellen) und manchmal wird eine bestehende Funktionalität verändert (z.B. noch einmal den Lagerstand bei der Warenwirtschaft nachfragen, bevor das Produkt in den Einkaufswagen gelegt wird).
Zuerst sollte man sich daher überlegen: was möchte ich erreichen und um welchen der obigen Fälle handelt es sich? Analog dazu gibt es drei Möglichkeiten, die Controller-Schicht zu modifizieren.
- Einen eigenen Controller erstellen
Ein Bereich für Veranstaltungen hat nichts mit den bisherigen Modulen (Kategorie- und Produktseiten, Kundenbereich, Warenkorb etc.) zu tun. Ein neuer Controller bietet sich dafür an. - Einen bestehenden Controller um eigene Aktionen erweitern
Wenn wir ein konfigurierbares Produkt mit vielen Varianten haben, können wir es unserem Kunden ersparen, die Seite zigfach aufzurufen, um verschiedene Varianten zu bestellen. Stattdessen erstellen wir ein Bestellformular mit den verfügbaren Varianten. Der Shop-Kunde trägt die Stückzahlen ein und freut sich, weil er viel Zeit spart. Hierfür kann man den bestehenden Cart-Controller erweitern, mittels dem Produkte in den Warenkorb gelegt werden. Damit die ursprüngliche Funktionalität unangetastet bleibt, erstellt man eine eigene Aktion in demselben Controller. - Bestehende Aktionen eines Controllers verändern
Im dritten Beispiel bestellt der Kunde in einem sehr belebten Webshop. Eine externe Warenwirtschaft kalkuliert den Lagerbestand, doch der kann sich so schnell ändern, dass ein Kunde ein nicht mehr vorhandenes Produkt kauft. Hier kann die bestehende Aktion so verändert werden, dass unmittelbar auf den Klick „In den Warenkorb legen“ bei der Warenwirtschaft nachgefragt wird, ob der Artikel noch verfügbar ist.
Sehen wir uns jetzt an, wie man das macht.
6.1 Einen eigenen Controller erstellen
Hier ist die Sache klar: wir erstellen eine eigene Extension und legen darin einen neuen Controller an. Nachdem wir das Grundgerüst für die Extension „Emzee_Events“ vorbereitet haben, erzeugen wir die Controller-Datei app/code/local/Emzee/Events/controllers/IndexController.php:
<?php class Emzee_Events_IndexController extends Mage_Core_Controller_Front_Action { public function indexAction() { // Hierher kommt der Code $this->loadLayout(); $this->renderLayout(); } }
Magento muss wissen, dass die Extension einen Controller bereitstellt und unter welcher URL der Controller aufgerufen wird. Dazu erweitern wir die Konfigurationsdatei app/code/local/Emzee/Events/etc/config.xml:
<?xml version="1.0" encoding="UTF-8" ?> <config> <modules> <emzee_events> <version>0.0.1</version> </emzee_events> </modules> <frontend> <routers> <emzee_events> <use>standard</use> <args> <module>Emzee_Events</module> <frontName>events</frontName> </args> </emzee_events> </routers> </frontend> </config>
Was machen wir hier? Wir definieren unsere eigene Route emzee_events (Tag <emzee_events> unter <routers>). Hier könnt ihr jeden beliebigen Namen vergeben, er sollte jedoch einzigartig sein. Wenn man den Namen der Extension verwendet, findet man ihn später leichter wieder. Dann teilen wir Magento mit, dass die Route im Standardbereich des Webshops gilt (<use>standard</use>
) und nicht im Admin-Bereich.
Schließlich geben wir an, dass die Route vom Modul Emzee_Events verwendet wird (hier muss die Schreibweise stimmen) und dass die Route in der URL über den String „events“ identifiziert wird.
View anpassen
Wenn wir nun die URL http://www.dein-webshop.tld/events/ aufrufen, sehen wir den Shop mit unserem Theme, natürlich noch ohne unsere veranstaltungsspezifischen Inhalte. Dass wir überhaupt etwas sehen, verdanken wir den beiden Methoden-Aufrufen loadLayout()
und renderLayout()
im Controller. Ohne sie wäre die Seite komplett weiß. Um zu testen, ob wir uns im Controller finden, können wir im Controller per „echo“ eine Ausgabe machen. Der Text sollte dann ganz oben angezeigt werden.
Nun wollen wir aber eigene View-Dateien einsetzen können. Dazu erstellen wir in unserem Theme (bei mir „emzee“) die Layout-XML-Datei app/design/frontend/default/emzee/layout/events.xml:
<?xml version="1.0" encoding="UTF-8" ?> <layout version="0.1.0"> <emzee_events_index_index> <reference name="content"> <block type="core/template" name="emzee_events_overview" template="events/overview.phtml" /> </reference> </emzee_events_index_index> </layout>
So legen wir fest, dass
- beim Aufruf der Route „emzee_events“ (Achtung: hier gilt der Name, den ihr in der config.xml unterhalb von <routers> für die Route angegeben habt!) und genauer gesagt
- beim Aufruf der Index-Aktion des Index-Controllers dieser Route
- dem Strukturblock „content“ ein Inhaltsblock hinzugefügt wird, wobei
- der Inhaltsblock
- vom Typ „core/template“ ist (normalerweise also die Block-Klasse Mage_Core_Block_Template zum Einsatz kommt),
- den Namen „emzee_events_overview“ hat und
- für die Darstellung die Datei events/overview.phtml im aktuellen Theme verwendet wird.
Zwei Schritte müssen wir hinter uns bringen, damit das funktioniert: 1. Magento davon informieren, dass die Layout-XML-Datei eingesetzt werden soll und 2. die Datei events/overview.phtml anlegen.
Ok, wir werden nun die Layout-XML-Datei einbinden. Dazu öffnen wir wieder die Konfigurationsdatei der Extension und ergänzen sie um folgenden Code:
<?xml version="1.0" encoding="UTF-8" ?> <config> <!-- ... --> <frontend> <layout> <updates> <emzee_events> <file>events.xml</file> </emzee_events> </updates> </layout> <!-- ... --> </frontend> </config>
Zuletzt legen wir noch die Template-Datei app/design/frontend/default/emzee/template/events/overview.phtml an (den Theme-Namen müsst ihr wieder anpassen):
<h1><?php echo $this->__('Events'); ?></h1>
Wenn alles geklappt hat, taucht die Überschrift „Events“ auf, wenn man den Webshop mit dem Pfad events/ aufruft.
Puh, so viel Arbeit für so wenig Ergebnis. Aber immerhin haben wir die Grundlage für unseren Veranstaltungsbereich geschaffen. Der Rest bleibt euch als Hausaufgabe. 😉
6.2 Einen bestehenden Controller um eigene Aktionen erweitern
Einen bestehenden Controller zu erweitern, bedeutet weniger Arbeit. Wir erweitern für unser Bestellformular den vorhandenen Cart-Controller und weisen Magento an, die neue Klasse anstatt der Standardklasse zu laden.
Dieser Code ist der Rumpf für unseren Cart-Controller (app/code/local/Emzee/Checkout/controllers/CartController.php):
<?php require_once Mage::getModuleDir('controllers', 'Mage_Checkout') . DS . 'CartController.php'; class Emzee_Checkout_CartController extends Mage_Checkout_CartController { public function addMultipleAction() { // Hierher kommt der Code } }
Alle Original-Methoden des Controllers bleiben dadurch unangetastet und updatefähig. Jetzt machen wir Magento unseren Controller noch über die Extension-Konfiguration bekannt (app/code/local/Emzee/Checkout/etc/config.xml):
<?xml version="1.0" encoding="UTF-8"?> <config> <modules> <Emzee_Checkout> <version>0.0.1</version> </Emzee_Checkout> </modules> <frontend> <routers> <checkout> <args> <modules> <emzee_checkout before="Mage_Checkout">Emzee_Checkout</emzee_checkout> </modules> </args> </checkout> </routers> </frontend> </config>
Magento weiß nun: in der Route „checkout“ muss ich im Modul Emzee_Checkout nachsehen, bevor ich die Klasse Mage_Checkout_CartController (oder einen anderen Mage_Checkout-Controller) lade. Mit einem „echo“ in der Controller-Action addMultipleAction() könnt ihr überprüfen, ob alles funktioniert hat, bevor ihr euch an die weitere Implementierung macht.
6.3 Bestehende Aktionen eines Controllers verändern
Wenn man eine vorhandene Aktion verändern müss, ist man schnell versucht, wie in Tipp 6.2 beschrieben eine eigene Klasse vom existierenden Controller abzuleiten und die Methode zu überschreiben. Bevor man diese Option zieht, sollte man jedoch überlegen, ob sich dieser Schritt nicht vermeiden lässt.
Frage 1: kann ich ein bestehendes Event verwenden, um die Action im eigenen Sinne zu beeinflussen?
Magento löst immer vor und nach einer Controller-Action Events aus, die man nutzen kann, um sich in den Ablauf einzuhängen. Beim Hinzufügen eines Produkts in den Warenkorb gibt es unter anderem folgende Events:
- controller_action_predispatch_checkout
- controller_action_predispatch_checkout_cart_add
Sie werden vor der Ausführung der Action aktiv. Da das erste Event bei jedem Seitenaufruf im Checkout-Modul ausgelöst wird, sollte man in unserem Beispiel-Szenario auf das zweite Event zurückgreifen, um mit einem Event-Observer die Anfrage an die Warenwirtschaft zu starten.
Wenn man sich nicht sicher ist, welche Events ausgelöst werden, kann man kurzfristig(!) die Methode Mage::dispatchEvent() verändern, um die ausgelösten Events mitzuloggen. Ihr werdet vielleicht erstaunt sein, was da so alles auftaucht.
Falls der Weg über die Events nicht möglich ist, stelle ich mir folgende Frage:
Frage 2: handelt es sich wirklich um eine Änderung in der Controller-Schicht?
Natürlich sollte man sich das auch schon vorher überlegen, doch nun ist ein guter Zeitpunkt, um noch einmal zu grübeln. Möchte ich wirklich etwas an der Aufgabe des Controllers verändern oder handelt es sich nicht doch um eine Änderung in Model bzw. View? Wenn ja, dann gehört die Änderung dorthin, und vielleicht kann ich dort wiederum mit Events arbeiten.
Angenommen, es hilft alles nichts und ich muss tatsächlich ohne Events im Controller etwas anpassen, dann kommt die nächste Kontrollfrage.
Frage 3: kann ich nicht doch einen eigenen Controller bzw. eine eigene Action verwenden?
Bei genauerer Betrachtung ist es oft doch nicht unbedingt nötig, eine bestehende Action zu überschreiben. Stattdessen tut es eine eigene Action (oder, wenn es der Übersicht dient) ein eigener Controller, in dem das Verhalten abgebildet wird.
Frage 4: darf ich jetzt endlich die Action-Methode überschreiben?
Wenn alle Fragen abschlägig beantwortet werden mussten, dann bleibt uns wohl wirklich nichts übrig, als die Methode zu überschreiben. Dazu erstellt man wie in Tipp 6.2 beschrieben den neuen Controller, überschreibt aber die vorhandene Methode. Wenn man die Magento-Version aktualisiert, sollte man dann überprüfen, ob man Änderungen / Bugfixes in den eigenen Code übernehmen muss.
Fazit
Teil 3 der Entwicklungs-Tipps für Magento hat sich mit der Frage beschäftigt, wie man eigene Controller erstellt, bestehende Controller erweitert und möglicherweise doch darum herumkommt, eine bestehende Controller-Action zu überschreiben. Eine der drei MVC-Schichten haben wir damit bereits abgehandelt, bei Fragen bitte wie üblich einen Kommentar hinterlassen. 😉
Und noch einemal dicke entschuldigung,
da ich in den anderen beiden Beiträgen feststellte das ich kein XML hier mitsenden kann nun meine direkte Frage.
Kann ich in der config.xml in freontend, routers mehrere Controller registrieren? Ich habe für mein Modul nämlich meinen Stadard Controller und will dann da drin noch konfigurieren den OnepageController vom Checkout zu überschreiben.
Geht das? Kann man das machen? Bei mir funktioniert das jedenfalls nicht.
Danke für eine hilfreiche Antwort und noch mals bitte ich um Verzeihung für die Mühe die ich mit den letzten beiden versauten Beiträgen gemacht habe.
Gruß Daniel
Hallo Daniel,
dass man den XML-Code nicht richtig verwenden kann, liegt an WordPress / einem Plug-In – irgendwann kümmere ich mich darum, aber zum Glück wird eher selten Code gepostet. 😉
Man kann in einer Extension komplett eigene Controller definieren (wie in 6.1) und vorhandene Controller überschreiben (wie in 6.2 bzw. 6.3).
Um den Checkout-Controller zu erweitern und überschreiben, musst du wie in 6.2 beschrieben im XML die Route „routers > checkout“ angeben und Magento mitteilen, dass dein Controller vor dem Magento-Controller verwendet werden soll (siehe „before“).
Du hast stattdessen mit deinem Code einen eigenständigen Controller registriert.
Kommt in deiner Extension beides vor, machst du unter der XML-Node „routers“ einfach mehrere Einträge. Der eine kann „checkout“ sein, in dem du den Checkout-Controller überschreibst und der zweite kann eine eigene Route mit Controller von dir sein.
Daniel und ich haben uns per Mail weiter mit dem Problem beschäftigt. Daraus entstand dieses Posting mit Tipps zum Debuggen von Controllern:
Hilfe, mein Magento-Controller wird nicht geladen – warum?
Hallo Mathias,
bitte um Entschuldigung für den zweiten Beitrag, der erste nahm das xml nicht an und ich hatte übersehen das ich auch code tags nutzen kann.
Also bei mir will das mit dem Controller überschreiben nicht funktionieren.
Hier mal mein Auszug der config.xml
standard
Mycompany_Coupon
coupon
Mycompany_Coupon_Checkout
Und dazu mal die php die in Mycompany/Coupon/controllers/Checkout liegt.
require_once Mage::getModuleDir(‘controllers’, ‘Mage_Checkout’) . DS . ‘OnepageController.php’;
class Mycompany_Coupon_Checkout_OnepageController extends Mage_Checkout_OnepageController
{
public function indexAction()
{
$helper = $this->_saleData();
echo “”; print_r($helper); echo “”; exit;
}
protected function _saleData()
{
$helper = Mage::helper(‘mycompany_coupon/saledata’);
}
}
Wenn ich nun den OnepageController aufrufe dann bleibt trotzdem der originale erhalten.
Konfiguriere ich das flasch oder wo liegt der Fehler?
Vielen Dank für eien aufklärende Antwort. Mfg Daniel
Hallo Mathias,
also bei mir will das mit dem Controller überschreiben nicht funktionieren.
Hier mal mein Auszug der config.xml
standard
Mycompany_Coupon
coupon
Mycompany_Coupon_Checkout
Und dazu mal die php die in Mycompany/Coupon/controllers/Checkout liegt.
require_once Mage::getModuleDir(‚controllers‘, ‚Mage_Checkout‘) . DS . ‚OnepageController.php‘;
class Mycompany_Coupon_Checkout_OnepageController extends Mage_Checkout_OnepageController
{
public function indexAction()
{
$helper = $this->_saleData();
echo „“; print_r($helper); echo „“; exit;
}
protected function _saleData()
{
$helper = Mage::helper(‚mycompany_coupon/saledata‘);
}
}
Wenn ich nun den OnepageController aufrufe dann bleibt trotzdem der originale erhalten.
Konfiguriere ich das flasch oder wo liegt der Fehler?
Vielen Dank für eien aufklärende Antwort. Mfg Daniel
Hi Matthias,
zunächst einmal vielen Dank für den tollen Blog:)
Ich habe versucht den Abschnitt 6.1. Schritt für Schritt zu machen. Am Ende bekomme ich aber „Page not Found“. Ich muss erstmal sagen, dass ich in diesem Bereich ganz Neuling bin. Ich fasse kurz zusammen, welche Schritte ich durchgeführt habe. Villeicht findest du meinen Fehler (Kann auch was einfaches sein:) )
1. Die Konfigurationsdatei der Extension abgelegt in: app/code/local/Emzee/Checkout/etc/config.xml
2. Die Konfigurationsdatei zur Aktivierung der Extension abgelegt in: app/etc/modules/Emzee_Checkout.xml
3. Controller-Datei erzeugt in: app/code/local/Emzee/Events/controllers/IndexController.php
4. Konfigurationsdatei erweitert in: app/code/local/Emzee/Events/etc/config.xml
5. Layout-XML-Datei erstellt in: app/design/frontend/default/emzee/layout/events.xml
6. Konfigurationsdatei der Extension (Punkt 4.) ergänzt.
7. Template-Datei gelegt in: app/design/frontend/default/emzee/template/events/overview.phtml
8. Wie ruft man hier die Seite richtig auf? habe localhost.
Vielen Dank im Voraus.
Hi,
bei deinen Schritten ist „Checkout“ und „Events“ vermischt. Deswegen kann ich nicht sagen, unter welcher URL du die Seite erreichen kannst. Prinzipiell ist dein Controller unter dem Pfad erreichbar, den du in der config.xml unter „frontName“ definiert hast. Steht dort „events“, dann müsste dein Index-Controller unter http://www.deinshop.tld/events/ erreichbar sein.
Ein Leser hat gestern einen tollen Link gepostet: http://silksoftware.com/magento-module-creator/
Wenn du „Need Frontend Page“ auf „Yes“ stellst und die „Demo Block Position“ auf „None“ gestellt lässt, erhältst du den Code für deinen eigenen Controller.
Hallo,
ich habe dein Tutorial nachgebaut (übrigens echt gut), jedoch wird bei mir in der html das Event nicht angezeigt. Die Index wird richtig aufgerufen, da wenn ich ein echo eingebe dies über der Seite ausgegeben wird.
Muss ich bei der xml irgendwas beachten? In dem Controllertutorial sind deine Tags kleingeschrieben, während sie im „Extension erstellen“ großgeschrieben sind.
Gruß, Patrick
Hallo Patrick,
das funktioniert auch in Magento 1.7 noch.
Groß-/Kleinschreibung Tags: unter config > modules sollten die Tags groß geschrieben sein, das ist hier falsch gemacht. Davon unabhängig kannst du die Tags unter config > frontend > layout > updates usw. klein schreiben. Die Bezeichnung muss hier nur einzigartig sein.
Da dein der Controller verwendet und die Index-Action aufgerufen wird (du hast das „echo“ im Index-Controller deiner Extension ausgeführt, richtig?), ist das aber nicht das Problem.
Scheinbar wird deine Layout-XML-Datei nicht interpretiert oder deine .phtml-Datei nicht gefunden. Probiere, beide Dateien in die jeweiligen Verzeichnisse des base/default-Themes zu verschieben. Klappt es hier, dann kannst du die Dateien wieder in das von dir gewünschte Theme verschieben und musst dafür sorgen, dass deine Store-View eben dieses Theme verwendet.
Hey Matthias,
erstmal ein riesen Dank für deinen klasse Blog, hat mir schon sehr weitergeholfen. 🙂
Ich wollte den Customer AccountController überschreiben und da kam mir dieser Artikel genau recht, allerdings hat es nicht funktioniert. Es kam mir auch irgendwie komisch vor, weil ich nicht nachvollziehen konnte, woher Magento weiß, dass es meinen modifizierten ActionController verwenden soll. Ich konnte mir nicht vorstellen, dass Magento automatisch indem Ordner app/code/local/Emzee/Checkout/etc/ die config.xml lädt.
Nachdem ich ein wenig recherchiert habe, bin ich darauf gestoßen, dass ich noch unter app/etc/modules eine .xml Datei anlegen muss, damit Magento mein Controller lädt.
true
local
Kann das sein, dass das in deinem Tipp hier fehlt oder habe ich irgendwas übersehen (in diesem Teil 3 oder in Teil 1 / 2)?
Schöne Grüße
Christian
Hey Christian, das freut mich!
Der Hinweis war gut versteckt in der Bemerkung „wir erstellen eine eigene Extension“. 😉
Wie du richtig schreibst, muss man diese XML-Aktivierungs-Datei anlegen, damit eine Extension verwendet werden kann. Ich habe den Text jetzt mit dem Artikel „Magento: eigene Extension erstellen“ verlinkt, damit man sich leichter zurecht findet.