Tackling this above-the-fold CSS issue

Performance Optimization is somewhat like housekeeping. If you don’t take care of it regularly, the shit piles up. My history of our web performance goes back almost five years now and what started as a blazin’ 94 once was now a 76 while mobile, which was never really good, dropped from the mid-seventies to the high-fifties. Even though I optimize all images and write as less code as possible.

I came across these horrifying numbers because one of our main competitors relaunched recently and while pointing at him and laughing at his numbers (52 Desktop, 51 Mobile) I had to admit that ours aren’t really great either.

Running the Erzgebirge-Palace through PageSpeed Insights spoiled two obvious issues: The long-avoided above-the-fold render-blocking stuff and the trust seal. But that will be a different story, we will focus on the render-blocking css for now.

The core layout for Erzgebirge-Palace is ten years old now and also it has clean markup which made reponsive retrofitting quite easy there is no build process or anything near to it. So the CSS is a little bit messy. Identifying the above-the-fold styles manually was not an option, but hey, Smashing Magazine tackled this issue already two years ago. I came across this article after I unsucessfully tried to run a simple gulp task with the critical plugin by Addy Osmani.

While gulp just spit errors into my console the grunt task by Ben Zörb worked right away and returned a pretty good result. I had to add some more code as there happen things on the page the plugin cannot be aware of as different headers during the holiday season and different headers for different languages. But in the end it was less work than I expected and the result gives me now 92 on Desktop and 91 on Mobile (also resolved the trust seal, though).

All numbers given are from Google PageSpeed Insights. While this tool is ok to do a quick check on your site’s performace you should rely on other tools when actually optimizing your website, my tool of choice here is Webpagetest.

Ein CDN für den modified-Shop – xtcModified on steroids

Steter Tropfen höhlt den Stein. Augrund immer wiederkehrender Nachfragen folgt hier Teil fünf meiner kleinen Serie zur Geschwindigkeitsoptimierung von xtcModified. Es geht um die Anbindung eines CDN für statische Inhalte und daher direkt hier die Warnung: Das ist nichts für Anfänger!

Seit dem letzten Artikel sind fast drei Jahre vergangen, die Software hat sich dem xtc im Namen entledigt und der alte Testserver existiert nicht mehr.
Ich habe daher eine neue Installation aufsetzen müssen. Dieses Mal bei Uberspace. Wenn man auf der Suche nach Shared Hosting mit möglichst großer Konfigurationsfreiheit ist, dann ist das das Beste, was man für Geld bekommen kann. Ich hab natürlich die Standardinstallation erstmal grundlegend optimiert, um so Dinge wie Javascript im Footer aber einen Bogen gemacht, weil es den Aufwand für den Testbetrieb nicht rechtfertigt.

Damit auch ein paar Produkte im Shop sind wurde dieser Dump eingespielt. Anders als vorgeschlagen wurde aber nicht das Image-Processing gestartet. Wer das Image-Processing für eine gute Idee hält braucht gar nicht erst weiterlesen. Die Bilder wurden mittels Photoshop auf die passende Größe gebracht und dann mit einem mehrstufigen Prozess aus ImageOptim und JPEGmini optimiert (ähnliche Software gibt es sicher auch für Windows). Nur so hat man wirklich kleine Dateien. Und die sind aus zwei Gründen sinnvoll: Zum einen sind weniger Bytes schneller durch die Leitung geschickt, zum anderen rechnen CDNs meist nach Datenvolumen ab und hier ist jedes Byte dann bares Geld.
Der Demodump ist leider etwas kaputt, was Kategorie 1 angeht, das soll uns aber für ein Testsystem egal sein.

CDN – was ist das überhaupt?

Ein CDN, oder in Langform Content Delivery Network, ist ein aus mehreren Rechnern bestehendes Netz, das Inhalte optimiert ausliefert – entweder möglichst schnell (Performanceoptimierung) oder mit möglichst wenig Bandbreite (Kostenoptimierung) oder beides zusammen. Im konkreten Fall soll es eher um die Performanceoptimierung gehen, denn aus finanzieller Sicht wird das ganze für den Shopbetreiber auf den ersten Blick teurer als wenn er die Inhalte von seinem eigenen Server ausliefern lässt.
Das CDN macht die Auslieferung aus zwei Gründen schneller:

  • Zum einen sind die Server darauf spezialisiert, schnell statische Inhalt auszuliefern.
  • Zum anderen befinden sich die Server auf der ganzen Welt verteilt, ein Besucher aus Japan wird daher von der „Edge Location“ beliefert, die ihm physikalisch am nähsten ist. Weder die Anfrage noch die Antwort müssen um den halben Erdball, wie das bei einem in Deutschland stehenden Server der Fall wäre.

Und brauch ich das?

Wie bei so vielem ist auch hier die richtige Anwort: Kommt drauf an.
Wer einen Server hat, der in Spitzenzeiten in die Knie geht, weil der Apache nicht mehr hinterherkommt mit dem Ausliefern von Shopseiten und statischen Inhalten kann hier schnell für Abhilfe sorgen, ohne direkt den Server wechseln zu müssen.
Wer viele Kunden aus dem Ausland hat, diesen aber trotzdem Inhalte schnell ausliefern will, ist mit einem CDN ebenfalls gut bedient.
Außerdem kann man, wenn man es geschickt angeht, Browserlimitierungen umgehen was den gleichzeitigen Download von Dateien angeht. Das geht aber prinizipiell auch ohne CDN.

Und was kostet das?

Da es hier um Amazons Cloudfront gehen wird gibt darüber die Preisliste von Amazon Auskunft. Da kann man sich natürlich nicht wirklich viel drunter vorstellen, daher hier noch ein paar Zahlen aus der Praxis. Wir haben für den Erzgebirge-Palast alles auf dem CDN liegen: CSS-Dateien, Layoutbilder und Produktbilder. Unser Monatsmittel 2013 lag bei ca. $21, im vergangenen Jahr bei ca. $26. Für das Geld bekommt man ganz sicher nirgends ein vernünftiges Serverupgrade, mit dem man Lastspitzen wegpuffern kann. Und für uns, die einen sehr deutlichen Peak am Jahresende haben, ist das natürlich sehr wichtig.

Los geht’s: CDN-Distribution anlegen

Man braucht für das, was jetzt folgt, einen Amazon-AWS-Account und Zugriff auf seine Domainkonfiguration. Den Account kann man sich auf der Amazon-AWS-Seite anlegen.
Hat man das erledigt und auch eine Zahlungsweise hinterlegt kann man in der AWS Console ‘CloudFront’ auswählen. Nach dem Klick auf ‘Create Distribution’ wählen wir die Option ‘Web’, denn wir wollen ja Inhalte über HTTP und HTTPS ausliefern.

Jetzt geht es ans Ausfüllen des Formulars. Der einzige wichtige Eintrag ist Origin Domain Name. Hier tragen wir die URL unseres Shops ein (im Beispielfall ladeze.it) und nutzen somit automatisch Custom Origin, was vom Wartungsaufwand deutlich angenehmer ist als beispielsweise das Ausliefern der Dateien über Amazon S3.

Wer darüber hinaus eine regionale Einschränkung bei seinen Besuchern vornehmen kann, weil er beispielsweise gar nicht nach Asien verkauft, kann in den ‘Distribution Settings’ das bei ‘Price Class’ entsprechend auswählen. Wer eigene Domainnamen verwenden will statt abc.cloudfront.net kann diese bei ‘Alternate Domain Names (CNAMEs)’ angeben. Das machen wir mal und tragen cdn0.ladeze.it bis cdn5.ladeze.it dort ein, pro CNAME eine Zeile. Das bringt uns nämlich Vorteile wenn wir die Downloadbeschränkung der Browser umgehen wollen. Bei SSL müssen wir dann aber auf die von Amazon zur Verfügung gestellten URLs zurückgreifen, die Konfiguration mit einem eigenen SSL-Zertifikat würde den Rahmen dieses Artikels sprengen.

Nach dem Klick auf ‘Create Distribution’ startet Amazon das Deployment unserer neuen Distribution. Das dauert einen Moment und daher können wir die CNAME-Einträge für unsere Domains anlegen. Die Ziel-URL für den CNAME-Eintrag zeigt uns Amazon im Distribution-Dashboard bei ‘Domain Name’ an.

cdn-deployment

Wenn beides fertig ist, sowohl das Deployment der Distribution als auch das Einrichten der DNS-Einträge, können wir unser neues CDN zum ersten Mal testen. Das geht ganz einfach im Browser. Statt http://ladeze.it/images/product_images/thumbnail_images/53_0.jpg rufen wir aber http://cdn5.ladeze.it/images/product_images/thumbnail_images/53_0.jpg auf. Da DNS-Einträge bis zu 48 Stunden dauern können kann man, sollten die eigenen sich noch nicht herumgesprochen haben, den Test auch mit der von Amazon zur Verfügung gestellten URL durchführen und http://d4qtfj4qyflow.cloudfront.net/images/product_images/thumbnail_images/53_0.jpg aufrufen. Kommt das richtige Bild haben wir alles richtig gemacht.

Ein Blick in die Header-Angaben zeigt uns auch durch den vorhandenen Via-Record, dass die Datei von Cloudfront ausgeliefert wurde. Auch finden wir hier den X-Cache-Record. Beim ersten Aufruf steht dort ‘Miss from cloudfront’. Hier lag das Bild also nicht in Cloudfront vor und der Amazon-Server musste es von unserem Server laden. Alle subsequenten Aufrufe haben dann den Eintrag ‘Hit from cloudfront’.

cf-misscf-hit

Auch sieht man hier schön, dass unsere Einstellungen für max-age übernommen werden. Das Header-Plugin im Screenshot ist übrigens HTTP Headers.

Anpassungen am Shop

Jetzt geht es darum, dass unser modified-Shop die Bilder auch vom CDN ausliefert und nicht mehr von unserem Server.

Cache-Busting

Daten auf dem Cloudfront-CDN werden in der Default-Konfiguration erst nach 24 Stunden erneut auf Änderungen überprüft. Unsere .htaccess sagt sogar, dass Bilder 30 Tage im Cache gehalten werden können, ohne erneut zu prüfen, ob sich etwas verändert hat. Wir kommen also um ein Cache-Busting nicht herum. Dieses manuell zu machen wäre aber viel zu aufwendig. Daher betrifft die erste Änderung die Datei includes/classes/product.php, Zeile 492 wird dort ersetzt durch:

      $iTime = filemtime(DIR_FS_CATALOG.$path.$name);
      $aPath = pathinfo($name);
      return $path.$aPath['filename']."__".$iTime.".".$aPath['extension'];

Wir bauen also das letzte Modifikationsdatum mit in den Dateinamen ein und trennen es vom eigentlichen Namen mit zwei Unterstrichen ab. Völlig egal also ob wir über den Webbrowser oder direkt per SFTP ein Produktbild erneuern, es bekommt dann ein neues Modifikationsdatum und damit auch einen neuen Namen, der noch nicht im Cloudfront-CDN vorhanden ist und daher frisch angefordert wird.

Damit das auch funktioniert müssen wir außerdem im images/-Ordner eine .htaccess anlegen mit folgendem Inhalt:

RewriteEngine On
RewriteBase /
 
RewriteRule (.*)/(.*)__(.*).jpg$ images/$1/$2.jpg [L]

Weitere Änderungen am Core

Für das weitere Vorgehen braucht es eine Änderung in includes/configure.php, die wir nach dem define für HTTP_SERVER einfügen:

	define('HTTP_SERVER_CDN', 'http://cdn{i}.ladeze.it');
	define('HTTPS_SERVER_CDN', 'https://d4qtfj4qyflow.cloudfront.net');

Als nächstes modifizieren wir inc/xtc_image.inc.php.

Ersetzt wird

	// alt is added to the img tag even if it is null to prevent browsers from outputting
	// the image filename as default
	$image = '<img src="' . xtc_parse_input_field_data($src, array('"' => '&quot;')) . '" alt="' . xtc_parse_input_field_data($alt, array('"' => '&quot;')) . '"';

durch

	if(preg_match("/__/", $src)) {
		$aImage = pathinfo($src);
		$sSrc = preg_replace("/(.*)__(.*)/", "$1", $aImage['filename']);
		$sRealSrc = $aImage['dirname']."/".$sSrc.".".$aImage['extension'];
	}
	if(file_exists($src)) {
		$iCDN = (filesize($src)%5);
	} elseif(isset($sRealSrc) && file_exists($sRealSrc)) {
		$iCDN = (filesize($sRealSrc)%5);
	}
	if(REQUEST_TYPE!="SSL") {
		$sCDNSrc = str_replace("{i}", $iCDN, HTTP_SERVER_CDN)."/".$src;
	} else {
		$sCDNSrc = str_replace("{i}", $iCDN, HTTPS_SERVER_CDN)."/".$src;
	}
 
	// alt is added to the img tag even if it is null to prevent browsers from outputting
	// the image filename as default
	$image = '<img src="' . xtc_parse_input_field_data($sCDNSrc, array('"' => '&quot;')) . '" alt="' . xtc_parse_input_field_data($alt, array('"' => '&quot;')) . '"';

Damit werden jetzt schon diverse Bilder über das CDN geladen, aber vor allem die Produktbilder noch nicht. Um das zu erreichen braucht es ein Smarty-Plugin und Anpassungen an allen betroffenen Template-Dateien. Das kann in Arbeit ausarten, muss aber nur einmal gemacht werden. An dieser Stelle machen wir es nur anhand der Produktinfo-Seite.

Außerdem nutzen wir statt einer Domain für die Bilder jetzt mehrere. Da wir die Subdomain aufgrund des Modulos der Dateigröße festlegen ist es auch immer die gleiche Subdomain, die genutzt wird. Da Browser im Maximum sechs gleichzeitige Verbindungen zu einer Domain aufbauen haben wir damit die Anzahl der parallelen Zugriffe vervielfacht – und das CDN steckt das performancetechnisch locker weg.

Templates

Zunächst das Smarty-Plugin, dieses kommt nach templates/xtc5/smarty/function.cdn_image.php:

<?php
/**
 * Smarty plugin
 * @package Smarty
 * @subpackage plugins
 */
 
 
/**
 * Smarty {cdn_image} function plugin
 *
 * Type:     function<br>
 * Name:     cdn_image<br>
 * Purpose:  print an cdn_image()
 * @author Matthias Slovig
 * @param array parameters
 * @param Smarty
 * @return string|null
 */
function smarty_function_cdn_image($params, &$smarty) {
	require_once (DIR_FS_INC.'xtc_image.inc.php');
 
	return xtc_image($params['src'], $params['alt'], $params['width'], $params['height'], $params['params']);
}
 
/* vim: set expandtab: */
 
?>

Und dann die Anpassungen in templates/xtc5/module/product_info/product_info_tabs_v1.html:

<img src="{$PRODUCTS_IMAGE}" alt="{$PRODUCTS_NAME}" class="productimage" />

wird ersetzt durch

{cdn_image src=$PRODUCTS_IMAGE params="class='productimage'" alt=$PRODUCTS_NAME}

Das ist wie gesagt überall dort zu wiederholen, wo Bilder, die eigentlich über das CDN geladen werden sollen, bislang noch durch einen normalen img-Tag eingebunden sind, also Produktlistings, Kategorienlistings, Boxen etc.

CSS und Javascript

Zuletzt sind CSS und Javascript dran, dafür bedarf es auch zunächst eines Cache-Bustings, dieses Mal in der normalen .htaccess des Shops direkt nach der Defintion der RewriteBase:

  RewriteRule (.*)-([0-9]+)\.css$ $1.css [L]
  RewriteRule (.*)-([0-9]+)\.js$ $1.js [L]

Die neue templates/xtc5/css/general.css.php sieht so aus:

<?php
/* -----------------------------------------------------------------------------------------
   $Id: general.js.php 1262 2005-09-30 10:00:32Z mz $
 
   XT-Commerce - community made shopping
   http://www.xt-commerce.com
 
   Copyright (c) 2003 XT-Commerce
   -----------------------------------------------------------------------------------------
   Released under the GNU General Public License
   ---------------------------------------------------------------------------------------*/
 
   // Put CSS-Definitions here, these CSS-files will be loaded at the TOP of every page
 
	$iCDN['stylesheet'] = filesize(DIR_FS_CATALOG."templates/".CURRENT_TEMPLATE."/stylesheet-min.css")%5;
	$iCDN['thickbox'] = filesize(DIR_FS_CATALOG."templates/".CURRENT_TEMPLATE."/css/thickbox-min.css")%5;
	$iCDN['jquery-ui'] = filesize(DIR_FS_CATALOG."templates/".CURRENT_TEMPLATE."/css/jquery-ui.css")%5;
	$sURL['stylesheet'] = str_replace("{i}", $iCDN['stylesheet'], REQUEST_TYPE=='SSL' ? HTTPS_SERVER_CDN : HTTP_SERVER_CDN)."/templates/".CURRENT_TEMPLATE."/stylesheet-min-".filemtime(DIR_FS_CATALOG."templates/".CURRENT_TEMPLATE."/stylesheet-min.css").".css";
	$sURL['thickbox'] = str_replace("{i}", $iCDN['thickbox'], REQUEST_TYPE=='SSL' ? HTTPS_SERVER_CDN : HTTP_SERVER_CDN)."/templates/".CURRENT_TEMPLATE."/css/thickbox-min-".filemtime(DIR_FS_CATALOG."templates/".CURRENT_TEMPLATE."/css/thickbox-min.css").".css";
	$sURL['jquery-ui'] = str_replace("{i}", $iCDN['jquery-ui'], REQUEST_TYPE=='SSL' ? HTTPS_SERVER_CDN : HTTP_SERVER_CDN)."/templates/".CURRENT_TEMPLATE."/css/jquery-ui-".filemtime(DIR_FS_CATALOG."templates/".CURRENT_TEMPLATE."/css/jquery-ui.css").".css";
?>
<link rel="stylesheet" href="<?php echo $sURL['stylesheet'] ?>" type="text/css" />
<link rel="stylesheet" href="<?php echo $sURL['thickbox'] ?>" type="text/css" media="screen" />
 
<?php // BOF - web28 - 2010-07-09 - TABS/ACCORDION in product_info ?>
<?php
if (strstr($PHP_SELF, FILENAME_PRODUCT_INFO )) {
?>
<link rel="stylesheet" href="<?php echo $sURL['jquery-ui'] ?>" type="text/css" media="screen" />
<?php
}
?>
<?php // EOF - web28 - 2010-07-09 - TABS/ACCORDION in product_info ?>

Das Laden der CSS-Datei über das CDN führt automatisch dazu, dass auch alle relativ referenzierten Bilder ebenfalls über Cloudfront ausgeliefert werden.

Und so sieht die neue templates/xtc5/javascript/general.js.php aus:

<?php
/* -----------------------------------------------------------------------------------------
   $Id: general.js.php 1262 2005-09-30 10:00:32Z mz $
 
   XT-Commerce - community made shopping
   http://www.xt-commerce.com
 
   Copyright (c) 2003 XT-Commerce
   -----------------------------------------------------------------------------------------
   Released under the GNU General Public License
   ---------------------------------------------------------------------------------------*/
 
 
   // this javascriptfile get includes at the BOTTOM of every template page in shop
   // you can add your template specific js scripts here
 
	$iCDN['jquery'] = filesize(DIR_FS_CATALOG."templates/".CURRENT_TEMPLATE."/javascript/jquery.js")%5;
	$iCDN['thickbox'] = filesize(DIR_FS_CATALOG."templates/".CURRENT_TEMPLATE."/javascript/thickbox.js")%5;
	$iCDN['jquery-ui'] = filesize(DIR_FS_CATALOG."templates/".CURRENT_TEMPLATE."/javascript/jquery-ui.js")%5;
	$sURL['jquery'] = str_replace("{i}", $iCDN['stylesheet'], REQUEST_TYPE=='SSL' ? HTTPS_SERVER_CDN : HTTP_SERVER_CDN)."/templates/".CURRENT_TEMPLATE."/javascript/jquery-".filemtime(DIR_FS_CATALOG."templates/".CURRENT_TEMPLATE."/javascript/jquery.js").".js";
	$sURL['thickbox'] = str_replace("{i}", $iCDN['thickbox'], REQUEST_TYPE=='SSL' ? HTTPS_SERVER_CDN : HTTP_SERVER_CDN)."/templates/".CURRENT_TEMPLATE."/javascript/thickbox-".filemtime(DIR_FS_CATALOG."templates/".CURRENT_TEMPLATE."/javascript/thickbox.js").".js";
	$sURL['jquery-ui'] = str_replace("{i}", $iCDN['jquery-ui'], REQUEST_TYPE=='SSL' ? HTTPS_SERVER_CDN : HTTP_SERVER_CDN)."/templates/".CURRENT_TEMPLATE."/javascript/jquery-ui-".filemtime(DIR_FS_CATALOG."templates/".CURRENT_TEMPLATE."/javascript/jquery-ui.js").".js";
?>
 
<script src="<?php echo $sURL['jquery'] ?>" type="text/javascript"></script>
<script src="<?php echo $sURL['thickbox'] ?>" type="text/javascript"></script>
 
<?php // BOF - web28 - 2010-07-09 - TABS/ACCORDION in product_info ?>
<?php
if (strstr($PHP_SELF, FILENAME_PRODUCT_INFO )) {
?>
<script src="<?php echo $sURL['jquery-ui'] ?>" type="text/javascript"></script>
<script type="text/javascript">
/* <![CDATA[ */
	//Laden einer CSS Datei mit jquery
    $.get("<?php echo 'templates/'.CURRENT_TEMPLATE; ?>"+"/css/javascript.css", function(css) {
		$("head").append("<style type='text/css'>"+css+"<\/style>");
	});
 
	$(function() {
		$("#tabbed_product_info").tabs();
		$("#accordion_product_info").accordion({ autoHeight: false });
	});
/*]]>*/
</script>
<?php
}
?>
<?php // EOF - web28 - 2010-07-09 - TABS/ACCORDION in product_info ?>

xt:Commerce performance issues due to getAlsoPurchased()

Modern shopsystems, and for the sake of this post I’m counting the dated xt:Commerce 3 as ‘modern’, are full of features. Features you most likely never use. We, for instance, don’t use Tell-a-friend (as it’s not compatible with german law) or wishlists (as we never saw a need for it).

However, chances are good that those unused features turn out to be a bottleneck when it comes to performance. Especially when they are coded by someone who has heard the word ‘MySQL’ for the first time (i.e. those guys who wrote xt:Commerce 3).

At X-Skating we recently faced the problem that the front page loaded pretty good, the page for a single product however had almost one second till time to first byte (the .700 in the screenshot is better than most other tests we ran).
Read more

International Tracking Links

I’m currently building a new ERP for our company. To make life easier for our customer service they have direct links from within any order to the parcel tracking. We ship most of our items with DHL Germany. Other than with DHL Express the parcel will not be delivered with DHL in the destination country but with the local company instead. DHL provides us with a match code/UPU once the parcel reaches the destination country.

I already built a lot of icons for this but I wanted to improve this even more for our customer service. So where available I tracked down the URL for the Track&Trace in the destination countries – if available the direct link to the result page without any need for our people to put in the match code into another form and submit it.

I’d like to share this list, maybe it’s helpful to anybody else. If you have suggestions, improvements or want to add missing companies, just drop me a line.

This project on Github

Read more

Query Doctrine/Translatable for translated strings

I’m currently working on a new backend for all our onlineshops that will help us to streamline our workflow so we can get our lovely nutcrackers and cuckoo clocks to the customers even faster than ever before (it’s all about speed, no matter if in the browser or in the warehouse).

As I’m not doing that much backend development these days I gave both Zend Framework and Symfony a try. I went with Symfony (and Rock Hammer, but that’s a story for another day). So far this works out quite well. Symfonys documentation is great, everything (well, almost everything) works as expected and you save a lot of time because you can concentrate on your business logic and don’t have to think about the underlying things as database connections etc.

However, there is one thing with Doctrine and Translatable that took me almost two hours today to figure out.

I have a database table with countrienames in German, english names are stored with the help of Translatable in the default table ext_translations. What I tried to achieve was to get the ID of a country. The only information I got was the name in english:

$oCountry = $oEM->getRepository('W3LShopBundle:Country')->findOneByName($aOrder['countries_name']);

returns no hits. I searched the whole web for a solution as the documentation is not very helpful here. I came with up some solutions that set hints on the query, all of them returning exactly zero rows. I tried to write native SQL, however Doctrine messus up with the backslashes needed for object class. In the end, you get zero results with Doctrine while the same statement works fine in phpMyAdmin (you could work with a like statement on the object class field, but that’s a real performance hog).

Finally, I figured out that you need to set multiple hints.


$oQB = $this->getContainer()->get('doctrine')->getManager()->createQueryBuilder();
$oQuery = $oQB->select('c')->from('W3LShopBundle:Country', 'c')->where('c.name = :name')->getQuery()->setParameter('name', $sCountry);
$oQuery->setHint(\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER, 'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker');
$oQuery->setHint(\Gedmo\Translatable\TranslatableListener::HINT_TRANSLATABLE_LOCALE, 'en_US');
$aCustomerCountry = $oQuery->getResult();

This finally returns results. Maybe this is helpful for anybody else.

So this is a relaunch

So this is a relaunch. But not only the look and feel of this blog changed, other changes are coming along. The old version had the claim „Aus dem Alltag eines Halbtagsselbständigen” („From the everday life of a part-time self-employed”). This claim is gone because it is no longer true. I quit my other job effective April, 30th and I’m now solely selling cuckoo clocks for a living.

This blog is now responsive. As I hadn’t had the time to do it on my own I used the Tatami-Theme by Elma Studio. There is a lot of good stuff in this template and you can learn a lot from just using it. Of course there are some things I don’t like, too. But I guess that’s the price you have to pay when you use something off-the-shelf.

I also want to use this relaunch to get a consistent line regarding my blogs. So this blog will focus on work-related things and will be in english most of the time. Thanks to the Tatami-Theme I can now also post quotes, links and videos in a more tumblelog-style. Blogpotato will become (or remain) the geek stuff blog in german and I will continue to blog about becoming a vegan on Ist mir egal, ich ess das jetzt einfach (also in german, the URL translates to „I don’t care, I just eat this now”). And, if this wouldn’t be enough: I bought a flat lately which is currently under construction. Thoughts on this subject (in german) will be published on K12.

Viel heiße Luft

Es gibt ja auch noch andere Shopbetreiber die bloggen. Und zumindest so tun, als würden sie der Community was zurückgeben.

Natürlich gilt es Chancengleichheit für alle Kollegen zu gewährleisten: wer die Präsentation ebenfalls haben möchte, schreibt mir einfach eine mail

Habe ich gemacht. Mehrmals. Und der Kollege auch. Reaktion: Keine. Auch Kommentare im ‘Blog’ werden ignoriert, indem sie einfach nicht freigeschaltet werden.

Da bleibt mir nur zu sagen: Put your money where your mouth is. Oder einfach keine leeren Versprechungen abgeben.

Der schnellste Erzgebirge-Palast, den es je gab.

Schnell ist der Erzgebirge-Palast ja eigentlich schon immer. Also spätestens seit Ende 2008, wo zum ersten Mal der Server etwas geschwächelt hat und ich viel optimiert habe. Seit dieser Zeit kommen auch die statischen Inhalte größtenteils von Amazons Cloudfront CDN. In den letzten Jahren hat es der Shop mit diesen Optimierungen in Google’s PageSpeed auf einen Wert von 95 (100 ist der Maximalwert) gebracht.

Ich habe auch viel dazu geschrieben, wie man mit einfachen Mitteln ähnliche Werte erreichen kann. Was ich nie erwähnt habe ist die Sache mit dem CDN, weil das schon eher Highlevel ist und weil mein bisheriger Workflow, Cloudfront war damals quasi frisch geschlüpft und konnte noch nicht wirklich viel, doch sehr umständlich ist:
Inhalte mussten auf Amazon S3 liegen, was man zwar mit einem Tool wie s3sync mehr oder weniger automatisieren kann, aber aufwendig ist es trotzdem. Und da ich lange Expire-Header nutze musste ich beispielsweise dem CSS immer ein Datum mitgeben, das ganze syncen und schließlich die Referenz im <head> auch noch anpassen. Nichts, was man irgendwem raten möchte, nachzubauen.

Beim Blättern in den Folien von Christian Schäfer kam mir dann der Gedanke, dass ich mir ja grad mal das PageSpeed-Resultat vom Shop anzeigen lassen könnte. Weiterhin 95, aber ein Punkt tauchte dort auf, der mich eigentlich schon immer nervt, seit ich auf das CDN umgestellt habe: Die CSS-Datei ist nicht gzip-komprimiert. Und wir reden hier von 65kB vs. 12kB! Aus dem Grund hatte ich da auch schon mal versucht, was zu bauen, was aber nie wirklich funktioniert hat (Datei bereits gezippt ablegen und mit entsprechendem Zusatzheader ausliefern). Kurz gegoogelt und darauf gestoßen, dass Cloudfront schon seit einiger Zeit ‘custom origin’ unterstützt. Das hat zwei Vorteile:

  1. Die Inhalte müssen nicht mehr wie bisher auf S3 liegen sondern können von einem beliebigen Webserver kommen.
  2. Header, die der Original-Webserver schickt, werden von Cloudfront genau so weitergegeben.

Man braucht dazu nur zwei Dinge:
Eine Domain, die cookieless arbeitet. Also einen vhost einrichten, der das selbe DocumentRoot hat wie der eigentliche Webauftritt und diesem sagen, dass er sämtliche Cookies verwerfen soll: Header unset Cookie.
Mit dieser Domain, nennen wir sie mal static.example.org, legen wir jetzt eine neue Distribution in Cloudfront an, geben an, dass die Header so übernommen werden sollen, wie sie von der Quelle kommen, warten einen Moment, bis das alles deployed ist und können das ganze dann wie bisher nutzen. Statt von S3 holt Cloudfront jetzt aber die Daten von unserem Server. Das passiert auch nur genau beim ersten Request, denn ab dann hat Amazon das ganze ja im Cache.

Damit kann man Cloudfront nicht nur mit gzip nutzen, sondern sogar mit mod_rewrite, denn die eigentlichen RewriteRules werden ja auf dem eigenen Server ausgeführt. Das eröffnet völlig neue Möglichkeiten und so hat mein CSS keinen von Hand eingetragen Datumsteil mehr, sondern einen dynamisch ausgelesenen Zeitstempel des letzten Modifikationsdatums:
http://cdn6.wstatic.com/templates/erzgebirge/styles-yui-min-1350656989.css

Wer sich übrigens dafür interessiert, wie ich das für xt:Commerce umgesetzt habe, dass die statischen Inhalte über Cloudfront ausgeliefert werden, möge dies bitte in den Kommentaren kundtun. Dann kann ich das auch mal in einem Artikel entsprechend aufbereiten.

Was das ganze gebracht hat sind zwei weitere Punkte auf der Skala: 97 von 100. Der Rest liegt nicht mehr wirklich in meiner Macht, außer, ich würde Google Analytics, olark und die SSL-Logos rauswerfen.

Ein Paypal-Account, mehrere Shops, verschiedene Logos

Ein Paypal-Account, mehrere Shops, verschiedene Logos

Der Titel ist zugegebenermaßen etwas sperrig. Bei Elmastudio ist gestern ein Artikel erschienen, wie man die Paypal-Bezahlzeite etwas individualisieren kann.
Hat man jetzt mehrere Shops auf einem Paypal-Account laufen muss man sich aber nicht zwingend für das Logo der ‘Dachgesellschaft’ entscheiden, das der Kunde vermutlich gar nicht kennt und eher vorsichtig skeptisch reagiert. Denn man kann für das Paypal Standard Payment über entsprechende Parameter das gewünschte Logo mitgeben:

cpp_header_image
ist die max. 750×90 Pixel große Kopfgrafik
cpp_logo_image
ist das max. 190×60 Pixel große Logo

Bei cpp_logo_image muss man darauf achten, dass die übergebene URL maximal 127 Zeichen lang sein darf. Wer also bereits http://www.verein-zur-erhaltung-historischer-feuerwehrfahrzeuge-und-geraete.de/ 1 als Domain hat sollte sich bei Pfad und Bildnamen eher kurz halten.

Und so sieht das dann aus:




1 Wer meint, den Fehler gefunden zu haben, kann sich ja mal in den Kommentaren melden. Ich überleg mir was schönes.

Umziehen Part 4: Richtiges Zertifikat für den Proxy

Beim Umzug des Erzgebirge-Palastes ist noch etwas aufgefallen, das ich bislang im Rahmen der Umzugsservice-Reihe noch nicht behandelt hatte:

Wenn die umzuziehende Seite (auch) unter https erreichbar ist muss der Proxy ebenfalls mit dem richtigen Zertifikat ausgestatt sein. Glücklich, wer noch eine IPv4-Adresse frei hat. Ansonsten hagelt es Fehlermeldungen. Bedenken sollte man auch, dass es hinter dem Proxy unverschlüsselt weitergeht, ein solches Vorgehen ist also nur wirklich angebracht, wenn man sich anschließend in einer vertrauenswürdigen Umgebung bewegt.