Avec Gentoo et les ebuilds, rien de plus facile que de tester de nouvelles versions.

Installation de PHP 7.1

Voici la manipulation rapide :

  • créez votre overlay local (si ce n’est pas déjà fait !) ;
  • créez le répertoire dev-php/php et mettez-y le dossier files/ que vous trouverez dans le /usr/portage/dev-php/php ;
  • copiez également ce ficher (j’ai juste modifier le nom et l’url de téléchargement présents dans le fichier php-7.0.7.ebuild) : wget -P . http://cyril.me/dl/php-7.1.0_alpha1.ebuild
  • tapez la commande ebuild php-7.1.0_alpha1.ebuild digest merge.

Configuration de FPM

Modifiez le fichier php-fpm.ini de php7.1 en apportant les modifications suivantes (faîtes attention au listen également si vous faîtes tourner plusieurs fpm) :

  1. pid = run/php-fpm-php7.1.pid

Ensuite, créez le fichier dans init.d de pour cette version :

ln -s php-fpm /etc/init.d/php-fpm-php7.1

et c’est parti !

/etc/init.d/php-fpm-php7.1 start

php7.1.0_alpha1

Alors voilà le topo : j’ai une table qui compte mes visiteurs en fonction de leur source :

colonne type index
date DATE PRIMARY
source VARCHAR(50) PRIMARY
hits INT

A chaque visite, j’incrémente le nombre de hits en fonction de la date et de la source. L’approche naïve, c’est d’aller chercher l’objet, de le créer au besoin, de l’incrémenter, et de le sauver. Le principal problème de ce genre de fonctionnement est la gestion de la concurrence : que se passe-t-il si j’ai deux visiteurs qui arrivent au même moment sur mes serveurs ? Je vais sans doute avoir une erreur…

Avant, j’utilisait INSERT … ON DUPLICATE KEY … :

  1. INSERT INTO
  2.   my_table
  3.   (`date`, `source`, `hits`)
  4. VALUES
  5.   (CURDATE(), 'referer', 1)
  6. ON DUPLICATE KEY UPDATE
  7.   hits = hits + 1

Mais ça, c’était avant Doctrine…

Doctrine est un ORM…

… compatible avec beaucoup de moteurs de bases de données, du coup pas possible d’utiliser certaines spécificités du langage à moins de

  • utiliser des requêtes natives ;
  • étendre Doctrine pour faire ce que vous voulez que ça fasse, par exemple rajouter des types.

Bref, c’est du boulot ! Alors si on peut trouver une petite solution de contournement…

Notre problème est double :

  1. Pas de primary sur des champs DATE avec Doctrine : peut être contourné avec un type particulier ;
  2. Pas de INSERT ON DUPLICATE KEY UPDATE : peut être contourné en utilisant une requête native.

Mais bon, si on utilise un ORM, c’est parce qu’on préfère gérer des entités et du DQL 🙂

Utiliser Doctrine, son Entity Manager et le DQL

L’approche que je propose n’est pas des plus optimisées par rapport à la requête d’origine, mais a le mérite d’être compatible DQL.

1ère étape : Modification de l’entité

On préfère travailler avec des entités avec des IDs, on sait jamais ce qu’on va en faire, alors mieux vaut prévoir le coup ! Et même si Doctrine ne gère pas les dates en PRIMARY, ça gère les dates en Unique 🙂

colonne type index
id INT PRIMARY
date DATE UNIQUE(date_source)
source VARCHAR(50) UNIQUE(date_source)
hits INT

Notez que la contrainte unique est sur les deux colonnes date et source.

2ème étape : Traduction de l’INSERT .. UPDATE

Elle se décompose en trois étapes :

  1. on incrémente la ligne qui correspond à la date et la source ;
  2. si aucun enregistrement n’a été mis à jour, on crée une nouvelle ligne dans la base ;
  3. si au moment de l’écriture, ça génère une erreur de duplicate key (sur la UniqueConstraint), alors on relance l’incrément qui cette fois-ci devrait fonctionner 🙂

On se retrouve avec 3 cas :

  1. La ligne éxiste déjà (majorité des cas dans mon contexte) : alors on fait une seule requête, comme avant ;
  2. La ligne n’éxiste pas : on la crée et le INSERT se passe correctement : on a fait 2 requêtes, mais qu’une seule fois par jour et par source ;
  3. La ligne n’éxistait pas, mais a été créée entre temps par un autre visiteurs : on incrémente cette ligne. On a fait 3 requêtes, mais comme avant ça n’arrive qu’une seule fois par jour et par source.

Exemple de code :

  1. <?php
  2.  
  3. namespace Tests\Model;
  4.  
  5. class HitsManager
  6. {
  7.     /** @var EntityManager */
  8.     protected $em;
  9.  
  10.     /**
  11.      * Executes the update statement
  12.      * @param  \DateTime $date   The date
  13.      * @param  string    $source The source value
  14.      * @return integer           1 if successful (thanks to unique key), 0 otherwise
  15.      */
  16.     protected function doInc($date, $source)
  17.     {
  18.         return $em
  19.             ->createQuery('
  20.                UPDATE \Tests\Entity\Hits h
  21.                SET h.hits = h.hits + 1
  22.                WHERE h.date = :date AND h.source = :source
  23.            ')
  24.             ->setParameter('date', $date)
  25.             ->setparameter('source', $source)
  26.             ->execute();
  27.     }
  28.  
  29.     /**
  30.      * Executes the insert ... on duplicate key update
  31.      * @param  \DateTime $date   The date
  32.      * @param  string    $source The source value
  33.      * @return boolean           True if successful, false otherwise
  34.      */
  35.     public function inc(\DateTime $date, $source)
  36.     {
  37.         // Try to increment
  38.         if (0 < $this->doInc($date, $source)) {
  39.             // We were successful
  40.             return true;
  41.         }
  42.  
  43.         // Failed to increment, try to create new one
  44.         $hit = new \Tests\Entity\Hits();
  45.         $hit->setDate($date);
  46.         $hit->setSource($source);
  47.         $hit->setHits(1);
  48.         try {
  49.             $this->em->persist($hit);
  50.             // Flush to generate \Exception if failure !
  51.             $this->em->flush();
  52.  
  53.             return true;
  54.         } catch (\Doctrine\DBAL\DBALException $e) {
  55.             // _One reason_ might be we have duplicate key
  56.             return (boolean) $this->doInc($date, $source);
  57.         }
  58.     }
  59. }
  60.  

Avec les nouveaux outils développeurs des navigateurs, il est désormais possible de faire du debug sur des fichiers JS et CSS compilés à partir de Coffee Script, LESS ou SCSS, en particulier sur Firefox et Chrome.

Les sources maps

Les sources maps sont des informations sur les fichiers générés, qui permettent de lier une instruction compilée avec l’endroit ou elle a été définie. Le format est bien complexe, mais les outils que nous utilisons nous permettent aisément de générés ces sources maps automatiquement, en rajoutant des options à la compilation.

Les sources maps et assetic

Le filtre assetic LESS peut prendre des options de compilation, qui sont distinguées en deux groupes : les options qui permettent de transformer le code source LESS en arbre (parser : représentation informatique du code source), puis de transformer cet arbre en fichier CSS (dumper). Ce qui nous intéresse ici, c’est de passer les bonnes options au moment du dump pour dire à LESS de dumper également les sources-maps.

Un coup d’oeil au fichier lessc nous permet de trouver les options à passer au dumper en fonction des options que prend lessc.

  1. $filter = new Filter\Less();
  2. $filter->addTreeOption('outputSourceFiles', true);
  3. $filter->addTreeOption('sourceMap'', true);

Avec ces options, le compilateur less va inclure les fichiers sources dans les fichiers compilés.

Intégration avec Symfony 2

Il suffit juste d’ajouter ces options au service « Filtre LESS » de symfony 2 de l’assetic-bundle.

Modification du Bundle :

  1. // \Acme\Demo\AcmeDemoBundle
  2. public function build(ContainerInterface $container)
  3. {
  4.     parent::build($container);
  5.     $container->addCompilerPass(new Compiler\LessSourceMapPass());
  6. }
  7.  

Ajout d’une nouvelle passe de compilation :

  1. // \Acme\Demo\DependencyInjection\Compiler\LessSourceMapPass
  2.  
  3. class LessSourceMapPass implements CompilerPassInterface
  4. {
  5.     public function process(ContainerBuilder $container)
  6.     {
  7.         // Activation uniquement en mode debug
  8.         if ($container->getParameter('kernel.debug')) {
  9.             $lessAsseticFilter = $container->getDefinition('assetic.filter.less');
  10.             // Ajout des options au dumper
  11.             $lessAsseticFilter->addMethodCall('addTreeOption', array('outputSourceFiles', true));
  12.             $lessAsseticFilter->addMethodCall('addTreeOption', array('sourceMap', true));
  13.         }
  14.     }
  15. }
  16.  

iGraal a développé un bundle pour répondre à la question suivante : comment pouvoir utiliser les extensions et filtres twig dans un fichier javascript, qui sera lui-même passé dans la moulinette d’assetic ?

Expression du besoin

iGraal a une application mobile compatible Android et iOS. iGraal souhaite utiliser le cache varnish pour son site, mais certains traitements traînent dans les request listeners, dont le test de compatibilité mobile. Hors, il faudrait éviter de faire un appel à toute la couche Symfony juste pour détecter qu’un utilisateur est mobile ou non. Une solution qui fonctionne très bien est de passer ce test en javascript.

Le rôle de ce javascript est donc 1) la détection du device utilisé par la personne (iOS / Android), 2) la décision de l’affichage (via cookies), 3) la redirection vers une URL donnée et 4) la possibilité d’inclure ce JS dans notre JS global déjà compilé par assetic.

Le filtre assetic Twig

Nous voulions donc utiliser :

  1. Assetic pour inclure ce JS dans notre JS global
  2. Utiliser la fonction twig path pour trouver vers quelle URL rediriger notre internaute

Un bundle a donc été créé : https://github.com/igraal/twig-assetic-filter-bundle

Au début, c’était pas vraiment un bundle, juste un bout de code, mais autant partager !

Ce bundle permet d’utiliser un filtre « twig » pour inclure des JS ou des CSS.

Exemple d’utilisation

Fichier HTML

  1. <DOCTYPE html>
  2. <html>
  3.   <head>
  4.     <% javascripts
  5.       '@AcmeWhateverBundle/Resources/js/main.js'
  6.       '@AcmeWhateverBundle/Resources/js/feature.twig.js'
  7.       filter="twig" %>
  8.     <script type="text/javascript" src="{{ asset_url }}" async defer />
  9.     <% endjavascripts %>
  10.   </head>
  11. ...
  12. </html>

Fichier JS

  1. if (isMobile()) window.location.replace('{{ path('www_mobile') }}');

 

Principales utilisations

Utilisation du filtre :

  1. Utiliser les routing dans du javascript (comme le montre l’exemple)
  2. Utiliser des traductions dans du javascript, surtout utile lors de validation de form en JS, dans tous les cas il y a l’ajax 😉
  3. Utiliser vos propres extensions pour du JS, par exemple pour faciliter le codage ou autre. On pourrait très bien imaginer une fonction js_debug qui fait un console.debug en javascript en dev, rien en prod.

 

Lorsqu’on parle de haute disponibilité, on parle de load-balancer, de plusieurs serveurs frontaux, qui tournent sur le même code grâce à la mise en production d’un code versionné, et sur des bases de données qui ont elles-mêmes leur propre stratégie pour la haute disponibilité.

Le problème est de servir des fichiers qui ne sont pas versionnés, mais plutôt uploadés (par des utilisateurs, ou dans mon cas des administrateurs, un peu comme un blog), surtout quand ça passe par une interface admin. Quelle stratégie adopter ? Les stratégies que je vais présenter ont l’avantage d’être relativement simple et rapide à mettre en place, et conviennent tout à fait (jusqu’à un certain point qu’on serait tous content de franchir !).

Stratégie 1 : stockage en base de données

La première stratégie consiste à stocker ces fichiers dynamiques dans une base de données. La base de données a sa stratégie de réplication, de sauvegarde, donc c’est un endroit sûr où stocker ses données.

Ensuite, il y a deux possibilités pour servir ces fichiers :

  • si la demande n’est pas trop importante, et que les fichiers sont plutôt statiques (ou si on peu se satisfaire d’une ancienne version pendant un temps), alors (ou je sais c’est moche) on peut les servir directement via la base de données.
  • si la demande est plus importante, alors on peut aller chercher via cron les fichiers modifiés dans la base de données à interval régulier, et les stocker sur le filesystem de la machine qui va les servir

Stratégie 2 : stockage directement en système de fichiers

L’autre possibilité est d’avoir donc un serveur qui héberge ces fichiers. On met en place un serveur FTP sur une machine. Ensuite, dès qu’un fichier est uploadé, supprimé ou modifié, alors on envoie le fichier sur les frontaux qui s’occupent de les servir. Côté soft, on peut utiliser PROFTPD qui supporte parfaitement ce genre d’opération.

Histoire de pallier d’éventuels problèmes de réseau au moment de la copie, on peut aussi utiliser RSYNC pour synchroniser les fichiers de temps à autres.

Et si le serveur FTP tombe en panne ? On peut utiliser un pacemaker pour basculer l’IP du FTP vers le serveur de backup 🙂

Pas de doute, le Responsive Web Design est à la mode. En même temps, pourquoi faire plusieurs site web à maintenir alors qu’un seul pourrait suffir ?

Se pose donc la question, pour les entreprises pour lesquelles le référencement naturel est important, de l’opportunité de passer en mode responsive.

Mobiles/Tablettes/Desktop : mêmes problématiques ?

1. Mon service est-il le même ?

L’une des questions qu’il faut se poser en premier : vais-je proposer le même service sur tablettes/mobiles et sur desktop ? Ou bien est-ce un service différent ? Pour un site de contenu, on proposera bien évidemment le même service, à savoir du contenu. Pour un site marchand, là encore le but est de vendre. Et dans certains cas la réponse va être différente. Si les utilisateurs n’ont pas le même usage, ou les mêmes intérêts sur mobile et sur desktop, un site dédié pourrait être mieux adapté. Dans l’exemple du site d’une banque, on mettra beaucoup plus en avant l’accès au compte, ou la localisation des agences que les services proposés par la banque.

2. Ma stratégie SEO est-elle du coup la même ?

Le principe fondamental du Responsive est donc que c’est le même contenu qui est proposé aux moteurs de recherches. Si la stratégie est différente, parce que les services sont différents, alors un site dédié pourrait être mieux adapté.

Responsive : c’est trop bien…

Nous avons déjà vu l’un des avantages : développer à moindre un coup à la fois un site desktop, mobile et tablette.

Par ailleurs, on peut aussi du coup s’affranchir des ennuyantes popup et autres headers où l’on nous propose de basculer vers une version mobile, en perdant du coup plus d’une fois sur deux l’information que nous sommes venus chercher.

Et côté SEO, ce sont les mêmes pages, le même contenu, pas besoin de se soucier du duplicate, et aussi un seul site à gérer.

… mais pas forcément tout le temps !

Le responsive est une technologie supportée par les mobiles et tablettes. Ces mêmes mobiles et tablettes utilisent des navigateurs beaucoup plus puissants que les vieux navigateurs encore installés sur la machine de Mamie (que vous devriez aller mettre à jour d’ailleurs) ou de certaines entreprises. A faire du responsive, on risque du coup de se priver de bon nombre de technologies développées depuis et très bien supportées sur 100% du parc visé.

De la même manière, les techniques utilisées pour assurer la compatibilité du code et du rendu entre navigateurs va alourdir le poids de la page, et n’est donc pas forcément adapté.

Enfin, charger la même image en version desktop/tablette/mobile n’est pas non plus une bonne pratique, car on va du coup télécharger sur notre mobile une image desktop que l’on n’affichera jamais ! Notez qu’il n’y a pas de problème avec les sprites, si vous en faîtes un par version.

Responsive pour qui ?

  • si votre service et votre stratégie SEO sont identiques sur mobile/tablette et desktop ;
  • si votre site n’a pas un design et une ergonomie trop compliqués (genre Metro Style), ou que l’on accepte certaines approximations (bord carrés, fond uni, …) ;
  • si vous respectez les bonnes pratiques pour éviter de tout charger pour tout le monde (par exemple http://www.lukew.com/ff/entry.asp?1707)

 

Update du 03.04.2013 : nginx-1.3.15 est maintenant directement fourni avec SPDY module.

Nginx est un très bon logiciel alternatif à apache ou lighttpd. Voici comment utiliser le protocole SPDY avec Nginx.

SPDY : c’est quoi ?

SPDY est un protocole Web qui vient entre le SSL et le HTTP. Le but est d’accélérer le téléchargement des données.

  1. Le premier moyen d’accélérer la connexion est de faire du multiplexage, c’est à dire de faire passer plusieurs flux HTTP en parrallèle au travers de la même connexion. Ainsi, une seule connexion est requise.
  2. Le deuxième moyen est de compresser les données qui ne le sont pas, notamment les entêtes HTTP.

Attention toutefois : SPDY n’est compatible que pour des URLs sécurisées (HTTPS) : « we believe that the long-term future of the web depends on a secure network connection » ( « nous pensons que le futur du web va s’appuyer sur une connexion réseau sécurisée »).

Enfin, SPDY est déjà compatible avec Chrome et Safari, et c’est déjà pas mal ! Voici ce lien pour une liste des navigateurs compatibles à jour.

Conséquenses sur le développement

Le protocole SPDY permet de s’affranchir de certaines règles qui permettent d’afficher plus rapidement une page web :

  1. « moins » besoin de multiplier les noms de domaine des serveurs de fichiers statiques ;
  2. pas besoin de faire de Sprites CSS, car le principe du sprite est d’utiliser la même connexion pour télécharger plusieurs images, mais en combinant toutes les images dans une seule.

Installer nginx-1.3.9 avec SPDY sous gentoo

Voici l’overlay à installer 🙂 (ou bien récupérez juste l’ebuild et le patch !)

https://github.com/paxal/nginx-spdy

En savoir plus

Update 20.01.2013 : L’ebuild de la version nginx-1.3.11 avec spdy est disponible

Update 03.04.2013 : L’ebuild de la verstion nginx-1.3.15 avec spdy est désormais disponible sur le repository officiel de gentoo

« Mazette ! », comme peuvent dire certains ! Que de complications pour avoir accès à certaines fonctionnalités sur des Debian/Ubuntu ! (ci-après dénommées Debian tout court…)

J’ai récemment passé un serveur de Gentoo à Debian et voici les différentes difficultés que j’ai pu rencontrer…

Compilation avec des USE-flags incompatibles

Sur Gentoo, la vie est belle : j’ai besoin de telle fonctionnalité plutôt que de telle autre, je fais mon choix, puis j’emerge le programme. Tandis que sous debian… ah la la… obligé de compiler manuellement pour avoir les bons paramètres dans le ./configure. Résultat : une version compilée à la main, mais dans un répertoire dans un coin, qui ne profitera pas des nouvelles mises à jour.

J’en ai donc fait l’expérience avec PHP et le support des imageps*, ainsi qu’avec tinyproxy qui ne supporte pas la méthode Bind puisque compilé avec un mode « transparent » par défaut. Certes, il y a toujours moyen de contourner le problème du bind avec des règles de sécurité réseau, mais je fais quoi pour mon PHP ? J’ai les deux versions de script avec imageps* et imagettf*, et la version TTF n’est pas à mon goût au niveau de l’antialiasing de la police…

Init configurable

Pas le peine de lancer lighttpd alors que je ne l’ai même pas configuré ! Que se passe-t-il si le logiciel que se lance automatiquement fait planter ma machine à chaque fois (pas pratique d’utiliser le mode rescue pour ce genre de problème !) ou si la version du paquet contient des failles de sécurité ?

Gentoo : un monde imparfait…

Bon par contre, je les connais les problèmes avec Gentoo : configurer son boot (bootloader et kernel), mais avec de l’habitude ça passe plutôt bien ; ou encore attendre pas mal de temps pour compiler un programme genre LibreOffice (-bin ?) ou chromium (google-chrome ?).

J’ai aussi rencontrer des problèmes de stabilité dans certains programmes comme mysql-server ou lighttpd, et là je dois avouer que Debian m’a bien aidé !

Bon, j’arrête de troller…

Je vous ai déjà montré comment créer un certificat autosigné. Le problème de cette méthode, c’est que Chrome (par exemple) vous redemandera à chaque redémarrage d’accepter le certificat, ce qui plutôt pénible. Heureusement, voici la solution !

Autosigner un certificat avec une autorité

Pour que Chrome accepte votre certificat à chaque fois, il faut l’autosigner avec une autorité. Ensuite, il suffit de lui rajouter cette autorité dans la liste des autorités connues et le tour est joué !

Création de l’autorité

Nous allons créer la clé et le certificat permettant de signer d’autres certificats, et de se faire reconnaitre par les navigateurs. Ici, CA signifie Certification Authority.

  1. Je crée une clé privée pour mon autorité. On vous demandera un mot de passe, qu’il faudra fournir à chaque nouvelle signature de certificat.
    openssl genrsa -des3 1024 > ca.key
  2. Je crée le certificat d’autorité, celui que je vais importer plus tard dans Chrome. On va vous demander le mot de passe de la clé privée, ainsi que des informations sur l’autorité de certification que vous représentez. Ici, les données ont peu d’importance.
    openssl req -new -x509 -days 3650 -key ca.key > ca.crt

Création d’une demande de certificat

  1. Création de la clé privée. On vous demandera ici aussi un mot de passe, celui de votre certificat final.
    openssl genrsa -out mondomaine.key 1024
  2. Création d’une demande de signature CSR (Certificate Signature Request) : on crée un fichier contenant nos informations personelles, que nous transmettrons ensuite à notre autorité pour qu’il valide notre certificat. On vous demandera le mot de passe de votre certificat.
    openssl req -new -key mondomaine.key > mondomaine.csr

Signature du certificat

Il ne reste plus qu’à créer un certificat à partir du CSR en utilisant notre autorité.
openssl x509 -req -in mondomaine.csr -out mondomaine.crt -CA ca.crt -CAkey ca.key -CAcreateserial -CAserial ca.srl

La création du « serial » du certificat n’est nécessaire que la première fois.

Utilisation avec les navigateurs

Il ne vous suffit plus que d’ajouter le certificat de votre autorité CA.crt à la liste des certificats racines de votre navigateur, et tous les certificats émis par cette autorité seront automatiquement acceptés !

TL; DR

openssl genrsa -des3 1024 > ca.key
openssl req -new -x509 -days 3650 -key ca.key > ca.crt
openssl genrsa -out mondomaine.key 1024
openssl req -new -key mondomaine.key > mondomaine.csr
openssl x509 -req -in mondomaine.csr -out mondomaine.crt -CA ca.crt -CAkey ca.key -CAcreateserial -CAserial ca.sr

Netographie : linux-france.org : la création des certificats

Faire du HTML et du CSS est assez rébarbatif : toujours les mêmes balises, recopier les balises de fin, écrire des attributs class ou id… bref, un peu tout le temps la même chose en plusieurs exemplaires. C’est là que ZenCoding intervient !

Zen Coding, c’est quoi ?

Zend Coding est un plugin d’aide à la saisie pour votre HTML et votre CSS.

Concrètement, ça accélère la saisie, confère l’exemple ci-dessous !

Voici ce que l’on souhaite écrire comme code :

  1. <div id="content">
  2.   <ul class="list">
  3.     <li></li>
  4.     <li></li>
  5.     <li></li>
  6.   </ul>
  7. </div>

Et voici comment on va taper ça sur notre clavier :

div#content>ul.list>li*3 <Commande magique qui dépend de l'éditeur>

Fonctionnement

D’une manière plus générale, Zen Coding va interprété ce que vous venez d’écrire pour le transformer en code.

Dans notre exemple, nous avons utiliser une notation très proche du CSS pour écrire de l’HTML sans effort !

Mais ça marche aussi avec le CSS, notamment pour les attributs un peu long à écrire (ou à faire auto-compléter par l’éditeur), voire pour les valeurs. Il est en effet plus rapide de taper d:n<TAB> que display: none; avec la majorité des outils d’autocomplete.

Compatibilité

Le plugin est compatible avec énormément d’IDE, tels que Eclipse ou Vim (pour mon utilisation personnelle).

Plus d’informations sur le site de Zen Coding (hosté sur google code).