Anzeige
Anzeige
UX & Design

Magento-Erweiterungen im Eigenbau: Module für das Open-Source-Shop-System programmieren

Magento wird gerne für seine Erweiterbarkeit und Flexibilität gelobt. Doch mit der Flexibilität geht auch Komplexität einher. Vielen Entwicklern fällt daher trotz reichlich PHP-Erfahrung der Einstieg schwer. Informationen müssen aus Foren, Blogs und Wikis zusammengesucht werden, komplette Dokumentationen sind immer noch selten. Dieser Workshop zeigt, wie die ersten Schritte gelingen und eine erste eigene Extension entsteht.

7 Min.
Artikel merken
Anzeige
Anzeige

Bestellungen nach Produkt-Lieferanten aufteilen und sie an diese weiterleiten – eine Funktionalität, die in vielen Magento-Projekten benötigt wird. Dieser Workshop nimmt sich des Themas an und zeigt exemplarisch, wie sich solch eine Funktion als Extension abbilden lässt. Folgende Schritte werden dabei behandelt:

  • Erweiterung anlegen
  • Modul-Installations-Skripte erstellen
  • Modelle aus dem Magento-Core erweitern
  • Event-Observer benutzen
Anzeige
Anzeige

Der Begriff „Modul“ wird im Artikel synonym zu „Erweiterung“ genutzt, auch wenn genau genommen nicht jede Erweiterung ein eigenes Modul ist.

Wie fange ich an?

Zuerst müssen Sie drei Entscheidungen treffen:

Anzeige
Anzeige
  1. Soll die Erweiterung nur für das aktuelle Projekt dienen oder wollen Sie sie veröffentlichen? Die Antwort entscheidet über das Verzeichnis, in dem die Erweiterung angelegt wird. Lokale Module (wie in diesem Artikel) liegen in „app/code/local“, Module, die Sie veröffentlichen wollen, in „app/code/community“.
  2. Welchen Namensraum („Namespace“) wollen Sie verwenden? Der Namespace dient der Vermeidung von Konflikten zwischen Modulen und ist nichts weiter als der Name des Verzeichnisses, in dem Sie Ihre Module sammeln. Der Name sollte nicht zu lang sein, da er häufig getippt werden muss. Außerdem muss der erste Buchstabe groß geschrieben sein. Überhaupt lohnt es sich bei Magento-Modulen immer peinlich genau auf eine konsistente Groß-/Kleinschreibung zu achten. Für die Beispielanwendung benutzen Sie den Namespace „T3nBsp“.
  3. Wie soll das Modul heißen? Am besten sind kurze Namen, die etwas über das Modul aussagen. Auch hier muss der erste Buchstabe groß geschrieben werden. Für das Beispiel wurde „OrderSplitter“ gewählt.

Wie geht es weiter?

Ordnerstruktur des Beispielmoduls.

Ordnerstruktur des Beispielmoduls.

Aus den Antworten auf die drei Fragen ergeben sich die nächsten Schritte. Zuerst erstellen Sie das Verzeichnis „app/code/local/T3nBsp/OrderSplitter“. Noch erkennt Magento das Modul nicht – es muss erst angemeldet werden. Registrieren Sie dazu Ihre Erweiterung mit der Datei „app/etc/modules/T3nBsp_OrderSplitter.xml“:

Anzeige
Anzeige

Die Modulregistrierung unter app/etc/modules/

<?xml version="1.0"?>
<config>
	<modules>
		<T3nBsp_OrderSplitter>
			<active>true</active>
			<codePool>local</codePool>
		</T3nBsp_OrderSplitter>
	</modules>
</config>

Listing 1

Sollte ein Modul in „app/code/community“ liegen, gehört in den Knoten „<codePool>“ der Wert „community“ statt „local“. Ob alles stimmt, können Sie überprüfen, indem Sie im Magento-Backend die Seite „System -> Konfiguration -> Erweitert -> Erweitert -> Modulausgaben deaktivieren“ aufrufen.

Magento erkennt ein Modul, sobald die XML-Datei in „app/etc/modules/“ liegt.

Magento erkennt ein Modul, sobald die XML-Datei in „app/etc/modules/“ liegt.

Sollte das Modul dort nicht gelistet sein, ist vermutlich der System-Cache noch an (System -> Cache Verwaltung). Beim Entwickeln sollten Sie den Cache am besten komplett deaktivieren.

Anzeige
Anzeige

Das Herz des Moduls

Nachdem Ihr Modul nun existiert, müssen Sie es mit Leben füllen. Alle weiteren Schritte nehmen ihren Anfang in der Datei „etc/config.xml“ des Moduls (der komplette Pfad ist app/code/local/T3nBsp/OrderSplitter/etc/config.xml).

Skelett für die config.xml

<?xml version="1.0"?>
<config>
	<modules>
		<T3nBsp_OrderSplitter>
			<version>0.1.0</version>
		</T3nBsp_OrderSplitter>
	</modules>
	<global>
		<models>
			<OrderSplitter>
				<class>T3nBsp_OrderSplitter_Model</class>
			</OrderSplitter>
		</models>
	</global>
</config>

Listing 2

Attribute per Skript anlegen

Jedem Produkt soll eine Distributor-E-Mail-Adresse zugeordnet werden können. Damit das Modul wiederverwendbar ist, soll Magento das Produkt-Attribut automatisch bei der Installation der Erweiterung anlegen. Magento bietet dafür Setup-Skripte. Welche Setup- Skripte ausgeführt werden, bestimmen Einträge in der Datei „config.xml“. Zunächst brauchen Sie eine <resource>-Sektion in Ihrer Konfiguration, direkt unter dem schließenden </models>-Tag:

Resourcen für das Modul definieren

<resources>
	<OrderSplitter_setup>
		<setup>
			<module>T3nBsp_OrderSplitter</module>
			<class>Mage_Sales_Model_Mysql4_Setup</class>
		</setup>
		<connection><use>default_setup</use></connection>
	</OrderSplitter_setup>
	<OrderSplitter_write>
		<connection><use>default_write</use></connection>
	</OrderSplitter_write>
	<OrderSplitter_read>
		<connection><use>default_read</use></connection>
	</OrderSplitter_read>
</resources>

Listing 3

Der Name der Setup-Resource (<OrderSplitter_setup>) und der Wert des <version>-Knotens (0.1.0) bestimmen den Pfad und den Dateinamen des Setup-Skripts. Für Ihr Modul ist das „sql/OrderSplitter_setup/mysql4-install-0.1.0.php“. Der Kontext des Skripts, also die Klasse, die das Setup-Skript ausführt, steht im <class>-Knoten, hier also „Mage_Sales_Model_Mysql4_Setup“ (der Zend-Autoloader übersetzt den Klassennamen in einen Dateipfad und lädt dann automatisch „app/code/core/Mage/Sales/Model/Mysql4/Setup.php“). Hier der Inhalt Ihres Installations-Skripts:

Anzeige
Anzeige

Ein Attribut per Installations-Skript anlegen

<?php

$this->startSetup();

$this->addAttribute(
	'catalog_product',
	'distributor_email',
	array(
		'group'  => 'General',
		'type'   => 'varchar',
		'label'  => 'Distributor Email Adresse',
		'input'  => 'text',
		'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL,
		'required' => 1,
		'user_defined' => 1,
		'is_configurable' => 0,
	)
);

$this->endSetup();

Listing 4

Weitere mögliche Parameter für die Methode „addAttribute()“ können Sie in „app/code/core/Mage/Eav/Model/Entity/Setup.php“ nachlesen. Setup-Skripte führt Magento beim ersten Reload aus.

Wenn alles fehlerfrei funktioniert, steht das neue Textfeld für das Attribut unter „Katalog -> Produkte Verwalten -> Produkt hinzufügen“ zur Verfügung.

Wenn alles fehlerfrei funktioniert, steht das neue Textfeld für das Attribut unter „Katalog -> Produkte Verwalten -> Produkt hinzufügen“ zur Verfügung.

Tipp:
Magento speichert die aktuell installierte Version eines Moduls in der
Tabelle „core_resource“. Wenn ein Setup-Skript nicht ausgeführt wird, lohnt ein Blick in die Datenbank, um den Datensatz Ihres Moduls
zu löschen (Achtung: nur den Datensatz von Ihrem Modul!) oder die
Versionsnummer manuell herunter zu setzen. So kann man das erneute
Ausführen eines Setup-Skripts erzwingen.

Jetzt brauchen Sie ein paar Produkte mit unterschiedlichen Distributor-E-Mail-Adressen, um testen zu können.

Mage Core Klassen überschreiben

Anschließend kommt das eigentliche Aufteilen der Bestellungen nach Distributoren an die Reihe. Das Schwierigste dabie ist, den richtigen Ansatzpunkt zu finden. Da hilft leider nur Suchen und Code verfolgen, bis nach einiger Zeit genug Erfahrung vorhanden ist, um gezielt ansetzen zu können. In diesem Beispiel benutzen Sie die Klasse „Mage_Checkout_Model_Type_Onepage“. Sie befindet sich in der Datei „app/code/core/Mage/Checkout/Model/Type/Onepage.php“ im Modul „Mage_Checkout“.

Anzeige
Anzeige

Durch einen Eintrag in die „config.xml“ teilen Sie Magento mit, dass immer, wenn ein Objekt dieser Klasse gebraucht wird, stattdessen ein Objekt Ihrer erweiterten Klasse erzeugt werden soll.
Dafür ergänzen Sie die <models>-Sektion aus Ihrer config.xml:

Die komplette <models> Sektion

<models>
	<OrderSplitter>
		<class>T3nBsp_OrderSplitter_Model</class>
	</OrderSplitter>
	<checkout>
		<rewrite>
			<type_onepage>T3nBsp_OrderSplitter_Model_Checkout_Type_Onepage</type_onepage>
		</rewrite>
	</checkout>
</models>

Listing 5

Die Klasse, die Sie erweitern wollen, ist ein Modell im Modul „Mage_Checkout“. Welchen Tag-Namen Sie nehmen müssen, können Sie in der <models>-Sektion der „etc/config.xml“ des jeweiligen Moduls nachschlagen. Für das Mage_Checkout-Modul benutzen Sie also ein <checkout>-Tag. Auf dieselbe Weise können Sie auch Block- und Helfer-Klassen erweitern; in dem Fall würde das <checkout> in die <blocks>- oder die <helpers>-Sektion gehören.

In die Sektion, die das Modul bestimmt (hier <checkout>), kommt ein <rewrite>, dann der zu ersetzende Klassenname (<type_onepage>) und anschließend die neue Klasse (T3nBsp_OrderSplitter_Model_Checkout_Type_Onepage), die statt dessen benutzt werden soll. Die zu ersetzende Klasse wird ohne Namespace und Modulnamen angeben (also „type_onepage“). Das Ganze ist Anfangs etwas gewöhnungsbedürftig, wird aber mit etwas Übung schnell leichter.

Anzeige
Anzeige

Für die erweiterte Onepage-Checkout-Klasse legen Sie die Datei „Model/Checkout/Type/Onepage.php“ in Ihrem Modul an:

Eigene Klasse erweitert die Magento Core Klasse

<?php
class T3nBsp_OrderSplitter_Model_Checkout_Type_Onepage
	extends Mage_Checkout_Model_Type_Onepage {
}

Listing 6

Das Schwerste ist jetzt geschafft. Denn der aufwändigste Teil ist meist, die richtige Stelle zu finden, an der man ansetzt, um die gewünschte Funktionalität zu integrieren.

Jetzt weiter im Modul: Die Bestellung wird in der Methode „saveOrder()“ erzeugt. Der Plan sieht wie folgt aus: Vor dem Aufruf der eigentlichen Methode werden zuerst alle Produkte im Warenkorb (dem „Quote“-Objekt bei Magento) nach Distributoren sortiert. Dann werden für jeden Distributor die passenden Produkte im Warenkorb gesammelt, alle nicht passenden entfernt und eine Bestellung abgeschickt.

Anzeige
Anzeige

Das Aufteilen der Bestellung

public function saveOrder() {
	// Array zum Sammeln der Produkte sortiert nach Distributor
	$sorted = array();

	// Dieses Collection Objekt ist eine Sammlung aller Produkte im Warenkorb
	$quoteItems = $this->getQuote()->getItemsCollection();

	// Aufteilen der Produkte
	foreach ($quoteItems as $item) {
		// Distributor Email holen (das load() ist nötig damit alle Produkt-Attribute geladen sind)
		$distributorEmail = $item->getProduct()
			->load($item->getProduct()->getId())
			->getDistributorEmail();

		$sorted[$distributorEmail][] = $item;
	}

	// Für jeden Distributor eine Bestellung erzeugen
	foreach ($sorted as $distributorEmail => $items) {
		// Warenkorb leeren
		foreach ($quoteItems as $item) {
			$quoteItems->removeItemByKey($item->getId());
		}

		// Alle Produkte des aktuellen Distributors in den Warenkorb legen
		foreach ($items as $item) {
			$quoteItems->addItem($item);
		}

		// Zwischensumme, Steuern usw für aktuellen Inhalt des Korbes berechnen
		$this->getQuote()->collectTotals();

		// Aktuelle Distributor Email zur späteren Verwendung speichern
		$this->getQuote()->setDistributorEmail($distributorEmail);

		// Bestellung anlegen
		parent::saveOrder();
	}
	return $this;
}

Listing 7

Fast am Ziel

Jetzt fehlt nur noch das Verschicken der E-Mails an die Distributoren. Dabei hilft ein Event-Observer. Events sind in Magento Hooks, an denen eigener Code ausgeführt werden kann. Diese sollten Sie, wenn möglich, dem Erweitern von Core-Klassen vorziehen. Leider stehen sie nicht immer zur Verfügung, wenn sie gebraucht werden. Fangen Sie wieder in der Datei „config.xml“ an.

Direkt nach </global> einfügen:

<frontend>
	<events>
		<checkout_type_onepage_save_order_after>
			<observers>
				<OrderSplitter>
					<type>singleton</type>
					<class>OrderSplitter/observer</class>
					<method>checkoutTypeOnepageSaveOrderAfter</method>
				</OrderSplitter>
			</observers>
		</checkout_type_onepage_save_order_after>
	</events>
</frontend>

Listing 8

Eine Excel-Datei mit einer (fast vollständigen) Liste aller Events finden Sie im Magento-Wiki [1]. Um Ihr Modul zu vollenden, braucht es noch die Observer-Klasse:

Die Datei „Model/Observer.php“ im Modulverzeichnis

<?php
class T3nBsp_OrderSplitter_Model_Observer {
	public function checkoutTypeOnepageSaveOrderAfter($observer) {
		// Übergebenes Quote und Order Objekt holen
		$order = $observer->getEvent()->getOrder();
		$quote = $observer->getEvent()->getQuote();

		// Nur schicken wenn tatsächlich eine Adresse zugewiesen wurde
		if ($quote->getDistributorEmail()) {
			// Lieferadresse als Text formatiert holen
			$shipTo = $order->getShippingAddress()->format('text');
			$itemsTxt = "Items\n-----------------------\n";
			foreach ($order->getAllVisibleItems() as $item) {
				$itemsTxt .= 'SKU: ' . $item->getSku() . ' QTY: ' . $item->getQtyOrdered() . "\n";
			}
			$mail = Mage::getModel('core/email')
				->setFromEmail('shop@example.com')
				->setSubject('Neue Bestellung')
				->setToEmail($quote->getDistributorEmail())
				->setBody("Lieferadresse:\n\n" . $shipTo . "\n" . $itemsTxt)
				->send();
		}
	}
}

Listing 9

Damit ist Ihr Modul komplett. Ihm fehlt zwar noch einiges an Sonderfall- und Fehlerbehandlung, kann aber schon jetzt als Grundlage für Ihre ersten eigenen Erweiterungen dienen. Die vorgestellten Techniken bilden die Basis, auf der Sie bei der Magento-Modulprogrammierung aufbauen können.

Mehr zu diesem Thema
Fast fertig!

Bitte klicke auf den Link in der Bestätigungsmail, um deine Anmeldung abzuschließen.

Du willst noch weitere Infos zum Newsletter? Jetzt mehr erfahren

Anzeige
Anzeige
Kommentare (14)

Community-Richtlinien

Christian Conrad

Super Beitrag!!!

Gerade die Sache mit dem Order Split hat mir eine Herausforderung weniger beschert!

Vielen Dank und weiter so.

Christian

Michael Heyking

Hallo Vinai,

kann mich nur anschließen – super Beitrag.
Hat bei 1.3.x super funktioniert.
Kann es sein das bei 1.4.1.1 es nicht mehr funktioniert ?
Es kommt nur noch ein popup „undefined“.

Viele Grüße,
Michael

Michael Heyking

Hallo Vinai,

Problem ist gelöst. Deine Anleitung funktioniert nach wie vor perfekt.
Ich habe den Rechnungsprozess und Lieferprozess übersprungen.
Das verträgt sich nicht ohne weiteres mit dem Modul.

Vielleicht hat jemand einen Tip, was in diesem Fall zu tun ist ?

Viele Grüße,
Michael

Mario

hallo,
ich versuche mich an dem Tutorial mit Magento 1.5 (frisch installiert, einzig Market Ready Germany installiert)
Leider komme ich nur bis Seite 3/5: Das Feld: ‚Distributor Email‘ taucht einfach nicht auf..
Davor hat alles noch geklappt und ich habe eigentlich alles genauso gemacht wie beschrieben..
woran kann das liegen? Inkompatible Version?

Vinai

Hallo Mario,

seit Magento 1.4 muss man bei addAttribute() auch das Attribut Set und die Gruppe angeben wenn user_defined auf 1 gesetzt ist, ansonsten musst du das Attribut von Hand im Backend dem Set und einer Gruppe zuweisen.
Am einfachsten ist es allerdings im Setup Script user_defined auf 0 zu setzen, dann ist das ehemalige Verhalten wieder hergestellt und das Attribut wird automatisch allen Attribut Sets in der General Gruppe hinzugefügt.

Vinai

Dieter

Hallo,

ich nutze Version 1.4.2 und bekomme die Extension nicht zum laufen.
Wenn ich alles erstellt habe, kommt beim Aufruf des Checkout folgende Fehler Meldung:

Fatal error: Call to a member function getQuote() on a non-object in Pfad zu Magento\app\code\core\Mage\Checkout\controllers\OnepageController.php on line 151

Kann mir jemand weiter helfen? Oder die fertige Extension hochladen?

Danke

Dieter

Vinai

Hallo Dieter,

ich denke Du hast einen Fehler in der Klasse T3nBsp_OrderSplitter_Model_Checkout_Type_Onepage. Entweder einen PHP Syntax Fehler (PHP Error log checken?), oder vielleicht extended deine Klasse nicht Mage_Checkout_Model_Type_Onepage.

Viele Grüße,
Vinai

Dieter

Hallo Vinai,

danke dir. Ein kompletter Neustart und es läuft. Wirklich schöne Extension.

Gruß
Dieter

Dieter

Hallo,

kann es sein, dass die Berechnung der Zwischensumme, Steuern usw für aktuellen Inhalt des Korbes nicht funktioniert?
Also: $this->getQuote()->collectTotals();

Gruß
Dieter

Vinai

Hallo Dieter,

in der aktuellen Version sollte das so aussehen:
$this->getQuote()->setTotalsCollectedFlag(false)->collectTotals();

Dieter

Hallo Vinai,

ich nutze die Version 1.4.2, deine Lösung ist wohl für 1.5, denn leider funktioniert es nicht.
Scheinbar wird die Funktion nicht aufgerufen, ich habe in app/code/core/Mage/Sales/Model/Quote.php mal ein Mage::log Eintrag eingefügt und dieser wird nicht nach dem zusammenstellen der einzelnen Warenkörbe aufgerufen.
Hast du eine Idee?
Danke für deine Hilfe.

Gruß
Dieter

Vinai

Hall Dieter,

das $this->getQuote()->setTotalsCollectedFlag(false)->collectTotals(); passt auch für Magento in der Version 1.4.2 (vergleiche Zeile 946 in der Datei app/code/core/Mage/Sales/Model/Quote.php).
Es geht in Deinem Code um die Zeile zum Berechnen der Zwischensumme, Steuern usw für den aktuellen Inhalt des Korbes.

Viele Grüße,
Vinai

Dieter

Habe es leider bisher nicht zum laufen gebracht.
Ich werde demnächst mal eine neue Installation aufsetzen und schauen ob ich es dort hin bekomme
Danke für deine Hilfe Vinai.

Mrs_Byte

Hallo,

funktioniert diese Lösung auch für 1.6.1.1 und höher?

Danke

Bitte schalte deinen Adblocker für t3n.de aus!
Hallo und herzlich willkommen bei t3n!

Bitte schalte deinen Adblocker für t3n.de aus, um diesen Artikel zu lesen.

Wir sind ein unabhängiger Publisher mit einem Team von mehr als 75 fantastischen Menschen, aber ohne riesigen Konzern im Rücken. Banner und ähnliche Werbemittel sind für unsere Finanzierung sehr wichtig.

Schon jetzt und im Namen der gesamten t3n-Crew: vielen Dank für deine Unterstützung! 🙌

Deine t3n-Crew

Anleitung zur Deaktivierung
Artikel merken

Bitte melde dich an, um diesen Artikel in deiner persönlichen Merkliste auf t3n zu speichern.

Jetzt registrieren und merken

Du hast schon einen t3n-Account? Hier anmelden

oder
Auf Mastodon teilen

Gib die URL deiner Mastodon-Instanz ein, um den Artikel zu teilen.

Community-Richtlinien

Wir freuen uns über kontroverse Diskussionen, die gerne auch mal hitzig geführt werden dürfen. Beleidigende, grob anstößige, rassistische und strafrechtlich relevante Äußerungen und Beiträge tolerieren wir nicht. Bitte achte darauf, dass du keine Texte veröffentlichst, für die du keine ausdrückliche Erlaubnis des Urhebers hast. Ebenfalls nicht erlaubt ist der Missbrauch der Webangebote unter t3n.de als Werbeplattform. Die Nennung von Produktnamen, Herstellern, Dienstleistern und Websites ist nur dann zulässig, wenn damit nicht vorrangig der Zweck der Werbung verfolgt wird. Wir behalten uns vor, Beiträge, die diese Regeln verletzen, zu löschen und Accounts zeitweilig oder auf Dauer zu sperren.

Trotz all dieser notwendigen Regeln: Diskutiere kontrovers, sage anderen deine Meinung, trage mit weiterführenden Informationen zum Wissensaustausch bei, aber bleibe dabei fair und respektiere die Meinung anderer. Wir wünschen Dir viel Spaß mit den Webangeboten von t3n und freuen uns auf spannende Beiträge.

Dein t3n-Team

Kommentar abgeben

Melde dich an, um Kommentare schreiben und mit anderen Leser:innen und unseren Autor:innen diskutieren zu können.

Anmelden und kommentieren

Du hast noch keinen t3n-Account? Hier registrieren

Anzeige
Anzeige