Objekte erzeugen in Pimcore beschleunigen

Wer in Pimcore schon in der Situation war, viele User-Objekte erzeugen zu müssen (zum Beispiel bei Datenabgleichen mit anderen System) wird festgestellt haben, dass dies eher gemächlich vonstatten geht.

Mit zwei einfachen Maßnahmen kann man dem Prozess gehörig auf die Sprünge helfen.

Den Cache aktivieren

Ist in Pimcore kein Cache konfiguriert, wird per Default der Zend_File_Cache verwendet.
Das ist zwar besser wie nichts, aber immer noch sehr langsam.

Der Cache wird nur dann deaktiviert, wenn man dies explizit mit

Pimcore_Model_Cache::disable();

so setzt.

Es empfiehlt sich daher, den Cache auf eine schnelle Variante wie Memcached oder die MongoDB zu konfigurieren.

Memcache

Memcache ist ultraschnell, da er ausschliesslich im Speicher läuft, hat aber einen Nachteil – alles, was da reingestopft wird, ist global für jeden mit Zugriff auf den Daemon verfügbar. Um hier abgrenzen zu können, setzt Pimcore eine Zwischenschicht zum Taggen ein, und jene Tags werden wiederum in der normalen SQL-Datenbank abgelegt – dieser Umstand macht also den Memcache für Pimcore nicht zu ersten Wahl.

MongoDB

Die MongoDB ist die schnellere Alternative zum Memcache – obwohl sie im Prinzip eigentlich die langsamere wäre, es fehlt aber das Tagging-Handicap.
Die Installation der Datenbank sollte einfach zu bewerkstelligen sein, die meisten Distributionen enthalten sie.

Die Konfiguration in Pimcore erfolgt über die Datei website/var/config/cache.xml. Eine Beispiel-Datei existiert schon in dem Verzeichnis.

Eine funktionierende Version der cache.xml für die MongoDB würde Beispielsweise so aussehen:


    
    
        Core
        
            pricelist_
            99999
            true
        
    
    
        Pimcore_Cache_Backend_Mongodb
        true
        
            pimcore_cache
        
    

Die Datei wird bei jedem Request an Pimcore gesucht, sobald sie vorhanden ist, ist der Cache aktiv.

Den Cache nutzen um Zend_Db_Table zu beschleunigen

Beim Anlegen von vielen Objekten zeigt sich eine kleine, ich will nicht sagen, Unzulänglichkeit, von Zend_Db_Table:

Standardmäßig fragt Zend_Db_Table_Abstract die darunterliegende Datenbank für die Metadaten der Tabelle ab immer wenn diese diese Daten benötigt werden um Tabellenoperationen durchzuführen. Das Tableobjekt holt die Metadaten der Tabelle von der Datenbank indem es die describeTable() Methode des Adapters verwendet.
[...]
In einigen Fällen, speziell wenn viele Table Objekte auf der gleichen Datenbanktabelle instanziert werden kann das Abfragen der Datenbank nach den Metadaten der Tabelle für jede Instanz unerwünscht sein wegen der Geschwindigkeit. In solchen Fällen, können Benutzer davon profitieren das die Metadaten der Tabelle, die von der Datenbank empfangen werden, gecached werden.

Die Kurzfassung: bei jedem Erzeugen eines Daten-Objekts wird die Datenbank gefragt, wie die Daten denn auszusehen haben.
Dieser Vorgang kostet enorm viel Zeit und kann gecached werden mit Zend_Db_Table_Abstract::setDefaultMetadataCache()

Dieser statischen Methode muss einfach nur ein Cache übergeben werden – und einen solchen stellt Pimcore, wie weiter oben beschrieben, zur Verfügung.

Folgende beiden Zeilen aktivieren daher den Metadaten-Cache für Zend_Db_Table:

$cache = Pimcore_Model_Cache::getInstance();
Zend_Db_Table_Abstract::setDefaultMetadataCache($cache);

Die Praxis

Bei einem Kunden war es notwendig, ca 3500 Objekte (mit Assets, es finden also auch Kopiervorgänge auf der Platte statt) zu importieren.
Ohne Cache dauerte dies ca 2 Stunden, mit Cache 15 Minuten, und mit dem Metadaten-Cache nur noch fünf Minuten.

In der Praxis haben diese beiden Maßnahmen uns also enorme Geschwindigkeitssteigerungen gebracht!

EDIT:
Wir haben diese Verbesserung als Issue bei Elements eingereicht: Activate caching Zend table metadata by default.
Die zwei Zeilen sind angemommen worden, ab Version 1.4.10 ist der Cache daher nun im Core von vornherein mit drin :)

Mobile Chrome am Desktop über USB debuggen

Wir beschäftigen uns derzeit start mit Responsive Design in HTML mit Media Queries. Insbesondere interessant ist für uns dabei das Verhalten der mobilen Geräte wie Tablets oder Smartphones.

Leider gestaltet sich das Debuggen des HTML/CSS/Javascript sehr viel schwieriger bei mobilen Geräten als am Desktop, da die Konsole, wie sie zum Beispiel Firefox oder Chrome standardmäßig haben, nicht verwendbar ist. Die Nutzung würde sich mangels geeignetem Zeigegerät wie einer Maus und dem geringen Platz am Bildschirm sehr schwierig gestalten.

Debuggen mit Android

Google stellt allerdings für Android-Geräte mit dem USB-Debugging-Modus eine Möglichkeit bereit, den eigenen Browser Chrome anzuzapfen!

Um das nutzen zu können, braucht man nur am Desktop ebenfalls den Chrome-Browser sowie das das Android Software Development Kit (SDK), welches unter http://developer.android.com/sdk/index.html heruntergeladen werden kann. Für uns interessant sind hierbei aber nur die Platform Tools.

USB Debugging aktivieren am mobilen Gerät

Hier aktiviert man das USB-Debugging

Hier aktiviert man das USB-Debuggin


Um auf die Daten des Browsers zugreifen zu können, muss erst das Debugging aktiviert werden. Unter Android 4.x macht man dies in den Systemeinstellungen unter „Entwickleroptionen“ In diesem Menü macht man nun einen Haken bei “USB-Debugging”; fortan startet der Debugging-Modus, wenn das Gerät an USB angeschlossen wird.
Es empfiehlt sich, den Modus nach dem Debugging wieder zu deaktivieren, da sich hier Angriffsflächen für Hackingversuche bieten, wenn das Gerät in falsche Hände gerät.

Wenn man nun das Gerät an USB anschliesst, muss unter Umständen unter Windows noch der ADK (Android Debugging Kit) Treiber installiert werden, Linux kommt ohne weiteres Zutun klar.

Die Verbindung zum Browser des Mobilgeräts herstellen

Als nächstes muss die Verbindung zwischen dem Desktop und dem Browser des Mobilgeräts hergestellt werden. Dies erledigt das adb Programm der Platform-Tools:

$ cd adt-bundle-linux-x86_64/sdk/platform-tools 
$ ./adb forward tcp:9222 localabstract:chrome_devtools_remote

Wenn kein Fehler auftritt, steht nun unter dem Port 9222 des localhost der mobile Browser zur Verfügung.

Den mobilen Browser ansprechen

Der mobile Chrome kann am Desktop debugged werden

Der mobile Chrome kann am Desktop debugged werden

Um den mobilen Browser anzusprechen, ruft man mit dem lokalen Chrome die URL http://localhost:9222 auf. Es erscheint eine Auswahl von aktuell vorhandenen Tabs am entfernten Browser, hier wählt man den Debug-Kandidaten aus.
Es erscheint nun das Entwickler-Fenster, wie man es von Chrome gewohnt ist – nur dass man jetzt die Interna des entfernten Browsers sieht. Die Werkzeuge verhalten sich exakt wie zum lokalen Pendant; wenn man z.B. ein Node im DOM mit dem Mauszeiger überfährt, wird dieses im Browserfenster des Mobilgeräts hervorgehoben.

Gibt es zum Thema „mobiles Debugging“ Fragen oder Anregungen? Wir freuen uns über Eure Kommentare!

Dynamic Dropdown Module für Pimcore in Extension Hub eingestellt

Nachdem wir bislang ausschließlich positives Feedback zu unserem Dynamic Dropdown Module für Pimcore erhalten haben, ist nun der Entschluss gefallen das Plugin auch allgemein öffentlich zugänglich zu machen.

Das Plugin stellt ein Dropdown-Eingabefeld für Objekte zur Verfügung, dessen Inhalt sich konfigurierbar aus den Objekten eines Folder speist.

Pimcore bietet zur Veröffentlichung sehr komfortabel die Möglichkeit, die eigenen Plugins im Extension Manager direkt zu veröffentlichen – “Teilen” klicken, ein paar Formulare ausfüllen und Bilder hochladen – fertig.

Wir freuen uns über Feedback!

Google Recaptcha, Zend Forms, SSL und der Internet Explorer

In Pimcore lassen sich mit dem Form Builder Plugin relativ leicht Formulare erzeugen und mit Captchas ausstatten.
Unter den Auswahlmöglichkeiten der verfügbaren Captchas steht auch das Google Recaptcha auf der Liste.

Sobald man das Formular SSL gesichert überträgt bekommt man mit dem Internet Explorer ein Problem: je nach Sicherheitseinstellungen lässt der IE das Nachladen von ungesichertem Content nur mit Nachfrage zu – gegebenenfalls bekommt der User das Captcha also nicht einmal zu Gesicht und kann das Formular nicht korrekt ausfüllen.

Der IE7 meldet beispielsweise “Diese Seite enthält sowohl sichere als auch nicht sichere Objekte“, der IE9 zeigt die Fehlermeldung “Nur sicherer Inhalt wird angezeigt“; auf der Debug-Konsole ist zu lesen “SEC7111: HTTPS-Sicherheit beeinträchtigt durch http://www.google.com/recaptcha“.

Zu lösen ist das Problem offensichtlich dadurch, dass der Recaptcha ebenfalls über SSL geladen werden muss.
Dies geschieht durch Setzen eines Parameters im Zend_Service_ReCaptcha Objekt des Formelements: $service->setParam("ssl", true).
Will man dies allgemeingültig lösen, sollte HTTPS nur dann aktiviert werden, wenn die aufgerufene Seite ebenfalls mit HTTPS übertragen wurde. Diese Information erfährt man durch Aufruf der getScheme Methode des Request-Objekts, das der Controller bereithält: $scheme = $this->getRequest()->getScheme();.

Gesamt lässt sich das mit wenigen Zeilen lösen:

$formbuilder = new Formbuilder_Frontend();
$form = $formbuilder->getForm("ein_beispiel");
$captcha = $form->getElement("captcha");
$captcha->getCaptcha()->getService()->setParam("ssl", $this->getRequest()->getScheme() == "https");

Captchas von Zend Forms in individuelle Formulargestaltung integrieren.

In Formulare in Pimcore einfach erstellt hatten wir ja schon erläutert, wie man ein Formular “zu Fuss” programmiert und sich nur bei den benötigten Elementen bedient, um so das Formular ganz individuell ohne den Aufwand eigener Decorator zu erstellen.

Problem bei der Herangehensweise ist aber, dass Captcha Elemente keine renderViewHelper() Methode kennen und nur über render() ausgegeben werden können. Hierbei werden dann die default-Decorators benutzt, die man dann mit eigenen ersetzen müsste.

Um nun alle Elemente des Captchas (Captcha-Bild, Eingabefeld und hidden-field mit “Lösung”) auszugeben muss man die Schritte ausführen, die Zend_Form_Decorator_Captcha_Word mittels render() erledigen würde:

    public function render($content)
    {
        $element = $this->getElement();
        $view    = $element->getView();
        if (null === $view) {
            return $content;
        }

        $name = $element->getFullyQualifiedName();

        $hiddenName = $name . '[id]';
        $textName   = $name . '[input]';

        $label = $element->getDecorator("Label");
        if($label) {
            $label->setOption("id", $element->getId()."-input");
        }

        $placement = $this->getPlacement();
        $separator = $this->getSeparator();

        $hidden = $view->formHidden($hiddenName, $element->getValue(), $element->getAttribs());
        $text   = $view->formText($textName, '', $element->getAttribs());
        switch ($placement) {
            case 'PREPEND':
                $content = $hidden . $separator . $text . $separator . $content;
                break;
            case 'APPEND':
            default:
                $content = $content . $separator . $hidden . $separator . $text;
        }
        return $content;
    }

Interessant sind hier für uns die Codeteile, die sich mit dem Text- und Hiddenfield befassen.
Diese bedienen sich anderen View Helpern, nämlich Zend_View_Helper_FormHidden und Zend_View_Helper_FormText, die nur mit geeigneten Parametern wie dem Feldnamen oder zu vererbenden Attributen versorgt werden müssen.

Hier ein Beispiel, wie man die Darstellung behandeln kann:

<?php
$formbuilder = new Formbuilder_Frontend();
$form = $formbuilder->getForm("kontakt");
$captcha = $form->getElement("captcha");
$name = $captcha->getFullyQualifiedName(); // Der Name des Eingabefelds
$hiddenName = $name . '[id]'; // Der Name des hidden-fields, erweitert als Array
$textName   = $name . '[input]'; // Der Name des text-fields, erweitert als Array
?>

        <tr>
            <td><?php print $form->captcha->renderLabel() ?></td>
            <td>
              <?php print $form->captcha->renderCaptcha() ?><br>
              <?php print $this->formHidden($hiddenName, $captcha->getValue(), $captcha->getAttribs()); ?>
              <?php print $this->formText($textName, '', $captcha->getAttribs());  ?>
              <?php print $form->captcha->renderErrors() ?></td>
        </tr>

Merkwürdiges Blinkerproblem mit Zend_Autoloader – Ursache Cache

Heute hatten wir ein merkwürdiges “Blinker”-Problem mit Pimcore auf einem Kundenserver.
Mal ging die Seite, mal nicht, je mehr Requests desto öfters fiel die Seite mit einem Error 500 aus, merkwürdigerweise verschwand der Fehler nach einer gewissen Wartezeit:

PHP Fatal error:  Class 'Pimcore_Tool' not found in /blah/fasel/htdocs/pimcore/lib/Pimcore.php on line 515
PHP Fatal error:  Class 'Pimcore_Model_Cache' not found in /blah/fasel/htdocs/pimcore/lib/Pimcore.php on line 661

und dergleichen Fehlermeldungen mehr.

Sowas riecht in aller Regel ja nach Caching-Problemen, tatsächlich tauchen bei Recherche im Internet einige mögliche Ursachen auf, in aller Regel in Zusammenhang mit PHP Version rund um 5.3.2

Folgende Einstellungen haben die Symptome behoben – das eigentliche Problem dürfte tiefer liegen.

zend.enable_gc = Off
realpath_cache_size = 0

Einbetten von Bilder in einer HTML-Mail in Pimcore

Hier ein Fragment Code, mit dem man in Pimcore aus bereits bestehendem HTML-Code eine HTML-Email mit eingebetteten Bildern machen kann:

<?php

require_once('Zend/Mail.php');
$doc = new DOMDocument();
@$doc->loadHTML($body);
$tags = $doc->getElementsByTagName('img');
$counter = 0;
$mail = new Zend_Mail();

foreach ($tags as $tag) {
	$counter++;
	$body = str_replace($tag->getAttribute('src'),"cid:img_".$counter,$body);
	$tmp = array_reverse(explode("/",$tag->getAttribute('src')));
	$bildname = $tmp[0];
	$size = getimagesize(PIMCORE_DOCUMENT_ROOT . $tag->getAttribute('src'));

	if(strpos($tag->getAttribute('src'),"http://") !== false) {
		$output = file_get_contents($tag->getAttribute('src'));
	} else {
		$filename = PIMCORE_DOCUMENT_ROOT . $tag->getAttribute('src');
		$output = file_get_contents($filename);
	}
	$attachment = $mail->createAttachment(
		$output,
		$size['mime'],
		Zend_Mime::DISPOSITION_INLINE,
		Zend_Mime::ENCODING_BASE64,
		$bildname
	);
	$attachment->id = "img_".$counter;
}

$mail->setBodyText($body);
$mail->setFrom('absender@absenderbeispiel.com', 'Ein Absender');
$mail->addTo('jemand@anders.com', 'Ein Empfänger');
$mail->setSubject('Eine eMail mit eingebetteten Bildern');
$mail->send();

?>

Hierbei wird das DOMDocument benutzt, um das HTML zu parsen und die IMG-Elemente abzuarbeiten. So kann zielgerichtet das src-Attribut ausgelesen werden, um dessen Inhalt im per str_replace zu verändern.  Die URL wird zum Einbetten in die cid: Notation gewandelt, die in eMails gebraucht wird zur Identifikation des Attachment.

Document programmatisch rendern in Pimcore

Manchmal kommt man in die Verlegenheit, in Pimcore ein HTML-Seite mit variablen Inhalten erstellen zu müssen, die nicht zum “normalen” Seitenportfolio gehört – beispielsweise ein HTML-Newsletter. Pimcore kann aber Documents auch ausserhalb des normalen Dispatch-Vorgangs der Seitenerzeugung  verarbeiten.

Hierfür kennt der Document_Service von Pimcore die statische Methode render(). Dieser kann das gewünschte Dokument sowie ggf. seine Parameter übergeben werden:

$document = Document::getById($id)
$params = array("blah" => "fasel");
$output = Document_Service::render($document, $params);

In $output steht dann die fertige Ausgabe in HTML als String, die zur weiteren Verarbeitung genutzt werden kann.

Das Rendern erfolgt dabei aber ohne das Layout! Wer also einen vernünftigen Header für einen Newsletter braucht muss sich selbst kümmern.

DynamicDropdown Module 0.0.4

Eine neue Version unseres DynamicDropdown Moduls für pimcore ist fertig.

Die Auswahl des Eingabefelds

Die Auswahl des Eingabefelds in der Liste der vorhandenen Felder

Das Modul erweitert pimcore um ein neues Eingabefeld für Objekte; ein dynamisches Dropdown. Hierbei lassen sich die einzelnen Optionen eines Dropwdowns bestücken aus den Inhalten anderer Objekte.

Dies ist seit diese Version in sich dynamisch, ändert sich daher das gewählte Feld des Quell-Objekts, so auch die Ausgabe in diesem Eingabefeld.

 

 

 

Konfiguration des Eingefelds

Konfiguration des Eingefelds in pimcore

Die Datenquelle des Eingabefelds; andere von pimcore verwaltete Objekt, sowie dessen Datenfeld (bzw: die Ausgabemethode) lassen sich dabei in der Konfiguration des jeweiligen Eingabefelds frei und komfortabel mit Dropdowns oder per drag&drop wählen.

Herunterladen lässt sich das Modul hier: http://www.weblizards.de/pimcore/DynamicDropdown_0.0.4.tgz

Code debuggen auf einem Remoteserver mit xdebug und Netbeans

Die Standardsituation als Softwareentwickler: das Programm verhält sich nicht so wie erwartet.

Als Webentwickler hat man es da oft schwer und behilft sich mit Debugausgabe wie

print "DEBUG: blah fasel";

Eine große Arbeitserleichterung stellt es dar, einen Debugger zu verwenden. Mit PHP und Netbeans ist das kein Problem. Solange der Server nicht mit der eigenen Entwicklungsmaschine identisch istgilt es, ein paar Hürden zu überwinden.

Natürlich muss die xdebug-Erweiterung für PHP installiert sein. Die meisten Distributionen kennen xdebug von Haus aus, eine allgemeine Installation ist hier beschrieben.

Für uns wichtig ist die remote-Funktion, die mit den richtigen Einstellungen aktiviert sein muss. Dies kann in der php.ini oder der .ini des Moduls geschehen, in den Einstellungen des Virtualhosts oder einer .htacces, sofern möglich.

Hier beschrieben ist die .htaccess-Methode:

php_value xdebug.remote_enable 1
php_value xdebug.remote_handler dbgp
php_value xdebug.remote_mode req
php_value xdebug.remote_host 127.0.0.1
php_value xdebug.remote_port 9000
php_value xdebug.idekey netbeans-xdebug

Wie genau funktioniert das Debuggen dann?
Der Server führt seine Scripten nach wie vor genauso aus, nimmt aber von sich aus Kontakt auf mit einem “Fernsteuer-Rechner”, der Daten abgreifen und den Verlauf steuern kann. Hierbei ist die Situation Client/Server umgedreht: Wir nehmen im Browser Kontakt mit dem Server auf, dieser führt das PHP aus und gibt das Resultat zurück. Beim Debuggen nimmt zusätzlich der Server Kontakt auf mit dem Debugger, stellt hier also den Client dar.
Wir müssen dem Server daher sagen, wo der Debugger erreichbar ist, dies geschieht mit den Zeilen

php_value xdebug.remote_host

und

php_value xdebug.remote_port.

Da wir mit der Entwicklungsmaschine meist “irgendwo” im Internet sitzen und jedesmal die neue IP in der .htaccess konfigurieren müssten, und  vermutlich auch noch hinter eine Firewall sind und Port-Forwarding eingerichtet werden müsste, behelfen wir uns hier mit einem Trick, der die ganze Sache sowohl sicher als auch bequem macht: mit einem SSH-Tunnel.

Vorraussetzung ist natürlich Shellzugriff auf die Remote-Maschine, dann genügt folgendes Kommando auf dem lokalen Rechner:

ssh -g -N -R9000:127.0.0.1:9000 [remote_host]

 

Hiermit richtet man seinen eigenen Tunnel von Port 9000 auf der Remote-Maschine auf Port 9000 der lokalen Maschine ein – der Server nimmt daher über den Umweg “SSH-Tunnel” direkt Kontakt auf mit der eigenen Entwicklungsumgebung.

Nun muss nur noch die IDE (hier: Netbeans) richtig konfiguriert werden:

Einstellungen in den OptionsEinstellungen in den Project Properties

Ein Klick auf “Debug Project” und die IDE ruft den Browser auf und beginnt mit dem ersten Haltepunkt.

Wie das Debuggen in Netbeans funktioniert ist hier ausführlich beschrieben.