Installer plusieurs version de php

Bonjour,

Introduction

Quand on installe php5 et php7 sur son ordinateur (ou serveur), on ne peut en utiliser qu’un. Malheureusement, des fois, il nous faut tester le même site sous différentes versions de php. Et des fois, on aimerais travailler sur 2 projets qui nécessitent des versions de php différentes.

Je suis passé par plusieurs solutions plus ou moins efficaces, comme les machines virtuelles, 2 serveurs apaches sur 2 ports différents, etc.

La solution en théorie

J’ai fini par trouver la solution idéale :

Php en fast cgi et une variable dans htaccess pour choisir la version à executer. Théoriquement on pourrait avoir au sein du même site, une version différente de php par répertoire (site vitrine, blog…). Dans la pratique, on met un seul htaccess à la racine du site et c’est tout.

On peut aussi paramétrer php-fpm pour que les sites aient l’utilisateur et le groupe d’un compte spécifique, utile pour accorder les droits sur le site et sur ftp et les mettant sur un utilisateur système commun.

On va détailler ici uniquement la partie transformation de php de module vers fast cgi.

Je pars du principe que les 2 versions ont été installées via le repository de la distribution, pour debian :

sudo apt-get install apache2 php7.2 libapache2-mod-php7.2 mysql-server mysql-client php7.2-mysql phpmyadmin php7.2-odbc php7.2-curl php7.2-json php7.2-xml php7.2-mbstring

C’est typiquement ce que j’installe pour avoir un serveur de développement lamp fonctionnel.

On imagine donc que sur le serveur il y ait php5, php7.0 et php7.2 d’installés.

La version exécutée par apache dépend de l’ordre d’installation. Je suppose que dans une utilisation normale, on a installé php7.2 en dernier et c’est donc lui qui anime tous les sites sur lesquels on travail.

La soluton en pratique

Il faut tout d’abord arrêter apache

sudo service apache2 stop

Ensuite on supprime tous les modules php

sudo rm -f /etc/apache2/mods-enabled/php*
sudo apt-get remove --purge 'libapache2-mod-php*'

On installe la version cgi de php

sudo apt-get install libapache2-mod-fastcgi php5-fpm php5-cgi php7.0-fpm php7.0-cgi php7.2-fpm php7.2-cgi

On active le module actions

sudo a2enmod actions

On configure fastcgi

sudo nano -w /etc/apache2/mods-enabled/fastcgi.conf

Mettre le contenu suivant

<IfModule mod_fastcgi.c>
	AddHandler fastcgi-script .fcgi
	#FastCgiWrapper /usr/lib/apache2/suexec
	FastCgiIpcDir /var/lib/apache2/fastcgi
	AddType application/x-httpd-php5 .php5 .php
	AddHandler php5 .php5 .php
	Action php5 /cgi-php5
	Alias /cgi-php5 /usr/lib/cgi-bin/php5
	FastCgiExternalServer /usr/lib/cgi-bin/php5 -socket /var/run/php5-fpm.sock -pass-header Authorization
	AddType application/x-httpd-php7.0 .php7 .php
	AddHandler php7.0 .php7 .php
	Action php7.0 /cgi-php7.0
	Alias /cgi-php7.0 /usr/lib/cgi-bin/php7.0
	FastCgiExternalServer /usr/lib/cgi-bin/php7.0 -socket /var/run/php/php7.0-fpm.sock -pass-header Authorization
	AddType application/x-httpd-php7.2 .php7 .php
	AddHandler php7.2 .php7 .php
	Action php7.2 /cgi-php7.2
	Alias /cgi-php7.2 /usr/lib/cgi-bin/php7.2
	FastCgiExternalServer /usr/lib/cgi-bin/php7.2 -socket /var/run/php/php7.2-fpm.sock -pass-header Authorization
	<Directory /usr/lib/cgi-bin>
		Require all granted
	</Directory>
</IfModule>

Le bloc précédent est un peu indigest mais en le lisant, on voit qu’il y a 3 blocs, 1 pour chaque version de php.

Il ne reste plus qu’a activer apache

sudo service apache2 start

Tout devrait fonctionner comme avant. Y compris, la version php7.2 par defaut.

Pour changer cela, dans le htaccess, on mettra le code suivant

<IfModule mod_fastcgi.c>
 AddHandler php5 .php
 #AddHandler php7.0 .php
 #AddHandler php7.2 .php
</IfModule>

Il suffit de décommenter la ligne qui nous intéresse et de commenter les autres. On peut aussi supprimer celles qui ne nous intéressent plus.

Conclusion

Voilà, c’est la configuration que j’utilise sur les serveurs de développement. Cela permet d’avoir un seul serveur qui pourra s’adapter à toutes les versions de php.

Je ne sais pas comment c’est fait chez free.fr, mais je suppose que c’est très similaire, on peut donc supposer que cette méthode (avec un réglage de fpm plus approfondi pour les droits) peut convenir à un serveur de prod.

 

Coroutine en C

Bonjour,

Beaucoup de langages modernes ont une fonctionnalité que je trouve intéressante. Il s’agit des coroutines.

Pour simplifier, une coroutine est une boucle dans laquelle on peut sortir temporairement, sans en perturber la reprise.

Dans la plupart des langages, on remplace ‘return’ par ‘yield’.

Le langage C n’aime pas l’évolution. Je peux le comprendre, car il faut qu’il garde son statut du « plus bas niveau langage de haut niveau ».

L’avantage des coroutines, c’est qu’on peut séparer la génération de la donnée de son traitement.

De plus, on ne limite pas la génération à quelques valeurs, mais on peut faire une boucle infinie sans se soucier de combien il en faut.

C’est un bon moyen de rendre le code réutilisable.

Dans cet article, nous allons voir comment afficher des nombres séquentiellement. Sans coroutine, puis nous allons voir une méthode de transformation.

Voici un exemple d’un affichage des nombres premiers :

#include <malloc.h>
#include <stdio.h>
typedef struct _Doublet{
   int valeur;
   int compteur;
}Doublet;
int main(int argc, char *argv[]){
   int tabNb=0;
   Doublet *tab=0;
   for(int i=2;i<100;i++){
      int j;
      for(j=0;j<tabNb;j++){
         while(tab[j].compteur<i){
            tab[j].compteur+=tab[j].valeur;
         }
         if(tab[j].compteur==i){
            //pas premier
            break;
         }
      }
      if(j==tabNb){
         //premier
         tabNb++;
         tab=realloc(tab, sizeof(*tab)*tabNb);
         tab[tabNb-1].valeur=i;
         tab[tabNb-1].compteur=i;
         printf("%d\n",i);
      }
   }
   free(tab);
   tab=0;
}

J’ai fais un algorithme qui garde trace des nombres premiers trouvés pour tester les suivants. Rien de bien évolué, mais il y a une structure et un tableau. Ce qui montrera que la transformation ne marchera pas seulement pour les algorithmes triviaux, mais aussi dans les cas plus complexes.

L’étape suivante est de séparer la déclaration des variables, leur initialisation et la destruction dans des blocs distincts.

#include <malloc.h>
#include <stdio.h>
typedef struct _Doublet{
   int valeur;
   int compteur;
}Doublet;
int main(int argc, char *argv[]){
   //declaration
   int tabNb;
   Doublet *tab;
   int i;
   int j;
   //initialisation
   tabNb=0;
   tab=0;
   //traitement
   for(i=2;i<100;i++){
      for(j=0;j<tabNb;j++){
         while(tab[j].compteur<i){
            tab[j].compteur+=tab[j].valeur;
         }
         if(tab[j].compteur==i){
            //pas premier
            break;
         }
      }
      if(j==tabNb){
         //premier
         tabNb++;
         tab=realloc(tab, sizeof(*tab)*tabNb);
         tab[tabNb-1].valeur=i;
         tab[tabNb-1].compteur=i;
         printf("%d\n",i);
      }
   }
   //destruction
   free(tab);
   tab=0;
}

Rien de bien sorcier, il faut juste être rigoureux pour ne rien oublier.

Maintenant, il faut mettre toutes les déclarations dans une structure, et utiliser cette structure bien entendu. J’ai appelé cette structure ‘Contexte’

#include 
#include 
typedef struct _Doublet{
   int valeur;
   int compteur;
}Doublet;
typedef struct _Contexte{
   int tabNb;
   Doublet *tab;
   int i;
   int j;
}Contexte;
int main(int argc, char *argv[]){
   //init contexte
   Contexte *c=0;
   c=malloc(sizeof(*c));
   //initialisation
   c->tabNb=0;
   c->tab=0;
   //traitement
   for(c->i=2;c->i<100;c->i++){
      for(c->j=0;c->jtabNb;c->j++){
         while(c->tab[c->j].compteuri){
            c->tab[c->j].compteur+=c->tab[c->j].valeur;
         }
         if(c->tab[c->j].compteur==c->i){
            //pas premier
            break;
         }
      }
      if(c->j==c->tabNb){
         //premier
         c->tabNb++;
         c->tab=realloc(c->tab, sizeof(*c->tab)*c->tabNb);
         c->tab[c->tabNb-1].valeur=c->i;
         c->tab[c->tabNb-1].compteur=c->i;
         printf("%d\n",c->i);
      }
   }
   //destruction
   free(c->tab);
   c->tab=0;
   //fin contexte
   free(c);
   c=0;
}

On va créér des fonctions pour chacune des parties du code : initialisation, traitement, destruction. On pourra laisser le compilateur gérer l’init et la destruction de la structure ‘c’.

#include 
#include 
typedef struct _Doublet{
   int valeur;
   int compteur;
}Doublet;
typedef struct _Contexte{
   int tabNb;
   Doublet *tab;
   int i;
   int j;
}Contexte;
void init(Contexte *c){
   c->tabNb=0;
   c->tab=0;
}
void premier(Contexte *c){
   for(c->i=2;c->i<100;c->i++){
      for(c->j=0;c->jtabNb;c->j++){
         while(c->tab[c->j].compteuri){
            c->tab[c->j].compteur+=c->tab[c->j].valeur;
         }
         if(c->tab[c->j].compteur==c->i){
            //pas premier
            break;
         }
      }
      if(c->j==c->tabNb){
         //premier
         c->tabNb++;
         c->tab=realloc(c->tab, sizeof(*c->tab)*c->tabNb);
         c->tab[c->tabNb-1].valeur=c->i;
         c->tab[c->tabNb-1].compteur=c->i;
         printf("%d\n",c->i);
      }
   }
}
void clean(Contexte *c){
   free(c->tab);
   c->tab=0;
}
int main(int argc, char *argv[]){
   Contexte c;
   //initialisation
   init(&c);
   //traitement
   premier(&c);
   //destruction
   clean(&c);
}

Enfin, la dernière étape, ajouter un attribut yield, utiliser goto (oui, c’est mauvais, mais c’est ça ou le duff device, beuurk !) et utiliser notre coroutine.

#include 
#include 
typedef struct _Doublet{
   int valeur;
   int compteur;
}Doublet;
typedef struct _Contexte{
   int tabNb;
   Doublet *tab;
   int i;
   int j;
   int yield;
}Contexte;
void init(Contexte *c){
   c->tabNb=0;
   c->tab=0;
   c->yield=0;
}
int premier(Contexte *c){
   switch(c->yield){
      case 1: goto yield1;
   }
   for(c->i=2;;c->i++){
      for(c->j=0;c->jtabNb;c->j++){
         while(c->tab[c->j].compteuri){
            c->tab[c->j].compteur+=c->tab[c->j].valeur;
         }
         if(c->tab[c->j].compteur==c->i){
            //pas premier
            break;
         }
      }
      if(c->j==c->tabNb){
         //premier
         c->tabNb++;
         c->tab=realloc(c->tab, sizeof(*c->tab)*c->tabNb);
         c->tab[c->tabNb-1].valeur=c->i;
         c->tab[c->tabNb-1].compteur=c->i;
         c->yield=1;
         return c->i;
         yield1:;
      }
   }
}
void clean(Contexte *c){
   free(c->tab);
   c->tab=0;
}
int main(int argc, char *argv[]){
   Contexte c;
   //initialisation
   init(&c);
   //traitement
   for(int i=0;i<25;i++){
      printf("%d\n",premier(&c));
   }
   //destruction
   clean(&c);
}

Je ne veux pas intégrer ‘init’ et ‘clean’ dans le traitement à cause de ‘clean’ qui ne serait jamais appelé. Et une destruction explicite sans init me semble bizarre.

Voila, on a une coroutine pas trop dégueux à utiliser et qui reste fidèle à l’algorithme de départ.

Closure en php

Bonjour,

Les closures qu’est-ce que c’est ?

Eh bien, ça dépend du langage. Dans un langage fonctionnel, c’est une fonction et son environnement. En PHP, c’est une fonction anonyme.

Dans la version 7.1 de PHP, on se rapproche d’une vraie closure grâce à « use ».

Ce qui m’intéresse dans les fonctions anonymes en PHP, c’est leur côté first class functions. C’est à dire qu’on peut les utiliser comme des objets et les affecter à des variables, les passer en paramètre à d’autres fonctions, etc.

Si vous vous êtes demandé comment les routeurs (réécriture d’url) font leur travail, la réponse est ici.

Par exemple comment je peux écrire la fonction get pour que le code ci-dessous marche ?

$routeur->get('/un/chemin', controleur());
$routeur->get('/un/autre/chemin', function(){return "coucou";});

Dans le premier cas, c’est facile, la fonction controleur s’exécute et retournera sûrement une chaîne de caractère qui sera reproduite à l’écran.

L’implémentation de get pourrait être la suivante :

function get($chemin, $resultat){
   //quelques traitements
   if(is_callable($resultat)){
      echo $resultat();
   }else{
      echo $resultat;
   }
   //d'autres traitements
}

On peut facilement étoffer l’exemple avec de nouvelles fonctionnalités. Mais je vais m’arrêter là pour l’instant.