Stocker et manipuler les ressources distantes avec système de cache

  • Par Magentix le 17/01/2010
  • Difficulté : 3/4

Stocker et manipuler les ressources distantes avec système de cache Stocker l'intégralité des ressources externes (images, flux, scripts...) sur son serveur lorsque cela est possible est une bonne habitude à prendre, facile à mettre en place. A titre d'exemple nous allons voir comment mettre en cache le script Google Analytics sur son propre serveur avec Magento.

Stocker sur son serveur les ressources distantes permet de gérer à sa convenance les en-têtes des documents, de réduire les DNS lookup et de ne pas dépendre de la charge du serveur qui héberge les fichiers dont nous avons besoin. Cette technique est d'ailleurs souvent préconisée par les outils d'analyse de performance de site.

Ces ressources externes sont bien entendus amenées à évoluer, parfois rapidement, notamment pour les flux d'actualités. L'objectif est donc de les stocker à intervalles réguliers sur le serveur avec un système de cache.

La fonction suivante va conserver dans un répertoire donné un fichier accessible depuis une URL spécifique :

Fonction cacheFile

function cacheFile($url,$age,$cacheDir,$name='') {
        $filename = strlen($name) ? $cacheDir.'/'.$name : $cacheDir.'/'.md5($url);
        $fetch = true;

        if(file_exists($filename)) $fetch = (filemtime($filename) < (time()-$age));

        if($fetch) {
                exec('wget -N -O '.$filename." \"".$url."\"");
                exec('touch '.$filename);
        }
        return $filename;
}

Par exemple, pour manipuler après mise en cache les données contenues dans le fichier flux.xml accessible à l'adresse http://www.site.com/flux.xml :

Manipuler un flux en cache

$file = cacheFile('http://www.site.com/flux.xml',86400,'/cache','flux.xml');
$_xml = simplexml_load_file($file);

Dans cet exemple le fichier flux.xml est stocké dans le dossier cache à la racine puis mis à jour sur le serveur toutes les 24 heures (86400 secondes).

NB : l'option allow_url_fopen doit être activée depuis le php.ini (Plus d'informations) :

php.ini

; Whether to allow the treatment of URLs (like http:// or ftp://) as files.
allow_url_fopen = On

Mettre en cache le script Google Analytics sur Magento

Pour un cas plus concret et avec Magento, nous allons appliquer l'astuce n°1 de l'article Top 3 Speed Tips for Sites using Google Analytics du site AskApache, c'est à dire stocker le script ga.js de Google Analytics sur le serveur (et éviter le très courant "En attente de google-analytics.com").

Pour cela il va falloir effectuer une petite surcharge du module GoogleAnalytics de Magento. Google Analytics s'active depuis le menu Système > Configuration > Ventes > API Google > Google Analytics.

Google Analytics

Architecture du module

  • app/code/local/Magentix/CacheAnalytics/Block/
  • Ga.php
  • app/code/local/Magentix/CacheAnalytics/etc/
  • config.xml
  • app/etc/modules/
  • Magentix_CacheAnalytics.xml

Développement du module

Depuis Magento 1.4, la version asynchrone d'Analytics est implémenté. Le module se base sur la version 1.3. Il est nécessaire de l'adapater pour un magento supérieur à 1.4.

Nous allons adapter la fonction cacheFile spécifiquement pour la mise en cache du script ga.js d'Analytics, avec les méthodes offertes par l'API de Magento :

Fonction cacheAnalytics

private function cacheAnalytics() {
        $protocol = Mage::app()->getStore()->isCurrentlySecure();
        $dir = Mage::getBaseDir('media').DS.'analytics';
        
        $gaJsHost = $protocol == 'https' ? 'https://ssl.' : 'http://www.';
        $url = $gaJsHost.'google-analytics.com/ga.js';
                
        $filename = $protocol == 'https' ? 'ga-2.js' : 'ga-1.js';
        $file = $dir.DS.$filename;
                
        $fetch = true;
                
        if(realpath($file)) $fetch = (filemtime($file) < (time()-86400));

        if($fetch) {
                $ioAdapter = new Varien_Io_File();
                $ioAdapter->setAllowCreateFolders(true);
                $ioAdapter->checkAndCreateFolder($dir);
                $ioAdapter->cp($url,$file);
        }
        return $this->getUrl('media/analytics').$filename;
}

Le fichier ga-1.js (ou ga-2.js selon le protocole utilisé) sera enregistré dans le dossier media/analytics, et mis à jour toutes les 24 heures. On utilisera ce fichier dans le code d'Analytics au lieu du ga.js accessible sur le site de google-analytics.com.

Finallement, la classe Magentix_CacheAnalytics_Block_Ga contiendra le code suivant :

app/code/local/Magentix/CacheAnalytics/Block/Ga.php

<?php

class Magentix_CacheAnalytics_Block_Ga extends Mage_GoogleAnalytics_Block_Ga {

        protected function _toHtml() {
                if (!Mage::getStoreConfigFlag('google/analytics/active')) {
                        return '';
                }
                
                $this->addText('<-- BEGIN GOOGLE ANALYTICS CODE -->
<script type="text/javascript">
//<![CDATA[
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src=\''.$this->cacheAnalytics().'\' type=\'text/javascript\'%3E%3C/script%3E"));
//]]>
</script>
<script type="text/javascript">
//<![CDATA[
var pageTracker = _gat._getTracker("' . $this->getAccount() . '");
pageTracker._trackPageview("'.$this->getPageName().'");
//]]>
</script>
<-- END GOOGLE ANALYTICS CODE -->
                ');

                $this->addText($this->getQuoteOrdersHtml());

                if ($this->getGoogleCheckout()) {
                        $protocol = Mage::app()->getStore()->isCurrentlySecure() ? 'https' : 'http';
                        $this->addText('<script src="'.$protocol.'://checkout.google.com/files/digital/ga_post.js" type="text/javascript"></script>');
                }

                return Mage_Core_Block_Text::_toHtml();
        }
        
        private function cacheAnalytics() {
                $protocol = Mage::app()->getStore()->isCurrentlySecure();
                $dir = Mage::getBaseDir('media').DS.'analytics';
        
                $gaJsHost = $protocol == 'https' ? 'https://ssl.' : 'http://www.';
                $url = $gaJsHost.'google-analytics.com/ga.js';
                
                $filename = $protocol == 'https' ? 'ga-2.js' : 'ga-1.js';
                $file = $dir.DS.$filename;
                
                $fetch = true;
                
                if(realpath($file)) $fetch = (filemtime($file) < (time()-86400));

                if($fetch) {
                        $ioAdapter = new Varien_Io_File();
                        $ioAdapter->setAllowCreateFolders(true);
                        $ioAdapter->checkAndCreateFolder($dir);
                        $ioAdapter->cp($url,$file);
                }
                return $this->getUrl('media/analytics').$filename;
        }
}

Nottez l'appel à la fonction _toHtml() de la classe Mage_Core_Block_Text et non de la classe parente Mage_GoogleAnalytics_Block_Ga pour éviter la duplication du code dans la page (et l'inutilité de la surcharge).

Il ne reste plus qu'à configurer le module et à le déclarer :

app/code/local/Magentix/CacheAnalytics/etc/config.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Magentix_CacheAnalytics>
            <version>1.0</version>
        </Magentix_CacheAnalytics>
    </modules>
    <global>
        <blocks>
            <googleanalytics>
                <rewrite>
                    <ga>Magentix_CacheAnalytics_Block_Ga</ga>
                </rewrite>
            </googleanalytics>
        </blocks>
    </global>
</config>

app/etc/modules/Magentix_CacheAnalytics.xml

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

Reste à vérifier que la compression gzip est appliquée sur le fichier, et l'activer si ce n'est pas le cas.

8

Commentez cet article Stocker et manipuler les ressources distantes avec système de cache

T Le 20/06/2010 à 23:36
Très intéressant. Merci !
#1
Comte Le 20/06/2010 à 23:36
Super article :-)

j'ai une question concernant le tag e-commerce de la page checkout/succes.phtml qui remonte à analytics pour les stats des ventes,
il y a un problème avec paypal quant un client fait un retour chez le marchand quant il n'a pas validé sont paiement !
j'ai constater ce problème de façon récurrente par exemple, pour 1 vente enregistre dans magento le client est revenue 2 fois sans régler et la troisième était la bonne "qui ne la pas vécue ;-)" donc 3 commande mais 1 validé, quant je consulte mes stats analytics j'ai 3 transaction avec les numero de commande, mais en faite il y en a qu'une !
donc les tag analytics sont remonter 3 fois à google !
1er hypothèse: quant le client revient sur magento les tag sont renseigner et remonte à google analytics (mais pourquoi ?), ce qui fausse les statistique !
2ieme hypothèse le client revient après le paiement sur la page checkout/succes est y revient plusieurs fois dessus ou actualise la page!

je sais pas si je suis claire dans mes explication, mais si vous avez une solution à ce problème ce serait vraiment top ;-)
Cordialement Laurent
#2
Magentix Le 20/06/2010 à 23:37
Il va falloir se pencher un peu sur le fonctionnement du module Google Analytics ;)

Le script Analytics est contenu dans un bloc nommé google_analytics, enfant de la référence before_body_end (voir fichier layout/googleanalytics.xml).
Ce bloc est affiché sur l'intégralité des pages. Son contenu peut varier, et est dicté par la fonction _toHtml() de la classe Mage_GoogleAnalytics_Block_Ga.
Si Analytics n'est pas activé la fonction ne retourne rien, sinon elle affiche le script :

if (!Mage::getStoreConfigFlag('google/analytics/active')) {
return '';
}
/* ... */


Jusque là rien de compliqué ;)

Les méthodes de l'API e-commerce d'Analytics sont générées à une condition : qu'un objet Mage_Sales_Model_Quote du bloc ait été instancié. Or celui-ci ne l'est qu'au moment où l'événement checkout_onepage_controller_success_action est lancé. La méthode order_success_page_view de l'observer du module se charge de générer l'objet et de l'associer au bloc :

/* ... */
$quote = Mage::getModel('sales/quote')->load($quoteId);
$analyticsBlock->setQuote($quote);
/* ... */


A partir de là les méthodes de l'API Google peuvent être générées dans la page.

Donc dans ton cas de figure, l'événement checkout_onepage_controller_success_action serait appelé que le paiement soit validé ou non, ce qui est quand même assez étrange, voir impossible...
Cet événement ne peut intervenir que si le paiement est validé, c'est à dire quand le contrôleur entre en action avec la méthode successAction().

J'éliminerai la première hypothèse... Si le client annule son paiement sur PayPal ou qu'il revient sur le site directement l'événement ne peut se produire.
Pour la deuxième hypothèse, le client ne peut pas actualiser la page, et même si cela était possible, les données envoyées devraient toujours être celles de la dernière commande enregistrée, j'élimine également.

Donc une 3ème hypothèse se dessine et que je pense plus logique...

Les 3 commandes sont associées au même "QuoteId" récupéré depuis la session de l'utilisateur.

Une "quote" pour faire simple, représente le panier du client. Une fois la commande validée, cette "quote" est transformée en commande, avec un ID qui lui est propre. Dans mon hypothèse, à chaque fois qu'une commande est générée, elle est associé à la même quote.
Lorsque Magento va générer les méthodes de l'API Google, il récupère une collection de commandes associées à cette "quote" :

$orders = Mage::getResourceModel('sales/order_collection')
->addAttributeToFilter('quote_id', $quoteId)
->load();
$html = '';
foreach ($orders as $order) {
$html .= $this->setOrder($order)->getOrderHtml();
}


Pour chacune des ces commandes il ajoute le code Analytics... Donc si le client dans la même session a passé 10 commandes sans jamais les payer, et qu'il finit par régler la 11ème, le code est généré pour les 11 commandes et les données sont envoyées à Analytics...

Si cette hypothèse se révèle vrai c'est un petit soucis qu'il faudrait corriger... Certains clients ont des comportement parfois étranges.

Pour corriger je surchargerai la méthode getQuoteOrdersHtml() du bloc (le même que dans l'article) avec quelque chose qui ressemblerai à çà :

/* ... */
$orders = Mage::getResourceModel('sales/order_collection')
->addAttributeToFilter('quote_id', $quoteId)
->load();
$html = '';
$i = 1;
$nbOrders = count($orders);
foreach ($orders as $order) {
if($i == $nbOrders) $html .= $this->setOrder($order)->getOrderHtml();
$i++;
}
/* ... */


J'espère avoir pu t'aider un peu, en tout cas je pense que tu as mis le doigt sur un problème de Magento ;)
#3
Magentix Le 20/06/2010 à 23:38
Le mieux serait que tu puisse tester : passer une commande, une fois sur l'interface PayPal revenir sur le site (sans annuler) en indiquant l'URL de la boutique dans la barre d'adresse. Repasser la même commande une nouvelle fois mais cette fois ci en allant jusqu'au bout et en payant.

Une fois sur la page success, vérifier le code du script Analytics.
#4
Fabrice Le 20/06/2010 à 23:39
Je suis tombé sur ton article en faisant une recherche sur le tracking des commandes. Je vais être un peu hors sujet quoique ..

Volià, je cherche à intégrer un javascript de tracking des commandes pour le comparateur Twenga. Cela n'a rien de compliqué en soi, il faut récupérer obligatoirement le total HT de la commande sans les frais de port (facile de récupérer le subtotal d'un quote) et au choix, l'order ID ou l'email du client ou le customer ID)

J'y parviens aisément, si je place le script dans success.phtml (ce que la logique voudrais), mais c'est là que ça se complique, Twenga souhaite que ce script soit placé juste avant la redirection vers le paiement, car tout les moyens de paiement ne renvoient pas automatiquement vers la page success. C'est souvent un lien de retour vers la boutique, lien sur lequel un client ne clique pas automatiquement après avoir payé.

Je précise que Twenga fournit un back office permettant n'annuler les commandes qui ne donneront pas lieu à une commission si le client n'a pas confirmé la commande ou si le paiement à échoué.

Je pense que je dois placer le script sur la page review.phtml (du moins je crois) , mais là, récupérer l'order ID est impossible, puisque d'après ce que tu écris, l'order ID n'est enregistré qu'après confirmation de la commande.
Qu'à cela ne tienne puisque l'email du client ou son ID peuvent être transmis à la place.

Là encore cela n'a rien de compliqué pour moi, si et seulement si le client est enregistré....
Le problème vient des client qui passent commande en tant qu'invité. Ma question est donc :
Ou / comment récupérer l'adresse email du client qui passe commande en tant qu'invité ?
#5
Pierre Le 20/06/2010 à 23:40
Bonjour et merci pour votre article.

J'ai réussi à mettre en pratique le cache du fichier ga.js mais j'ai toujours un fichier utm.gif chargé depuis le site de google analytics ( utm.gif ) Y a t il moyen d'empêcher cet appel externe ?

Cordialement,
Pierre
#6
Comte Le 20/06/2010 à 23:40
Bonjour et félicitation pour ce correctif que je viens de mettre en place ici app/code/local/Mage/GoogleAnalytics/Block/ histoire qu'il ce fasse pas écraser à la prochaine mise à jour ;-)
Je suis impressionner par votre analyse et excellente maitrise de magento, je vais suivre l'évolution de ce tracking de plus près et vous tiens au courant.

Cordialement Laurent
#7
Magentix Le 20/06/2010 à 23:43
@Pierre : pour le fichier utm il n'y a aucun moyen de le supprimer.
#8
Rédiger un commentaire

Cliquez pour générer un nouveau code

* champs obligatoires