PHP et Unicode

Introduction: PHP et le Japonais

Faisons une petite expérience! Supposons que votre système soit capable d’afficher les caractères japonais…
Éditez un fichier php et entrez-y le code suivant:

<?php echo strlen('元気ですか?'); ?>

La phrase 元気ですか? que l’on pourrait traduire par “Comment allez-vous?” ou “Est-ce que ça va?” contient deux kanji, trois hiragana et un point d’interrogation. Cette phrase contient donc 6 caractères.

On pourrait donc s’attendre naturellement à ce que le code PHP précédent retourne comme résultat: “6“.

Eh bien pour autant… A l’heure où j’écris ces lignes, et partant d’une installation standard de PHP 5.3.3 sous Debian Squeeze le résultat qui s’affiche chez moi est “18“.

Peut-être en est-il de même chez vous? Si c’est le cas, ne vous inquiétez pas. J’ai peut-être la solution pour vous éviter l’overdose d’aspirine.

Pour la petite histoire, PHP ne gère pas l’unicode en natif. Pour gérer ce type d’encodage, il faut utiliser des extensions. L’extension qui est peut-être la plus utilisée pour ce type de traitement est mbstring qui fournit des alternatives à un grand nombre de fonctions dont, entre autre: mb_strlen qui est l’équivalent de strlen. Cette extension est d’ailleurs tellement utilisée qu’elle est généralement installée par défaut, il ne nous reste donc qu’à configurer son comportement.

Nous allons donc passer par deux étapes:

  1. Configuration de l’extension mbstring
  2. Surcharges des fonctions standard par celles de mbstring

Configuration de l’extension mbstring

En effet, si vous tentez de suite d’utiliser mb_strlen à la place de strlen, il se peut que vous obteniez exactement le même résultat.

Rendez-vous d’abord dans votre fichier de configuration php.ini et cherchez toute ligne contenant mbstring.internal_encoding.

Sous Debian par exemple il y a la bloc suivant:

; internal/script encoding.
; Some encoding cannot work as internal encoding.
; (e.g. SJIS, BIG5, ISO-2022-*)
; http://php.net/mbstring.internal-encoding
;mbstring.internal_encoding = EUC-JP

Décommentez alors la dernière ligne ou copiez là juste en dessous et modifiez-la de façon à obtenir :

mbstring.internal_encoding = UTF-8

Maintenant, si vous exécutez le code fournit en début de cet article, vous aurez toujours “18“, mais si vous remplacez la fonction strlen par mb_strlen, vous devriez obtenir “6“.(1)

Surcharge des fonctions standards par celles de mbstring

Oh bonne mère! Je vais devoir changer toutes mes fonctions str* par des mb_str* !!?

Mais non! Détendez-vous! PHP veille sur vous… Nous allons tout simplement lui demander d’utiliser les fonctions mb_* automatiquement.

Pour cela, retournons dans php.ini. Cette fois-ci cherchez la ligne

;mbstring.func_overload = 0

et rajoutez (ou remplacez-la par):

mbstring.func_overload = 7

(Alors normalement, il y a un truc de masque avec 0,1,2,4 que vous aurez tout le temps d’approfondir. Mais là, on va au plus simple: on active mbstring pour tout. Donc la valeur à mettre, c’est 7.)

Maintenant, si vous retournez lancer le script d’exemple, vous devriez avoir un joli “6” réconfortant qui apparaît, preuve que la modification est en place et qu’il ne vous reste plus qu’à profiter de l’unicode avec vos fonctions str* et la langue que vous voulez.

Conclusion

Si le support de l’unicode est plus ou moins possible avec PHP, il nécessite cependant que l’on s’attarde quelques fois sur la configuration de l’environnement. L’utilisation de mbstring est une solution provisoire qui fonctionne relativement bien mais qui reste parfois difficile à obtenir dans certains cas tels que les hébergement mutualisés.


(1): PHP peut être utilisé soit en ligne de commande, soit derrière un serveur web comme apache. En ligne de commande, toute modification du fichier /etc/php5/cli/php.ini sera de suite effectif, mais dans le cas d’un serveur web (/etc/php5/apache/php.ini par exemple), pensez à bien redémarrer le service à chaque modification.


PHP et Unicode par Charles-Edouard Coste est mis à disposition selon les termes de la Licence Creative Commons Paternité – Pas de Modification 3.0 France

Licence Creative Commons

Arborescences et PostgreSQL

Problématique

Un problème sur lequel je me suis plusieurs fois cassé les dents est celui de la représentation d’une arborescence dans une base de données SQL. Bien souvent, il faut choisir entre une base bien structurée mais peu performante, ou performante mais mal structurée.

En général, le respect des bonnes pratiques nous restreint à utiliser une couche d’abstraction et de n’utiliser que des fonctionnalités communes à chaque SGBD ce qui malheureusement nous fait passer à côté de fonctionnalités réellement intéressantes comme celles des requêtes récursives de PostgreSQL et nous pousse à utiliser d’autres solutions qui ont leurs limites.

Structure de base

Intuitivement et conformément à la définition mathématique d’un arbre (qui est un graphe particulier, et qui contient donc un ensemble de nœuds et un ensemble de couples) une table des relations enfant/parent tombe sous le sens.

Par exemple, pour stocker une arborescence du type:

1
├── 2
└── 3
    └── 4

On aura donc une table similaire à celle-ci :

parent enfant
1 2
1 3
3 4

Mais le stockage est une chose, la récupération des informations en est une autre. Or, s’il est très simple ici de récupérer le parent d’un nœud, pour connaître l’ensemble de ses ancêtres, c’est une autre paire de manches…

Solution 1: la bidouille applicative

Intuitivement toujours, la solution serait de faire plusieurs requêtes :

SELECT parent FROM arbre WHERE enfant = 4;

Cette requête retourne {3} et on peut donc renchaîner sur

SELECT parent FROM arbre WHERE enfant = 3;

qui retourne {1}

On peut donc réussir à récupérer les ancêtres du nœud numéro 4 mais au prix de 2 requêtes. Globalement, plus l’arborescence est profonde et plus le nombre de requêtes est important. Un nœud qui sera à une profondeur de 10 nécessiterait 9 requêtes.

Par ailleurs, les requêtes ne peuvent pas être rassemblées en une seule transaction puisque chacune dépend du résultat de la précédente, donc si une modification a lieu en même temps, le résultat pourra être erroné.

Solution 2: la bidouille structurelle

Autre solution: ajouter l’information dans une table supplémentaire.

ancetre descendant
1 2
1 3
1 4
3 4

Cette solution est déjà plus sûre et plus performante car une seule requête est nécessaire pour obtenir l’intégralité des ancêtres d’un noeud.

On est sur la bonne voie, mais le problème c’est que cela introduit de la redondance dans la base. En effet, la liste des relations ancêtre/descendant se déduit des relations parent/enfant. Notre base ne respecterait donc pas la forme normale de Boyce-Codd (et on y tient à cette forme normale n’est-ce pas?)

Et l’on serait confronté à toutes les complications techniques du genre “synchronisation de tables”, etc. qui pompent l’air et rendent l’application difficile à maintenir dans le meilleur des cas.

Il nous faut donc une requête (éventuellement stockée dans une vue) qui va nous fournir la deuxième table en fonction de la première, dans un temps suffisamment court et en une seule requête pour préserver les propriétés ACID de notre base.

Solution 3 : PostgreSQL, WITH et RECURSIVE

PostgreSQL est à ce jour le seul SGBD libre que j’ai pu trouver et qui accepte les requêtes récursives. Mais je ne suis pas un expert sur le sujet; il y a peut-être d’autres SGBD qui le supportent ou peut-être que MySQL a prévu de l’implémenter…

Pour PostgreSQL, en tout cas, on utilise le mot-clef WITH qui permet de constituer des tables temporaires pendant la durée d’une requête. Le mot-clef RECURSIVE autorise alors l’utilisation de cette même table dans la requête dont elle est issue.

Dans notre cas, cela donne :

WITH RECURSIVE descendances(ancetre, descendant) AS (

  SELECT arbre.parent, arbre.enfant
  FROM   arbre

UNION 

  SELECT descendances.ancetre, arbre.descendant
  FROM   arbre, descendances
  WHERE  arbre.parent = descendances.descendant
)
SELECT * FROM descendances;

Selon la documentation de PostgreSQL, elle-même, il ne s’agit pas à proprement parler de récursivité, mais le mot-clef RECURSIVE est celui qui a été choisi au niveau de la normalisation SQL.

La table ancestors va fournir l’équivalent de la table présentée dans la solution 2. Sauf que celle-ci est volatile et disparaîtra dès la fin de la requête.

On pourra remplacer le SELECT * FROM descendances en fonction de ce que l’on souhaite obtenir.

Pour récupérer les ancetres du noeud 4 :

SELECT ancetre FROM descendances WHERE descendant = 4

Pour récupérer les descendants du noeud 1 :

SELECT descendant FROM descendances WHERE ancetre = 1

Je ne saurai pas expliquer le fonctionnement de cette requête mieux que la page de documentation officielle ne le fait déjà, cela dit les exemples présentés sont cependant légèrement plus complexes car ils s’appliquent à des cas plus génériques. Il existe aussi un post très intéressant avec un cas concret  concernant des employés et leur hiérarchie.

Conclusion

Il est toujours bon de s’appliquer à mettre en place des bonnes pratiques de conception. Cependant il arrive parfois que, faute de solutions techniques suffisamment évoluées,  une entrave au règlement sur l’une puisse améliorer le respect d’une autre.

En faisant le choix d’utiliser la récursivité dans PostgreSQL, on se rend dépendant de ce système, mais on gagne en clarté et en cohérence sur l’ensemble de l’application dès qu’il s’agit de manipuler des arborescences ou toute autre structure complexe à parcourir.

PostgreSQL est donc, selon moi, la meilleure solution actuelle pour travailler avec des structures arborescentes. En attendant le développement de bases de données sémantiques…


Arborescences et PostgreSQL par Charles-Edouard Coste est mis à disposition selon les termes de la Licence Creative Commons Paternité – Pas de Modification 3.0 France

Licence Creative Commons

Installation de Ant sous Ubuntu

Ayant eu quelques soucis au début, j’écris ce billet pour ceux qui auraient le même problème que moi.

Pour installer Ant sous ubuntu, la commande que l’on trouve naturellement sur internet est la suivante :

sudo apt-get install ant

A ce moment, la commande ant est ajoutée et vous pouvez la taper dans un terminal.

Cependant, vous pouvez avoir l’erreur suivante :

Unable to locate tools.jar

Pour ma part j’ai donc entrepris de chercher ce fichier :

sudo updatedb
locate tools.jar

Mais il était toujours introuvable.
Si cela vous arrive aussi, alors pas de panique… C’est juste qu’il vous manque le JDK Java.

En théorie, vous pouvez installer la version officielles de Sun ou la version libre. Comme je préfère la version libre j’ai donc tapé :

sudo apt-get install openjdk-6-jdk

Et tout est rentré dans l’ordre.


Installation de Ant sous Ubuntu par Charles-Edouard Coste est mis à disposition selon les termes de la Licence Creative Commons Paternité – Pas de Modification 3.0 France

Licence Creative Commons

Installer les eZ Components sous GNU Debian/Linux

Introduction

Les Zeta Components  sont des bibliothèques PHP sous licence libre. Anciennement “eZ Components” et diffusées sous licence  New BSD elles seront dorénavant diffusées sous licence Apache License, Version 2.0 mais ne sont encore disponibles en téléchargement que sous l’ancienne appellation.

Celles-ci sont toutefois particulièrement bien conçues déjà, et suivent des patrons de conception bien connus des architectes logiciels ce qui les rend très faciles à réutiliser et à étendre.

Je propose de commencer aujourd’hui avec l’installation de ces bibliothèques et leur utilisation dans un projet PHP dans un environnement Linux et plus particulièrement pour une distribution Debian.

La marche à suivre devrait différer que très peu de celle des autres distributions Linux ou Unix. Quand à l’environnement Windows… Eh bien je ne considère pas ce système comme un environnement de développement à vrai dire…

Les pré-requis

Il vous faudra déjà PHP 5.2.1 au minimum si vous comptez utiliser les eZ Components. Et PEAR si vous souhaitez une installation et une mise à jour facile. Les quelques extensions nécessaires étant intégrées de base dans PHP, il ne sera pas nécessaire d’installer autre chose.

Ce qui nous fait sous Debian* :

# aptitude install php5 php-pear

Si vous êtes sur un hébergeur mutualisé et que vous ne pouvez pas installer PEAR, ne vous inquiétez pas ce n’est pas nécessaire.

Installation

…avec PEAR

Si vous venez d’installer PEAR, vous devrez peut-être faire une mise à jour:

# pear upgrade-all

Ensuite, il vous suffit d’ajouter le “channel” eZ Components ( l’ASF ne proposant pas encore de dépôt) puis de demander à PEAR d’installer tout ce qu’il faut :

# pear channel-discover components.ez.no
# pear install -a ezc/eZComponents

Voilà… Toutes les librairies PHP seront téléchargées automatiquement et entreposées dans /usr/share/php/ezc.

Tout l’intérêt de PEAR, hormis l’installation automatisée est qu’il suffit d’une commande pour mettre à jour les bibliothèques :

# pear upgrade ezc/eZComponent

… avec l’archive

Si vous n’avez pas la possibilité d’utiliser PEAR, vous pouvez toujours vous rendre sur la page de téléchargement des eZ Components. Je vous conseille de télécharger la version light. La version full contenant beaucoup de fichiers inutiles si vous ne comptez pas toucher aux code des bibliothèques en elles-mêmes..

Décompressez l’archive dans un dossier ( ezc par exemple ) et placez-le dans le dossier dans lequel vous comptez stocker les bibliothèques utiles à vôtre projet ( nous dirons ‘lib‘ ce qui nous donne au final: <racine_du_projet>/lib/ezc )

Utilisation

…la version courte

Pour rendre disponible l’intégralité des classes offertes par les eZ Components, ils vous suffit d’inclure le fichier ezc_bootstrap.php au début de vos scripts.

Si vous avez installé les bibliothèques à l’aide de PEAR cela donnera :

<?php
require 'ezc/Base/ezc_bootstrap.php';
...
?>

Et si vous avez utilisé l’archive et que nous sommes par exemple dans le fichier <racine_du_projet>/www/index.php :

<?php
require '../lib/ezc/Base/src/ezc_bootstrap.php';
...
?>

ou bien

<?php
set_include_path( "<racine_du_projet>/lib/ezc/" . PATH_SEPARATOR .  get_include_path());
require 'Base/src/ezc_bootstrap.php';
?>

…la version un tout petit peu moins courte

Le script ezc_bootstrap.php ne fait en réalité qu’importer la classe ezcBase et redéfinir la fonction d’auto-chargement (autoload) de PHP5 pour que ce mécanisme soit délégué à ezcBase::autoload().

Vous pouvez donc arriver au même résultat en faisant votre propre fichier bootstrap.php contenant le code suivant si installation par PEAR :

<?php
require_once "Base/base.php";
function __autoload( $className )
{
        ezcBase::autoload( $className );
}
...
?>

ou si installation via l’archive:

<?php
require_once "<racine_du_projet>/lib/ezc/"Base/base.php";
function __autoload( $className )
{
        ezcBase::autoload( $className );
}
...
?>

Cela peut vous permettre accessoirement de paramétrer plus finement le système de gestion de chargement des classes.

Une alternative possible est d’utiliser la fonction spl_autoload_register au lieu de redéfinir __autoload() :

<?php
require_once "<racine_du_projet>/lib/ezc/Base/base.php";
spl_autoload_register( array( 'ezcBase', 'autoload' ) );
...
?>

Test

Vous voulez savoir si l’installation s’est bien déroulée? Rien de plus simple :

<?php
...
echo ezcSystemInfo::getInstance()->cpuType."\n";
?>

Si tout va bien, vous devriez voir s’afficher le type de votre processeur.

Par exemple :

Intel(R) Core(TM) i7 CPU       Q 720  @ 1.60GHz

Dans le cas contraire vous devriez obtenir une belle erreur PHP:

PHP Fatal error:  Class 'ezcSystemInfo' not found

Conclusion

Vous voilà donc avec des eZ Components prêts à l’emploi. Toutes les classes de la bibliothèque sont désormais instanciables à volonté partout dans votre application. Le passage futur aux Zeta Components ne devrait pas changer grand chose à la démarche.

(*): L’invite sous forme de dièse (#) sous-entend que vous devez être administrateur pour effectuer la commande.


Installer les eZ Components sous GNU Debian/Linux par Charles-Edouard Coste est mis à disposition selon les termes de la Licence Creative Commons Paternité – Pas de Modification 3.0 France

Licence Creative Commons

Faut-il stocker des fichiers dans une base de données?

Constat de départ

J’ai souvent fait remarqué que le CMS eZ publish (comme bien d’autres) stocke les fichiers dans l’arborescence au lieu de les stocker dans la base de données.

Lorsque je commençais à peine à utiliser ce logiciel, il m’est arrivé une fois de perdre tous mes fichiers car je n’avais pas pensé à sauvegarder le répertoire de stockage. Ce répertoire contenait d’ailleurs à la fois les fichiers originaux et les fichiers générés automatiquement ce qui implique finalement de devoir vider les caches avant chaque sauvegarde si l’on ne souhaite pas gaspiller de l’espace.

Explications possibles

A ce sujet j’ai eu l’avis de deux confrères:

Le premier m’a fait remarquer que je jouais à “l’apprenti sorcier”. Car il me fallait au moins 2 serveurs dédiés, avec synchronisation et toute l’infrastructure qu’il faut.

Le deuxième soutenait que les fichiers n’avaient pas leur place dans une base de données car “un fichier est un fichier, il doit être géré comme tel”. Cet avis est d’ailleurs le même que bon nombre de personnes si l’on cherche un peu sur les forums.

Ce que j’en pense

Ces deux confrères ont plus ou moins raison mais le problème est qu’à aucun moment l’un d’eux ne s’intéresse à l’intégrité des données. L’un se repose sur l’infrastructure tandis que l’autre se trouve une bonne excuse pour ne pas aller plus loin.

Le système de gestion de base de données est supposé garantir la cohérence de ces données! A quoi cela peut-il bien servir que l’on définisse des contraintes d’intégrité référentielle s’il suffit de modifier des fichiers dans un terminal pour introduire des erreurs?

Bien sûr, on pourra dire que si le système est bien conçu alors personne n’est supposé avoir le droit de modifier les fichiers directement sur le serveur. Tout comme une application n’est pas supposée introduire des erreurs référentielles dans une base de données, toujours est-il que cela peut arriver et que c’est pour éviter cela que l’on déclare des contraintes.

Conclusion

Si un fichier fait partie du contenu d’un site web, il doit être stocké en base de données et ce même si cela rend la base de données plus lourde. C’est le seul moyen de garantir l’intégrité des données. Dans le cas contraire il ne s’agirait que d’une base de “méta-données”.

Mais ne me faites pas dire ce que je n’ai pas dit! On peut continuer de servir des fichiers sans pour autant faire appel à la base de données systématiquement, c’est ce qu’on appelle généralement : “un cache”.


Faut-il stocker des fichiers dans une base de données? par Charles-Edouard Coste est mis à disposition selon les termes de la Licence Creative Commons Paternité – Pas de Modification 3.0 France

Licence Creative Commons

Suivre

Get every new post delivered to your Inbox.

Joignez-vous à 229 followers