Aller au menu - Aller au contenu

[Plan du site] Vous êtes ici --- > Le Site du Zér0 > Les tutoriels > Non-Officiels > Site Web > PHP > Points particuliers > Lecture du tutoriel

Conservons nos variables avec serialize()

Avatar
Auteur : Lpu8er
Créé : le 01/11/2007 15:50:11
Modifié : le 04/02/2008 11:57:37
Noter et commenter ce tutoriel
Imprimer ce tutoriel
Vous vous apprêtez à lire un tutoriel rédigé par un membre de ce site. Malgré tout le soin que ce membre a pu apporter au tutoriel, nous ne pouvons pas garantir que les informations contenues sur cette page sont exactes à 100%. Merci de garder cela en tête lorsque vous lirez cette page ;o)
Avant d'aborder la lecture de ce tuto, vous devez maîtriser les notions suivantes :


À certains moments, on apprécierait de pouvoir enregistrer un array en intégralité (c'est-à-dire avec les clefs associées aux valeurs) en base de données, ou encore dans un fichier.
Des possibilités s'offrent alors à nous : on pourrait, par exemple dans le cas d'un fichier, parcourir l'array via un foreach, afin d'écrire ligne par ligne chaque élément... Mais cet exemple devient impossible dans le cas d'un array multidimensionnel, par exemple.
Un array multidimensionnel est un array contenant plus d'une dimension. Par exemple, $array[X][Y] signifierait qu'on a un array à 2 dimensions, alors que $array[X] nous indique un array à 1 seule dimension...
Plus généralement, un array multidimensionnel serait un array content un array (qui peut lui-même contenir un array... etc.).

On pourrait alors utiliser la récurrence... Et on se rend alors compte que cela fait beaucoup de bruit pour rien.
En effet, il existe une fonction bien particulière et ô combien puissante dans ce genre de cas en php : serialize().
Sommaire du chapitre :

Le principe

serialize() ne fonctionne correctement que depuis PHP4.
Mais je ne pense pas que vous soyez encore sous une version antérieure... Si c'est le cas, je vous conseille vivement de vous mettre à jour, il est plus que temps !


Le principe de serialize est simple, et bien résumé dans la documentation PHP :

Citation : Documentation PHP
serialize() retourne une chaîne contenant une représentation linéaire de value, pour stockage.
C'est une technique pratique pour stocker ou passer des valeurs de PHP entre scripts, sans perdre ni leur structure, ni leur type.


Ah, c'est simple et bien résumé, cela ?


Eh bien mine de rien, oui. Cela signifie simplement qu'on peut "transtyper" une variable, quelle que soit son type, en une chaîne de caractères.

Notez les guillemets autour du mot transtyper. En effet, le principe de serialize n'est pas de faire un transtypage classique, mais bien une linéarisation qui conserve le type (et donc les informations) de la variable originale.


Pour comprendre simplement, testons ce code :

Code : PHP
1
2
3
4
<?php
        $notes = array(7,3,8,9); // formation d'un array pour la forme
        echo serialize($notes); // echo du résultat de serialize sur cet array
?>

Citation : Affichage
a:4:{i:0;i:7;i:1;i:3;i:2;i:8;i:3;i:9;}


Un echo suffit, car serialize retourne une chaîne de caractères (en tout cas, s'il n'y a aucune erreur).
Cela nous affichera... quelque chose de peu compréhensible, certes. Mais on arrive à voir que nos valeurs sont toujours là et dans le même ordre, même si c'est sous forme différente.

Testons cet autre code, à présent :

Code : PHP
1
2
3
4
<?php
        $notes = array('maths'=>7,'anglais'=>3,'svt'=>8,'algo'=>9);
        echo serialize($notes);
?>

Citation : Affichage
a:4:{s:5:"maths";i:7;s:7:"anglais";i:3;s:3:"svt";i:8;s:4:"algo";i:9;}


Encore quelque chose de peu compréhensible... Mais on retrouve encore nos valeurs, et aussi les clefs !

Mais comment ça marche ?


Pour comprendre, testons ce code :
Code : PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
        $entier = 17;
        $bool = TRUE;
        $float = 0.75669;
        $chaine = "ma chaine";
        $array = array(0,4,7,3);
        $array2 = array('truc'=>'machin', 'chose'=>8, 9=>'youpie');
        echo serialize($entier);
        echo '<br />';
        echo serialize($bool);
        echo '<br />';
        echo serialize($float);
        echo '<br />';
        echo serialize($chaine);
        echo '<br />';
        echo serialize($array);
        echo '<br />';
        echo serialize($array2);
?>

Citation : Affichage
i:17;
b:1;
d:0.75668999999999997374544591366429813206195831298828125;
s:9:"ma chaine";
a:4:{i:0;i:0;i:1;i:4;i:2;i:7;i:3;i:3;}
a:3:{s:4:"truc";s:6:"machin";s:5:"chose";i:8;i:9;s:6:"youpie";}


serialize() prend la variable, et cherche d'abord son type. Cette définition de type sera conservée (i pour integer, b pour booléen, d pour double, s pour string et a pour array). Selon ce type, il va chercher le nombre d'éléments, ou non. Ce nombre d'éléments peut être la longueur d'une chaîne de caractères, ou le nombre d'éléments d'un tableau.
Ensuite, serialize définit le contenu, selon ce type. Dans le cas d'un entier, le contenu ne change pas. serializer un entier reviendrait donc juste à grossir la taille (en mémoire) prise par la variable... Oublions donc les entiers.
Pour un booléen, serialize traduit la valeur en entier (TRUE devient 1 et FALSE devient 0) par transtypage classique. On revient à la même problématique qu'avec les entiers : on évitera de sérializer un booléen.
De même pour une chaîne, à noter que serialize place des guillemets autour des chaînes, ce qui peut être intéressant à savoir dans le cas d'un enregistrement en base de données.
Vous remarquerez sûrement que notre nombre décimal est devenu... quelque peu bizarre. Cela est dû à une mauvaise précision de calcul de serialize, qui n'est réellement pas fait pour traiter des données aussi simples.
La précision de serialize est indiquée dans le php.ini. Il s'agit de la directive serialize_precision dans PHP Core.

Passons à nos chers arrays !
Il semblerait que serialize répète la procédure précédente (déterminer le type, puis le nombre d'éléments, puis la valeur...) pour chaque élément. Une dimension d'un array est délimitée par des accolades.
Et les clefs n'en sont pas exemptes, et semblent traitées... comme des valeurs simples !

Mais alors, comment différencier les clefs des valeurs ?

C'est là que joue le nombre d'éléments. :)
Si on a un seul élément, mais qu'on a deux valeurs dans l'array sérializé, c'est qu'il s'agit d'une paire clef / valeur.
De toute façon, tout array est normalement indexé numériquement en interne, par php. D'où notre premier affichage.

Et comme on a ainsi une chaîne de caractères (conservant néanmoins parfaitement notre array), on peut facilement la manipuler, pour la transmettre de page en page, l'envoyer en base de données, ou encore l'écrire dans un fichier.

Ah ouais, super... Mais on ne peut rien en faire de cette chaîne de caractères !


Et pof ! Voici unserialize.

Cette superbe fonction est résumée en une phrase : elle fait l'inverse de serialize.
En partant d'une chaîne de caractères venue de serialize, unserialize récupère les données et renvoie... les données originales. Concrètement, cela signifie qu'en testant ceci...

Code : PHP
1
2
3
4
5
6
7
<?php
        $notes = array('maths'=>1,'svt'=>8,'algo'=>6,'philo'=>5); // un array...
        $serialized = serialize($notes); // on sérialize et on stocke cette chaîne
        echo '<pre>'; // les balises pre permettent d'afficher lisiblement un array
        print_r(unserialize($serialized)); // on utilise print_r pour une bonne raison
        echo '</pre>';
?>

Citation : Affichage
Array
(
[maths] => 1
[svt] => 8
[algo] => 6
[philo] => 5
)


On récupère notre array, intact. :)

Utilisations

On sait donc serializer et deserializer.
Apprenons donc à nous servir de cela, en passant par quelques exemples concrets.

Pour commencer, serializons un array de profondeur inconnue.

La profondeur d'un array, c'est le nombre de dimensions, mais certains disent qu'ils s'agit également du nombre d'éléments. J'ignore quelle définition est la bonne, mais personnellement je préfère dire qu'il s'agit du nombre de dimensions.


Code : PHP
1
2
3
<?php
        $srzed = serialize($array_inconnu);
?>


L'enregistrement dans un fichier


On va garder cet array pour la suite. Commençons par tenter de l'enregistrer en fichier.
Code : PHP
1
2
3
4
5
<?php
        $fh = fopen('test.txt','a+'); // ouverture d'un fichier en écriture/lecture, en le créant s'il n'existe pas
        fwrite($fh,$srzed); // on écrit
        fclose($fh); // on ferme
?>


Quoi de plus simple ?

Notez qu'il reste un problème : si le fichier trouve un caractère spécial pour les fichiers (\t, \n...), il va le traduire comme tel. Assurez-vous de vous protéger contre cela, par exemple en doublant le \ .

L'utilisation de serialize en barre d'adresse


Un autre exemple, à présent : la transmission via GET.
En effet, une chaîne peut facilement être transmise dans l'url.
Testons cela :

Code : PHP
1
2
3
4
<?php
        header("Location:autrepage.php?data=".$srzed); // une simple redirection, mais avec des données GET
        exit;
?>


Ce code générera probablement... une erreur. C'est normal. Certains caractères ne sont pas supportés dans les URL ou ont un sens bien particulier, notemment ces ';' qui sont si présents dans un array sérializé...
Mais une solution simple à cela est alors d'utiliser urlencode, qui encode ces caractères non-supportés ou à sens particulier, pour le passage via URL. :)

Testons à nouveau !

Code : PHP
1
2
3
4
<?php
        header("Location:autrepage.php?data=".urlencode($srzed)); // une simple redirection, mais avec des données GET
        exit;
?>


Et autrepage.php
Code : PHP
1
2
3
4
5
6
<?php
        $arrayornot = unserialize(urldecode($_GET['datas']));
        echo '<pre>';
        print_r($arrayornot);
        echo '</pre>';
?>


Magique, n'est-ce pas ?

L'enregistrement en base de données


Testons à présent l'insertion en BDD, sans doute le plus attractif pour beaucoup.
Le principe reste le même : on utilise serialize sur l'array, et on enregistre. Mais il faut juste ajouter l'étape de protection des données. Certains utilisent mysql_real_escape_string, d'autres addslashes. Je pense qu'il s'agit de préférences, même si mysql_real_escape_string <strong>devrait</strong> être utilisé.
Ne négligez JAMAIS l'influence de ces petits caractères " et ' dans une requête SQL, de même que les antislashes. Ces caractères sont parmi les plus dangereux si non-contrôlés. Et ce n'est pas parce que vous n'avez pas d'erreur que tout va bien...


Testons :
Code : PHP
1
2
3
<?php
        mysql_query("INSERT INTO matable VALUES(NULL, 'serialize', '".mysql_real_escape_string($srzed)."')") or die(mysql_error());
?>

Quand je dis que c'est magique. :)

Encore plus fort !

Attendez, c'est pas fini !



Si je vous dis, à présent, que les array ne sont pas les seuls types à être intéressants à sérializer...
Code : PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php
        class test
        {
                var $truc;
                var $machin;
                function test() // constructeur, peut aussi être appellé __construct en php5
                {
                        $this->truc = mt_rand(1,99);
                        $this->machin = array('truc'=>array(5,8,7),'chose'=>5,array('t','y','u','p'));
                }
        }
        $object = new test();
        echo serialize($object);
?>

Citation : Affichage
O:4:"test":2:{s:4:"truc";i:37;s:6:"machin";a:3:{s:4:"truc";a:3:{i:0;i:5;i:1;i:8;i:2;i:7;}s:5:"chose";i:5;i:0;a:4:{i:0;s:1:"t";i:1;s:1:"y";i:2;s:1:"u";i:3;s:1:"p";}}}

Wôh. Là, encore plus de possibilités, même si le tout reste identique. Le modèle semble très proche de celui des arrays : l'objet est délimité par des accolades.
Le nom de l'objet étant une chaîne de caractères, serialize précise le nombre de caractères de ce nom. Suivi du nombre d'éléments (propriétés) dans l'objet.
On peut donc utiliser serialize sur des objets.
Le nom de l'objet est conservé, ce qui permet, par exemple, de conserver des objets de diverses natures, comme un élément SimpleXML. :)
La transmission d'un objet par GET, ou son stockage en fichier ou BDD est alors... réglé !

__sleep et __wakeup



Des méthodes magiques __sleep et __wakeup peuvent être définies spécialement pour serialize et unserialize. __sleep sera appellée automatiquement lors de serialize, et fera son office AVANT la linéarisation. __wakeup sera appellée automatiquement lors d'unserialize, et fera ses opérations APRÈS la délinéarisation.
Cela peut être très utile dans certains cas, comme pour une connexion à une base de données, fermée par __sleep et relancée via __wakeup...
À noter que __sleep et __wakeup sont disponibles depuis PHP4.
Je le précise, car on aurait tendance à penser, à cause de l'existence de __construct, que les méthodes magiques de classe datent de php5. Ce n'est pas le cas.


Comportement de __sleep



Si __wakeup se comporte de manière relativement normale, il n'en est pas de même pour __sleep. Vous devez en effet systématiquement renvoyer un array contenant en valeurs les noms littéraux des variables.
Prenons cet exemple :
Code : PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
class test
{
        var $truc;
        function test() // constructeur, peut aussi être appelé __construct en php5
        {
                $this->truc = mt_rand(1,24);
        }
        
        function __sleep()
        {
                $this->truc *= 7;
                $this->truc -= 3;
                return array('truc'); // on renvoie un array contenant le nom littéral de la varible, cad 'truc' !
        }
        
        function __wakeup()
        {
                $this->truc += 3;
                $this->truc /= 7;
        }
}
 
$object = new test();
 
echo '<pre>';
print_r($object); // sert à afficher de façon propre les propriétés d'un objet
echo '</pre>';
 
$mid = serialize($object);
 
echo $mid;
 
$last = unserialize($mid);
 
echo '<pre>';
print_r($last); // sert à afficher de façon propre les propriétés d'un objet
echo '</pre>';
?>

Le return array('truc'); renvoie en réalité...
Citation : Affichage
O:4:"test":1:{s:4:"truc";i:53;}

En réalité, __sleep "met" l'array renvoyé dans l'objet en cours de linéarisation. Attention, car si vous ne faites pas ainsi, serialize linéarisera la valeur NULL, ce qui donnera 'N'...
Mais si vous regardez bien, ce comportement particulier nous permettrait de modifier une variable pour en "camoufler" la valeur réelle, ou encore de ne renvoyer que certaines propriétés de l'objet (il n'est parfois pas nécessaire voire inutile de linéariser et enregistrer certaines propriétés)...
Ce comportement atypique peut donc nous permettre d'agir en conséquence. :)


Que pourrait-il nous rester à apprendre ?

Utilisation plus atypique


Vous allez me dire qu'un array sérializé est inutilisable dans une requête SELECT. En effet, ça semble pas vraiment commode, mais... C'est possible !

Rappelons-nous de la structure d'un array linéarisé :
a:(nombre d'éléments):{(valeurs)}
ainsi que celui d'un élément d'array linéarisé :
(type élément)[:(nombre de caractères si c'est une chaîne)]:(valeur);

En sachant cela, on sait que pour rechercher l'entier 4, on va rechercher en fait la chaîne 'i:4;'.
En partant de là, on peut facilement faire de petites recherches en SQL...
Un exemple simple et typique (et non-optimisé à mon avis : amateurs de SQL, les suggestions sont bienvenues) :
Code : SQL
1
SELECT * FROM TABLE WHERE `champ` LIKE '%i:4;%'

Et voilà comment on peut rechercher les entrées dont le champ `champ` contient l'entier 4.
serialize, c'est magique. :)

Q.C.M.

Quelle est la fonction permettant de linéariser une variable ?
Parmi ces types de variables, lequel ne &lt;gras&gt;peut&lt;/gras&gt;-on pas linéariser ?
Parmi ces types de variables, lequel ne devrait-on pas linéariser ?
Quelle méthode est automatiquement appelée (si trouvée) lors de l'utilisation de unserialize sur un objet ?
On a une table `notes`, avec deux champs : le nom de l'élève (`eleves`), et ses moyennes (`moyennes`).
Dans le champ `moyennes`, on a stocké un array linéarisé.
La forme première de l'array est du genre :
Code : Autre
1
2
3
4
5
Array
{
  'matiere1'=>note1,
  etc...
}

MAIS certains élèves ont des matières que d'autres n'ont pas ! Dans ce cas, ces matières ne seront pas présentes. Par exemple, imaginons Jim :
Code : PHP
1
<?PHP array('svt'=>7,'algo'=>9,'anglais'=>5); ?>

Et imaginons Bob :
Code : PHP
1
<?PHP array('svt'=>8,'methodo'=>7,'anglais'=>3); ?>


Quelle est la requête SQL pour rechercher tous les élèves ayant une moyenne dans une matière précise ?


Quand je parle de moyenne, il s'agit juste d'un élément d'array (la paire clef / valeur, ou plutôt, ici, la paire 'matière'=>note).
Pour faire simple, on va ici rechercher tous les élèves qui ont eu 'algo'.
On note qu'il existe aussi une matière nommée 'walgon' (un professeur a mal ortographié 'wagon', une matière portant sur l'art et la manière d'imaginer des wagons)...

Alors, quelle serait la meilleure manière ?


Comme vous avez pu le constater, le duo serialize / unserialize est vraiment puissant, permettant ainsi de grandes choses pour une action aussi simple.

Juste une chose : les ressources ne sont pas linéarisables via serialize. La fonction transtypera la ressource en entier (donc 0), et donnera donc i:0; ...

Mais entraînez-vous, par exemple, à développer une classe SQL manipulant les objets __sleep et __wakeup pour conserver des données de classes d'une page à l'autre...

Un autre exemple (dont j'abuse, personnellement) est celui de linéariser un array pour le transmettre via GET (je l'utilise pour les fichiers php générant une image via gd) en passant par urlencode...

Et voilà, vous savez à présent pas mal de choses sur la (dé)linéarisation via serialize / unserialize !
Auteur : Lpu8er
Noter et commenter ce tutoriel
Imprimer ce tutoriel

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | Fil RSS | XHTML 1.0 | CSS 2.0
Édité par Simple IT SARL : Nous contacter | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 564 Zéros connectés | Requêtes SQL 8 requêtes | Temps de génération de la page : Total (SQL) 0.5454s (0.528s)