EcomDev_PHPUnit Tipp #2

Seit Jahren ist das Test-Framework EcomDev_PHPUnit quasi-Standard für Magento Unit Tests. Die aktuelle Version ist 0.3.7 und der letzte Stand der offiziellen Dokumentation ist Version 0.2.0 – seitdem hat sich viel getan, was man leider im Code und GitHub Issues selbst zusammensuchen muss. Diese Serie soll praktische Tipps zur Verwendung sammeln.

Tipp #2: Expectation Keys

Das ist ein Kurzer: expected('works %s sprintf', 'like').

Welche Expectations geladen werden sollen, hängt üblicherweise von den Testdaten ab, wenn Deine Expectation-Datei also so aussieht:

- product-1-qty-10:
  - answer: 42
- product-2-qty-10:
  - answer: 42

kannst Du sie wie folgt im Test laden:

/**
 * @test
 * @loadExpectation
 * @loadFixture
 * @dataProvider dataProvider
 */
public function testSomething($productId, $qty)
{
  $expectedAnswer = $this->expected('product-%s-qty-%s', $productId, $qty);
}

EcomDev_PHPUnit Tipp #1

Seit Jahren ist das Test-Framework EcomDev_PHPUnit quasi-Standard für Magento Unit Tests. Die aktuelle Version ist 0.3.7 und der letzte Stand der offiziellen Dokumentation ist Version 0.2.0 – seitdem hat sich viel getan, was man leider im Code und GitHub Issues selbst zusammensuchen muss. Diese Serie soll praktische Tipps zur Verwendung sammeln.

Tipp #1: Globalen Zustand zurücksetzen

Ein Umstand, der das Testen mit Magento erschwert, ist die freizügige Anwendung von globalen Zuständen, in Form von Singletons und Registry. Diese werden auch über Tests hinweg nicht zurückgesetzt, EcomDev_PHPUnit ermöglicht aber das explizite Zurücksetzen mit Annotations.

/**
 * @singleton checkout/session
 * @helper tax
 * @registry current_product
 */
public function testSomething()

Die Parameter sind jeweils die selben wie für Mage::getSingleton(), Mage::helper() und Mage::registry().

Es ist zu empfehlen, alle Singletons und Registry-Werte, die im Test genutzt werden zurückzusetzen, nicht erst, wenn es zu Konflikten kommt. Insbesondere für Session-Singletons ist es wichtig, übrigens unabhängig davon ob sie im aktuellen Test gemockt werden oder nicht. Bei zustandslosen Helpern, also solchen ohne eigene Attribute, ist ein Zurücksetzen allerdings nicht notwendig,

CSV-Verarbeitung in Magento

Ein Grundsatz bei der Entwicklung, nicht nur mit Magento, ist dass man nicht versuchen sollte, das Rad neu zu erfinden und insbesondere auf die Funktionen des verwendeten Frameworks zurückzugreifen, soweit möglich. Magento hat viele mehr oder weniger bekannte universelle Helfer, in den Helper-Klassen aus Mage_Core sowie unter lib/Varien, und natürlich im Zend Framework.

Ein Klassiker ist z.B. JSON Kodierung. PHP hat zwar built-in die Funktionen json_encode und json_decode, die haben aber einige Unzulänglichkeiten, die in der Implementierung von Zend_Json ausgebügelt wurden. So gibt es in Zend_Json::encode() einen Zyklen-Check. Magento hat in Mage_Core_Helper_Data::jsonEncode() noch Support für Inline-Translations innerhalb von JSON hinzugefügt.
In Magento sollte man also immer Mage::helper('core')->jsonEncode() (bzw. jsonDecode) benutzen.

Varien_File_Csv

Und wie sieht es bei der Verarbeitung von CSV Dateien aus? Da der import und Export im Standard mit CSV Dateien funktioniert, sollte Magento doch etwas haben… Vorhang auf für Varien_File_Csv. Naja, ich nehme das Ergebnis mal vorweg: außer bei ganz einfachen Aufgaben mit kleinen Dateien ist die Klasse nicht zu gebrauchen.

Continue reading “CSV-Verarbeitung in Magento”

Magento Indexer-Fortschritt visualisieren

Magento Reindex Progress

Beim Magento Hackathon Zürich 2014 haben Dima Janzen von Mash2 und ich uns ein altes Thema aus der „Projektideen“-Liste ausgesucht, „Visualize reindexing“ (Danke an Tim Bezhashvyly für den Vorschlag!). Das Team, das es letztes Mal angefangen hatte, zu implementieren, sagte uns, dass es keinen vernünftigen Weg gibt, den Fortschritt von verarbeiteten Index-Events zu bestimmen, was auch unser erster Ansatz gewesen wäre. Also kamen wir auf einen anderen Ansatz: Schätzen der Gesamtlaufzeit pro Indexer, basierend auf vorherigen Laufzeiten, so wie es zum Beispiel auch Buildserver machen.

Glücklicherweise sind diese Daten einfach auszulesen, Magento speichert bereits Startzeit und Endzeit des aktuellen/letzten Laufs in der Datenbank-Tabelle “index_process”, wir mussten sie nur in einer zweiten Tabelle persistieren, um den Verlauf zu sehen.

Dann war der Großteil der Arbeit, ein schönes User Interface drumherum zu bauen. Die wichtigen Ziele dabei waren:

  • Unaufdringliche Integration ins Indexer Grid
  • Information in Echtzeit

Weiterlesen auf integer-net.de

Ein JSON-RPC Adapter für die Magento API

Beim Durchsehen meiner alten Antworten auf Magento StackExchange bin ich auf diese Frage zum Ansprechen der Magento API via JavaScript gestoßen und musste feststellen dass der Link auf GitHub, der einen wesentlichen Teil der Lösung enthielt, nämlich die Implementierung eines JSON-RPC Adapters für die Magento-API mittlerweile tot ist.

Also habe ich kurzerhand das komplette daraus entstandene Modul selbst veröffentlicht (der originale Link war ein Core Hack):

GitHub: SGH_JsonRpc

Das ganze Modul sind weniger als 100 Zeilen Code. In config.xml wird unser Controller der api Route hinzugefügt:

    <frontend>
        <routers>
            <api>
                <args>
                    <modules>
                        <sgh_jsonrpc before="Mage_Api">SGH_JsonRpc_Api</sgh_jsonrpc>
                    </modules>
                </args>
            </api>
        </routers>
    </frontend>

Der neue API Adapter wird in api.xml definiert:

Continue reading “Ein JSON-RPC Adapter für die Magento API”

Magento Attribute effizient inkrementieren & dekrementieren

Magento.SE Screenshot

Diese Frage tauchte auf Magento StackExchange auf:

I need to decrement a value with an atomic database operation, is it possible using Magento models?

Es ist tatsächlich möglich, Attribute mit einem Update zu inkrementieren und dekrementieren und zwar mit einer weniger bekannten Technik mittels Zend_Db_Expr. Ich teile es auch hier:

$object->setNumber(new Zend_Db_Expr('number-1'));

Als Referenz:

Die Methode Mage_Core_Model_Resource_Abstract::_prepareDataForSave() enthält folgenden Code:

if ($object->hasData($field)) {
    $fieldValue = $object->getData($field);
    if ($fieldValue instanceof Zend_Db_Expr) {
        $data[$field] = $fieldValue;
    } else {
        ... [normale Verarbeitung folgt]

EAV Models:

Beachte, dass man das Attribut nur mit seinem Namen referenzieren kann (“number” im Beispiel) wenn es eine echte Spalte der Haupt-Tabelle ist, kein EAV-Attribut.

Obwohl die obengenannte Methode nur von Models mit flachen Tabellen benutzt wird, kann Zend_Db_Expr aber auch für EAV Attribute benutzt werden, die Methode die den Parameter verarbeitet, ist Varien_Db_Adapter_Pdo_Mysql::prepareColumnValue().

ABER man muss dann immer den Spaltennamen “value” verwenden:

$product->setNumber(new Zend_Db_Expr('value-1'));

Es ist nicht notwendig, einen Alias für die Tabelle anzugeben, weil jedes Attribue einzeln mit eigenem Query verarbeitet wird, so dass “value” nicht mehrdeutig ist.

Spryker vs. Magento

Neulich hatte ich die Gelegenheit, einen Blick in den Source Code von Spryker zu werfen, dem Ecommerce-Framework das sich anschickt zum neuen Player im Enterprise-Bereich zu sein. Spryker wird vom Berliner Inkubator Project A für den Einsatz in selbst geförderten Unternehmen entwickelt und soll in diesem Jahr der Öffentlichkeit zugänglich gemacht werden. Wobei Öffentlichkeit nicht ganz stimmt, denn soviel vorab: Eine Open Source Version ist nicht geplant, Lizenzen sollen um 100.000 € / Jahr kosten. Agenturen können sich als Partner registrieren, um ohne Lizenz Zugriff auf Source Code und Dokumentation zu bekommen. Mit der ersten verkauften Lizenz gibt es Zugriff auf zusätzliches Training-Material. Freelancer werden nicht angesprochen. Bereits offiziell als Partner gelistet sind auch die im deutschen Magento-Umfeld bekannten Agenturen CommercePlus und Symmetrics.

Was ist das Besondere an Spryker?

Spryker versteht sich nicht als fertig einsetzbares Produkt, sondern als Framework das alle Bausteine für eine individuelle E-Commerce Lösung bereitstellt, aus denen man sich für sein Projekt die benötigten auswählt und erweitert. Damit wird die Realität berücksichtigt, dass kein Projekt wie das andere ist und jeder Shop seine eigenen Prozesse und Infrastruktur hat, auf die individuell eingegangen werden muss.

Der Kern von Sprkyer sind zwei eigenständige Applikationen, Yves und Zed. Kurz gefasst, ist Yves eine leichtgewichtige Anwendung für das Frontend, Zed das schwere Geschütz fürs Backend.

Discover Spryker
Bild: http://spryker.com/product/

Yves (in der ersten Version mit Yii programmiert, jetzt mit Silex) liest sämtliche benötigten Daten ausschließlich aus einem In-Memory NoSQL Backend wie Redis.
Zed (Zend Framework 2) übernimmt die Kommunikation mit MySQL, Message Queue und Dritt-Systemen und enthält die Geschäftslogik zu Bestellprozessen usw.
Continue reading “Spryker vs. Magento”

Magento: Direktlink auf Tab in Adminhtml Tab Widgets

Für eine Extension an der ich neulich gearbeitet habe, habe ich mich gefragt, ob es eine eingebaute Möglichkeit gibt, direkt auf einen Tab einer Seite im Backend zu linken. Meine Nachforschungen haben nichts ergeben (sprich: Mein Google Foo hat versagt), also habe ich mir den Core Code vorgenommen um einen Ansatz zu finden. Was ich herausgefunden habe:

Das Problem

Im speziellen wollte ich einen Link auf den “Bezeichnungen / Optionen verwalten” Tab der “Produktattribut bearbeiten” Seite setzen:

Screenshot

Die Lösung

Tatsächlich ist es mit einem URL-Parameter möglich: ?active_tab=$id.

Wie finde ich die Tab-ID heraus?

Finde den verantwortlichen Tab Container. Dies ist eine Unterklasse von Mage_Adminhtml_Block_Widget_Tabs, in meinem Fall Mage_Adminhtml_Block_Catalog_Product_Attribute_Edit_Tabs.

Du wirst Aufrufe von $this->addTab() finden, üblicherweise in den Methoden _beforeToHtml(), oder _construct(). Der erste Parameter von addTab() ist die Tab ID:

    $this->addTab('labels', array(
        'label'     => Mage::helper('catalog')->__('Manage Label / Options'),
        'title'     => Mage::helper('catalog')->__('Manage Label / Options'),
        'content'   => $this->getLayout()->createBlock('adminhtml/catalog_product_attribute_edit_tab_options')->toHtml(),
    ));

Die URL ist also /admin/catalog_product_attribute/edit/attribute_id/123/?active_tab=labels, generiert mit diesem Code (innerhalb eines Adminhtml Blocks):

    $this->getUrl('adminhtml/catalog_product_attribute/edit',
        array('attribute_id' => 123, '_query' => array('active_tab' => 'labels'));

Wie es funktioniert

Schauen wir uns den verantwortlichen Code an:
Continue reading “Magento: Direktlink auf Tab in Adminhtml Tab Widgets”

Magento ACL: Kein “Ausloggen und wieder einloggen” mehr nach Extension-Installation

In fast jeden Installationsanweisungen für Magento Extensions findet man den Schritt “Ausloggen und wieder einloggen”, der notwendig ist um Zugriff auf neue Bereiche im Admin Menü oder der Systemkonfiguration zu erhalten. Aber warum müssen wir das hinnehmen? Das Problem ist, dass die Access Control List (ACL) nur bei Login geladen wird und dann in der Session gecached bleibt. Sie bei jedem Zugriff zu laden ist keine brauchbare Alternative, das würde das Backend zu sehr verlangsamen.

Aber mit nur wenigen Zeilen Code können wir das Neuladen der ACL ein wenig komfortabler gestalten:

Der Code

Diese Controller Action lädt die ACL auf Anfrage neu:

class SSE_AclReload_Adminhtml_Permissions_AclReloadController
    extends Mage_Adminhtml_Controller_Action
{
    public function indexAction()
    {
        $session = Mage::getSingleton('admin/session');
        $session->setAcl(Mage::getResourceModel('admin/acl')->loadAcl());

        Mage::getSingleton('adminhtml/session')->addSuccess(
            $this->__('ACL reloaded'));
        $this->_redirectReferer();
    }
}

Dieses Template stellt einen Button zur Verfügung, der zu dem neuen Controller linkt:

<?php
$request = Mage::app()->getRequest();
?>
<a href="<?php echo $this->getUrl('adminhtml/permissions_aclReload/index'); ?>">
<?php echo $this->__('Reload ACL'); ?>
</a>

Und dieses Layout Update fügt den Button zu “404” Fehlerseiten im Backend hinzu (die, die man sieht, wenn man keinen Zugriff auf eine Seite hat):

    <adminhtml_noroute>
        <reference name="content">
            <block type="adminhtml/template" name="content.aclReload"
                after="content.noRoute" template="sse_aclreload/button.phtml" />
        </reference>
    </adminhtml_noroute>

Das Ergebnis

Screenshot Magento Admin 404

Continue reading “Magento ACL: Kein “Ausloggen und wieder einloggen” mehr nach Extension-Installation”

TranslationHints 0.2 für Magento veröffentlicht

Screenshot: Translation Hint

Ich habe Version 0.2 meiner Magento Extension SSE_TranslationHints veröffentlicht, einem Entwickler-Werkzeug, das die Quelle von Übersetzungen zusammen mit alternativen, überschriebenen Übersetzungen für den selben Text direkt im Frontend anzeigt.

Die Konfiguration ist wie gehabt analog zu den Template Hints:

Screenshots: Translation Hints Configuration

News

Zusammen mit der Quelle der Übersetzung sieht man jetzt auch alternative Übersetzungen, die von der genutzten Quelle überschrieben wurden und einige zusätzliche Informationen.

Im folgendem Beispiel sieht man den Geltungsbereich (Scope) der Übersetzung (Mage_Customer), die Übersetzung für diesen Scope, sowie die Übersetzung, die im globalen Scope verwendet würde, das heißt wenn es keine Scope-spezifische Übersetzung gäbe. Der CACHED Tag sagt uns, dass die Übersetzungen us dem Translation Cache geladen wurden:

Screenshot: Translation Hint

Continue reading “TranslationHints 0.2 für Magento veröffentlicht”