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”

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 Tutorial: Wie man Increment Models nutzt, um IDs zu generieren (oder SKUs)

Hast du dich je gefragt, wie Magento die increment_id Werte für Bestellungen, Rechnungen etc. generiert und wie man diesen Mechnismus nutzen oder erweitern kann? Vielleicht hast du die eav_entity_store Tabelle entdeckt, die die jeweils letzte increment id pro Entity-Typ und Store enthält, wobei je Store ggf. ein unterschiedlicher Präfix verwendet wird:

mysql> select * from eav_entity_store;
+-----------------+----------------+----------+------------------+-------------------+
| entity_store_id | entity_type_id | store_id | increment_prefix | increment_last_id |
+-----------------+----------------+----------+------------------+-------------------+
|               1 |              5 |        1 | 1                | 100000090         |
|               2 |              6 |        1 | 1                | 100000050         |
|               3 |              8 |        1 | 1                | 100000027         |
|               4 |              7 |        1 | 1                | 100000005         |
|               5 |              1 |        0 | 0                | 000000011         |
|               6 |              5 |        2 | 2                | 200000001         |
|               7 |              5 |        3 | 3                | 300000002         |
|               8 |              8 |        3 | 3                | 300000001         |
|               9 |              6 |        3 | 3                | 300000001         |
+-----------------+----------------+----------+------------------+-------------------+
9 rows in set (0.00 sec)

In diesem Artikel erkläre ich, wie dieses System für andere Entities genutzt werden kann.

Zunächst mal, die Standard-Methode funktioniert nur mit EAV Entities, nur mit einem Attribut pro Entity und dessen Name muss increment_id sein. Ich erkläre gleich aber noch, wie diese Beschränkungen umgangen werden können.

Continue reading “Magento Tutorial: Wie man Increment Models nutzt, um IDs zu generieren (oder SKUs)”

Warum ich aktiv PHP 5.3 Kompatibilität aufgeben werde

PHP Supported Versions

Das geht ganz einfach und elegant, da in PHP 5.4 die short array syntax eingeführt wurde:

$everySingleArrayInitializationFromNowOn = [];

Warum dieser Schritt? Eine alarmierend große Zahl an Websites läuft noch auf PHP 5.3, das seit dem 14.8.2014 nach einem Jahr “security only” Support nicht mehr aktualisiert wird. Das heißt im Klartext, die nächste kritische Sicherheitslücke wird nur noch für Versionen ab 5.4 gefixt. Die aktive Weiterentwicklung am PHP 5.4 Branch ist übrigens auch am 14.9.2014 eingestellt worden, auch hier sind wir bereits in der “security only” Phase. Am 28.8.2014 ist PHP 5.6 released worden, am 20.6.2013 also vor fast 1,5 Jahren PHP 5.5

Im Jahre 2014 sollten wir also alle längst auf PHP 5.5 arbeiten. Soweit die Theorie. In der Praxis sieht es leider so aus:
PHP versions statistics - October 2014 - Pascal MARTIN
Quelle: http://blog.pascal-martin.fr/post/php-versions-stats-2014-10-en

Fast die Hälfte der Alexa Top 1M Sites, die auf PHP laufen, geben noch die Version 5.3 an, knapp ein viertel sogar noch 5.2, das seit Januar 2011 nicht mehr supported wird. PHP 5.2.17 ist sogar die am meisten in der Statistik auftauchende Patch-Version.

Gründe gibt es vermutlich viele:

  • “never touch a running system” Mentalität
  • Gar nicht oder nicht ausreichend gewartete Server
  • Inkompatible Frameworks und Legacy Anwendungen

Auf einige Hintergründe will ich kurz eingehen.

Continue reading “Warum ich aktiv PHP 5.3 Kompatibilität aufgeben werde”

PHP: header() mocken, um Controller zu Unit-testen

2011 habe ich eine Technik vorgestellt, Funktionen in PHP Unit Tests zu mocken, die sich die Regeln für Namensauflösung von PHP namespaces zunutze macht. Er kann hier gelesen werden:

Es macht mich stolz, dass der große Matthew Weier O’Phinney 1 nun die selbe Technik beschreibt, um Code zu testen, der Ausgaben erzeugt, insbesondere Code, der HTTP Header mit der Core-Funktion header() sendet. Lies mehr dazu in seinem Artikel:

Meiner Meinung nach ist das ein großartiges Beispiel dafür, wie nützlich diese Methode ist. “Headers already sent” Fehler in Unit Tests können einen in den Wahnsinn treiben. Unglücklicherweise gibt es immer noch viele Anwendungen, die keine Namespaces nutzen (*hust* Magento *hust*), dort funktioniert die Methode nicht.

Notes:

  1. für die, die ihn nicht kennen: Er ist Zend Framework Project Lead und Du solltest seinem Blog auf http://mwop.net/blog.html folgen!

Unit-testen von generierten PDFs mit PHPUnit und PDFBox

Zu den Features, die sich nicht leicht mit Unit Tests prüfen lassen gehört das generieren von PDF-Dateien.

Hilfreich dazu ist das Kommandozeilen-Tool PDFBox mit der Option ExtractText:

PDF

This application will extract all text from the given PDF document.

Das erlaubt uns schon mal, den Text-Inhalt des Dokuments zu testen oder nach bestimmten Strings darin zu suchen.

Interessant wird es mit der Option -html, die das PDF zu HTML konvertiert. Damit sind auch Struktur und Formatierungen ansatzweise testbar.

Leider arbeitet das Tool nicht mit Streams, wir müssen also temporäre Dateien nutzen. Ein einfaches Beispiel für eine Funktion, die ein PDF Dokument als String entgegennimmt, mit PdfBox in HTML konvertiert und das HTML als String zurückgibt:

/**
 * @var string $streamIn binary string with generated PDF
 * @return string HTML string
 */
function htmlFromPdf($streamIn)
{
  $pdf = tempnam();
  file_put_contents($pdf, $streamIn);
  $txt = tempnam();
  exec('java -jar pdfbox-app-x.y.z.jar ExtractText -encoding UTF-8 -html ' . $pdf . ' ' . $txt);
  $streamOut = file_get_contents($txt);
  unlink($pdf);
  unlink($txt);
  return $streamOut;
}

Für Regressionstests oder beim Refaktorisieren kann es mitunter ausreichen, zu testen, dass das generierte PDF sich gegenüber dem Referenz-Dokument nicht geändert hat. Hier bietet sich ein Hash an, die Datei selbst ist aber – vermutlich aufgrund von Timestamps – nicht bei jedem Durchlauf exakt gleich. Ein Hash des nach HTML konvertierten Dokuments dagegen genügt:

        // In PHPUnit test case:
        $converter = new PdfBox();
        $html = $converter->htmlFromPdfStream($pdf);
        $this->assertEquals('336edd9ee49b57e6dba5dc04602765056ce05b91', sha1($html), 'Hash of PDF content');

Continue reading “Unit-testen von generierten PDFs mit PHPUnit und PDFBox”

PHP: Referenzen und Speicher

Nutze niemals Referenzen in PHP, nur um Speicherbedarf zu reduzieren. PHP handhabt das bereits mit seinem internen copy on write Mechanismus.

Beispiel:

$a = str_repeat('x', 100000000); // Memory used ~ 100 MB
$b = $a;                         // Memory used ~ 100 MB
$b = $b . 'x';                   // Memory used ~ 200 MB

Du solltest Referenzen nur nutzen, wenn Du genau weißt, was du tust und sie für Funktionalität benötigst. Das ist fast nie der Fall, so dass man sie auch getrost völlig ignorieren kann. PHP-Referenzen sind im Allgemeinen eigenwillig und können in unerwartetem Verhalten resultieren.

Frage und Antwort auf StackOverflow

Große PHP-Arrays, SPL und Sessions

Folgende Problemstellung: eine große Datenmenge wird auf einmal abgefragt, soll aber nicht direkt komplett an den Client gesendet werden, also wird sie in der Session zwischengespeichert. Vielleicht im allgemeinen nicht die geschickteste Lösung, in meinem Fall fielen die Nachteile jedoch nicht ins Gewicht. “Groß” bedeutete dabei im Bereich von 10-50 MB in 50K-100K Datensätzen.

Das ist nun leider eine Menge, bei der PHP-Arrays nur noch mit Vorsicht einzusetzen sind. Der Flaschenhals war in diesem Fall array_shift(), womit Einträge aus dem in der Session befindlichen Arrays entnommen wurden. Was läge da näher, als auf eine der SPL-Datenstrukturen zurückzugreifen? Leider sind sowohl SplStack als auch SplFixedArray nicht serialisierbar und somit nicht ohne Weiteres mit Sessions zu gebrauchen.

Dies lässt sich nachrüsten, dabei muss allerdings doch wieder auf PHP-Arrays zurückgegriffen werden. Mit dem Performance-Verlust beim Serialisieren und Deserialisieren erkauft man sich allerdings eine deutlich effizientere Daten-Verarbeitung. In meinem Fall war SplStack bzw. SplDoublyLinkedList perfekt, da die Daten nur noch der Reihe nach abgeholt werden sollten. Die Erweiterung sieht wie folgt aus:

Serialisierbare SPL-Datenstruktur

class SerializableList extends SplDoublyLinkedList
{
    private $_data;
    
    public function __sleep()
    {
        $this->_data = array();
        
        $this->rewind();
        
        while ($this->valid()) {
            $this->_data[] = $this->current();
            $this->next();
        }
        
        return array('_data');
    }
    
    public function __wakeup()
    {
        foreach ($this->_data as $row) {
            $this->push($row);
        }
        
        $this->_data = array ();
    }
}

Kurz erkärt

Beim Serialisieren (__sleep()) wird die Datenstruktur in ein PHP-Array (im Attribut _data) konvertiert und mit return array('_data') festgelegt, dass genau dieses Attribut serialisiert werden soll. Beim Deserialisieren (__wakeup()) ist _data wiederhergestellt und kann zurück konvertiert werden. Anschließend wird mit $this->_data = array() der Speicher wieder freigegeben.

Vorsicht

Ob diese Lösung im konkreten Fall sinnvoll ist, kann nur durch eigene Messungen ermittelt werden. Dabei sollte vor allen Dingen darauf geachtet werden, die Anzahl der Serialisierungsvorgänge so gering wie möglich zu halten, denn wie schon gesagt sind die durch die zusätzliche Konvertierung teurer als zuvor. Als Beispiel: Bei 8 Abfragen a 10000 Datensätzen war meine Anwendung mindestens 20 mal schneller als bei 80 Abfragen a 1000 Datensätzen. Und beide Varianten schlagen die Implementierung nur mit PHP-Arrays um Längen.

Eine Herausforderung für weitere Optimierung wäre es noch, einen eigenen Serialisierer zu schreiben, der ohne PHP-Array auskommt. Das wäre allerdings eher etwas für die PECL, sprich direkt in C gehackt. In PHP selbst sehe ich da wenig Hoffnung in puncto Effizienz.