<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Cloud Connected &#187; pdo</title>
	<atom:link href="http://www.cloudconnected.fr/tag/pdo/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.cloudconnected.fr</link>
	<description>Thoughts of a french web developer</description>
	<lastBuildDate>Wed, 01 Feb 2012 08:53:57 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Internationalisation d&#8217;une base de données</title>
		<link>http://www.cloudconnected.fr/2009/03/06/internationalisation-dune-base-de-donnees/</link>
		<comments>http://www.cloudconnected.fr/2009/03/06/internationalisation-dune-base-de-donnees/#comments</comments>
		<pubDate>Fri, 06 Mar 2009 15:19:16 +0000</pubDate>
		<dc:creator>Rémi</dc:creator>
				<category><![CDATA[Non classé]]></category>
		<category><![CDATA[i18n]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[pdo]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://www.the-asw.com/?p=407</guid>
		<description><![CDATA[Disons que vous soyez en charge d&#8217;une application web écrite en PHP/MySQL, par exemple l&#8217;intranet de votre entreprise, et que vous ayez soudainement besoin de l&#8217;internationaliser parce que votre entreprise installe des bureaux à l&#8217;étranger et que, malheureusement, notre merveilleuse langue française n&#8217;est pas parlée dans tous les pays du monde. Pour les templates, pas [...]]]></description>
			<content:encoded><![CDATA[<p>Disons que vous soyez en charge d&#8217;une application web écrite en PHP/MySQL, par exemple l&#8217;intranet de votre entreprise, et que vous ayez soudainement besoin de l&#8217;internationaliser parce que votre entreprise installe des bureaux à l&#8217;étranger et que, malheureusement, notre merveilleuse langue française n&#8217;est pas parlée dans tous les pays du monde.</p>
<p>Pour les templates, pas de problème, <code>gettext</code> est là pour ça (je reviendrais peut-être dessus dans un futur article, si je suis motivé). Si vous n&#8217;avez pas envie de mettre les mains dans le système, votre framework propose sûrement une émulation plus ou moins performante en pur PHP, ou, au pire, une solution &#8220;maison&#8221;. Bref, ça c&#8217;est facile.</p>
<p>Ce qui pose plus de problèmes, ce sont les données présentes en base. Par exemple : la liste de catégories pour les tickets d&#8217;incidents des clients. Elle est stockée en base dans une table qui contient notamment l&#8217;intitulé de cette catégorie. Oui, mais cet intitulé doit être traduit. Et comme les catégories sont gérées dynamiquement, ce n&#8217;est pas envisage d&#8217;utiliser la méthode <code>gettext</code> qui repose sur des fichiers statiques.</p>
<h3>Transformer le modèle</h3>
<p>Nous allons commencer par ajouter une table pour stocker les traductions. Si on envisage la catégorie comme un élément identifié par un id uniquement, les traductions sont liées à une catégorie par une relation &#8220;1-N&#8221; : 1 catégorie possède N traductions. Chaque traduction est identifiée par un code de langue. L&#8217;idée est de retirer les champs contenant du texte à traduire de la catégorie, et de les stocker dans la table contenant les traductions</p>
<div id="attachment_408" class="wp-caption aligncenter" style="width: 395px"><a href="http://www.cloudconnected.fr/wp-content/uploads/2009/03/i18n.png"  rel="lightbox"><img src="http://www.cloudconnected.fr/wp-content/uploads/2009/03/i18n.png" alt="Schéma de la base" title="i18n" width="385" height="142" class="size-full wp-image-408" /></a><p class="wp-caption-text">Schéma de la base</p></div>
<p>On crée donc les tables suivantes :</p>
<pre>
CREATE TABLE `category` (
	`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
	`created_at` DATETIME NOT NULL,
	`deleted_at` DATETIME NULL,
	PRIMARY KEY (`id`)
) ENGINE = InnoDB;

CREATE TABLE `category_i18n` (
	`category_id` INT UNSIGNED NOT NULL,
	`lang` VARCHAR(6) NOT NULL,
	`name` VARCHAR(45) NOT NULL,
	PRIMARY KEY (`lang`, `category_id`),
	INDEX `category_i18n_category_fk` (`category_id` ASC),
	CONSTRAINT `category_i18n_category_fk`
		FOREIGN KEY (`category_id` )
		REFERENCES `category` (`id` )
		ON DELETE CASCADE
) ENGINE = InnoDB;
</pre>
<p><span id="more-407"></span></p>
<h3>Insertion</h3>
<p>Lors de l&#8217;insertion, il faut d&#8217;abord insérer dans <code>category</code>, récupérer l&#8217;id puis insérer dans <code>category_i18n</code>. Puisqu&#8217;on travaille avec plusieurs tables en même temps, l&#8217;utilisation d&#8217;une transaction (avec le moteur de stockage InnoDB) est fortement recommandée.</p>
<pre>
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO category (created_at) VALUES (SYSDATE());
Query OK, 1 row affected (0.00 sec)

mysql> SET @category_id = LAST_INSERT_ID();
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO category_i18n (category_id, lang, name) VALUES
    -> (@category_id, 'fr', 'Incident réseau'),
    -> (@category_id, 'en', 'The network is fucked up');
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
</pre>
<p>Et ça donne :</p>
<pre>
mysql> SELECT * FROM category;
+----+---------------------+------------+
| id | created_at          | deleted_at |
+----+---------------------+------------+
|  3 | 2009-03-06 15:58:44 | NULL       |
+----+---------------------+------------+
1 row in set (0.00 sec)

mysql> SELECT * FROM category_i18n;
+-------------+------+--------------------------+
| category_id | lang | name                     |
+-------------+------+--------------------------+
|           3 | en   | The network is fucked up |
|           3 | fr   | Incident réseau          |
+-------------+------+--------------------------+
2 rows in set (0.00 sec)
</pre>
<h4>Exemple d&#8217;implémentation en utilisant PDO</h4>
<pre>
// $names contient un tableau associatif "langue => valeur"
function newCategory($dbh, $names = array())
{
	if ( empty($names) )
		return false;

	try {
		$dbh->beginTransaction();

		$sql =
			'INSERT INTO category
			(created_at)
			VALUES (SYSDATE())';

		$dbh->exec($sql);
		$id = $dbh->lastInsertId();

		$sql = array();
		foreach ( $names as $lang => $name ) {
			$sql[] = sprintf(
				'(%d, %s, %s)',
				$id,
				$dbh->quote($lang),
				$dbh->quote($name)
			);
		}
		$sql =
			'INSERT INTO category_i18n
			(category_id, lang, name)
			VALUES '.implode(', ',$sql);
		$dbh->exec($sql);

		$dbh->commit();
	} catch (Exception $e) {
		$dbh->rollback();
		throw $e;
	}
}

// appel de la fonction
$dbh = new PDO('mysql:host=localhost;dbname=test', 'test', 'test');
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

newCategory($dbh, array(
	'fr' => 'Incident réseau',
	'en' => 'The network is fucked up'
));
newCategory($dbh, array(
	'fr' => 'Incident système',
	'en' => 'The system screwed up'
));
newCategory($dbh, array(
	'fr' => 'Commande de matériel',
	'en' => 'I need some stuffs'
));
newCategory($dbh, array(
	'fr' => 'Problème typiquement français'
));
</pre>
<h3>Sélection</h3>
<p>Pour récupérer la liste des catégories, il faut savoir dans quelle langue on les veut. Pour cela, on peut se baser sur les préférences indiquée par le navigateur de l&#8217;utilisateur, ou sur ses options s&#8217;il est inscrit, etc. La requête de sélection se fait ensuite par une simple jointure.</p>
<p>Dans l&#8217;exemple suivant, on sélectionne d&#8217;abord les catégories en français et ensuite les catégories en anglais. La catégorie non-traduite n&#8217;apparaît pas. On pourrait utiliser un <code>LEFT JOIN</code> pour avoir un libellé <code>NULL</code> (ça peut servir dans certains cas).</p>
<pre>
mysql> SELECT c.id, ci.name
    -> FROM category c
    -> JOIN category_i18n ci ON (ci.category_id = c.id AND ci.lang = "fr")
    -> WHERE c.deleted_at IS NULL;
+----+-------------------------------+
| id | name                          |
+----+-------------------------------+
|  1 | Incident réseau               |
|  2 | Incident système              |
|  3 | Commande de matériel          |
|  4 | Problème typiquement français |
+----+-------------------------------+
4 rows in set (0.00 sec)

mysql> SELECT c.id, ci.name
    -> FROM category c
    -> JOIN category_i18n ci ON (ci.category_id = c.id AND ci.lang = "en")
    -> WHERE c.deleted_at IS NULL;
+----+--------------------------+
| id | name                     |
+----+--------------------------+
|  1 | The network is fucked up |
|  2 | The system screwed up    |
|  3 | I need some stuffs       |
+----+--------------------------+
3 rows in set (0.00 sec)

mysql> SELECT c.id, ci.name
    -> FROM category c
    -> LEFT JOIN category_i18n ci ON (ci.category_id = c.id AND ci.lang = "en")
    -> WHERE c.deleted_at IS NULL;
+----+--------------------------+
| id | name                     |
+----+--------------------------+
|  1 | The network is fucked up |
|  2 | The system screwed up    |
|  3 | I need some stuffs       |
|  4 | NULL                     |
+----+--------------------------+
4 rows in set (0.00 sec)
</pre>
<h3>Modification</h3>
<p>La modification peut être triviale, ou un petit peu complexe, selon le type d&#8217;interface utilisateur mise en place. Quelques astuces : si un simple <code>UPDATE</code> ne suffit pas, vous devriez regarder du côté de <code>INSERT ... ON DUPLICATE KEY UPDATE ...</code>, ou implémenter une suppression complète immédiatement suivi de nouvelles insertions (toujours avec une transaction).</p>
<h3>Suppression</h3>
<p>Grâce à la clause <code>ON DELETE CASCADE</code> il suffit de supprimer l&#8217;entrée dans catégorie pour que les traductions correspondantes soient également supprimées. Personnellement j&#8217;utilise le champ <code>deleted_at</code> pour effectuer une suppression logique et ne pas casser toutes les clés étrangères des tables qui font référence à la catégorie supprimée.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cloudconnected.fr/2009/03/06/internationalisation-dune-base-de-donnees/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Les fetch modes de PDO 4 : les modes modificateurs</title>
		<link>http://www.cloudconnected.fr/2007/10/17/les-fetch-modes-de-pdo-4-les-modes-modificateurs/</link>
		<comments>http://www.cloudconnected.fr/2007/10/17/les-fetch-modes-de-pdo-4-les-modes-modificateurs/#comments</comments>
		<pubDate>Wed, 17 Oct 2007 18:00:15 +0000</pubDate>
		<dc:creator>Rémi</dc:creator>
				<category><![CDATA[Non classé]]></category>
		<category><![CDATA[pdo]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://www.the-asw.com/?p=129</guid>
		<description><![CDATA[Dernière partie de ma série consacrée aux modes de récupération de PDO, voici les modes que j&#8217;appelle &#8220;modificateurs&#8221;, car ils ne peuvent pas être utilisés tout seuls et se contentent de modifier le comportement d&#8217;un autre mode. Pour utiliser un modificateur, il suffit d&#8217;effectuer un &#8220;ou&#8221; binaire, par exemple : $results->fetchAll(PDO::FETCH_TRUC &#124; PDO::FETCH_BIDULE &#124; PDO::FETCH_CHOUETTE); [...]]]></description>
			<content:encoded><![CDATA[<p>Dernière partie de <a href="/2007/10/10/les-fetch-modes-de-pdo/">ma série consacrée aux modes de récupération de PDO</a>, voici les modes que j&#8217;appelle &#8220;modificateurs&#8221;, car ils ne peuvent pas être utilisés tout seuls et se contentent de modifier le comportement d&#8217;un autre mode.</p>
<p>Pour utiliser un modificateur, il suffit d&#8217;effectuer un &#8220;ou&#8221; binaire, par exemple :</p>
<pre>$results->fetchAll(PDO::FETCH_TRUC | PDO::FETCH_BIDULE | PDO::FETCH_CHOUETTE);</pre>
<p>La plupart de ces modes ne fonctionnent pas lorsqu&#8217;ils sont définis par la méthode <code>setFetchMode</code>. Ça ressemble a un bug, mais vu que rien n&#8217;est documenté, c&#8217;est peut-être normal&#8230;</p>
<p><span id="more-129"></span></p>
<h3>FETCH_CLASSTYPE</h3>
<p>Ce mode est destiné à être utilisé conjointement avec <code>FETCH_CLASS</code>. Lorsqu&#8217;il est présent, la première colonne de la requête SQL indique le nom de la classe à utiliser.</p>
<p>Exemple :</p>
<pre>
class MyClass1 { }
class MyClass2 { }
$sql = 'SELECT CONCAT("MyClass",id), id, login FROM user LIMIT 0,2';
$results->fetchAll(PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE);
</pre>
<pre>
Array
(
  [0] => MyClass1 Object
    (
      [id] => 1
      [login] => sload
    )

  [1] => MyClass2 Object
    (
      [id] => 2
      [login] => js
    )

)
</pre>
<p>Si une classe n&#8217;existe pas, <code>stdClass</code> est utilisé (comme <code>FETCH_OBJ</code>).</p>
<p>Je suis vraiment curieux de savoir à quoi ce mode peut bien servir !</p>
<h3>FETCH_GROUP</h3>
<p>Ce mode est plutôt intéressant : il permet de regrouper les résultats selon la valeur de la première colonne de la requête. Il ne fonctionne qu&#8217;avec <code>fetchAll</code>.</p>
<p>Exemple, avec <code>FETCH_ASSOC</code> :</p>
<pre>
$sql = 'SELECT hash_algo, id, login FROM user LIMIT 0,4';
$results->fetchAll(PDO::FETCH_ASSOC | PDO::FETCH_GROUP);
</pre>
<pre>
Array
(
  [sha1] => Array
    (
      [0] => Array
        (
          [id] => 1
          [login] => toto
        )

    )

  [md5] => Array
    (
      [0] => Array
        (
          [id] => 2
          [login] => titi
        )

      [1] => Array
        (
          [id] => 3
          [login] => tata
        )

      [2] => Array
        (
          [id] => 4
          [login] => tutu
        )
    )
)
</pre>
<h3>FETCH_UNIQUE</h3>
<p>Ce mode retourne les résultats indexés selon la valeur de la première colonne de la requête. Outre l&#8217;aspect &#8220;suppression des doublons&#8221; (qui est une conséquence naturellement de l&#8217;indexation), ce mode est intéressant car il retourne un tableau associatif.</p>
<p>Par exemple, avec <code>FETCH_ASSOC</code>, ce mode permet d&#8217;obtenir directement les utilisateurs indexés par leur id :</p>
<pre>
$sql = 'SELECT id, login, email FROM user LIMIT 0,2';
print_r($results->fetchAll(PDO::FETCH_ASSOC | PDO::FETCH_UNIQUE));
</pre>
<pre>
Array
(
  [1] => Array
    (
      [login] => toto
      [email] => toto@foo
    )

  [2] => Array
    (
      [login] => titi
      [email] => titi@foo
    )
)
</pre>
<p>Et comme ce mode utilise moins de mémoire, il est un peu plus rapide que <code>FETCH_ASSOC</code> seul quand il n&#8217;y a pas de doublons.</p>
<h3>FETCH_SERIALIZE</h3>
<p>Pour l&#8217;instant je n&#8217;ai pas réussi à obtenir de ce mode autre chose que des messages du genre : <q>SQLSTATE[HY000]: General error: PDO::FETCH_SERIALIZE can only be used together with PDO::FETCH_CLASS</q> ou <q>SQLSTATE[HY000]: General error: cannot unserialize class</q>. J&#8217;ai donc ouvert <a href="http://bugs.php.net/bug.php?id=42916">un bug (#42916)</a>.</p>
<h3>FETCH_PROPS_LATE</h3>
<p>Ce mode est destiné à être utilisé conjointement avec <code>FETCH_CLASS</code> pour que les propriétés de la classe soient écrites *après* l&#8217;appel du constructeur.</p>
<p>Et voila, c&#8217;est la fin de la série d&#8217;articles sur les fetch modes de PDO&#8230; Tout feedback est le bienvenue ! Et maintenant il est temps d&#8217;optimiser ses scripts PHP :-)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cloudconnected.fr/2007/10/17/les-fetch-modes-de-pdo-4-les-modes-modificateurs/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Les fetch modes de PDO 3 : les modes spéciaux</title>
		<link>http://www.cloudconnected.fr/2007/10/15/les-fetch-modes-de-pdo-3-les-modes-speciaux/</link>
		<comments>http://www.cloudconnected.fr/2007/10/15/les-fetch-modes-de-pdo-3-les-modes-speciaux/#comments</comments>
		<pubDate>Mon, 15 Oct 2007 05:56:09 +0000</pubDate>
		<dc:creator>Rémi</dc:creator>
				<category><![CDATA[Non classé]]></category>
		<category><![CDATA[pdo]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://www.the-asw.com/?p=127</guid>
		<description><![CDATA[Suite de ma série consacrée aux modes de récupération de PDO, voici les modes &#8220;spéciaux&#8221;, c&#8217;est à dire des modes au comportement particulier, très mal documentés mais souvent très pratiques ! FETCH_LAZY Il se comporte comme un mélange de FETCH_OBJ et FETCH_ASSOC puisque l&#8217;objet retourné peut être accédé comme un objet ($r->id) ou comme un [...]]]></description>
			<content:encoded><![CDATA[<p>Suite de <a href="/2007/10/10/les-fetch-modes-de-pdo/">ma série consacrée aux modes de récupération de PDO</a>, voici les modes &#8220;spéciaux&#8221;, c&#8217;est à dire des modes au comportement particulier, très mal documentés mais souvent très pratiques !</p>
<p><span id="more-127"></span></p>
<h3>FETCH_LAZY</h3>
<p>Il se comporte comme un mélange de <code>FETCH_OBJ</code> et <code>FETCH_ASSOC</code> puisque l&#8217;objet retourné peut être accédé comme un objet (<code>$r->id</code>) ou comme un tableau associatif (<code>$r['id']</code>). Mais, comme son nom l&#8217;indique, il semble faire des bidouilles interressantes au niveau de la gestion de la mémoire ; d&#8217;après la documentation de PHP <q>PDO::FETCH_LAZY crée les noms des variables de l&#8217;objet comme ils sont rencontrés.</q></p>
<p>A noter que ce mode ne peut-être utilisé qu&#8217;avec <code>fetch</code>, sans doute en raison de la gestion de la mémoire. De plus, le resultat affiché avec <code>print_r</code> ne semble par correspondre puisqu&#8217;on obtient :</p>
<pre>
PDORow Object
(
  [queryString] => SELECT id, login FROM user LIMIT 0,2
  [id] => 1
  [login] => toto
)
PDORow Object
(
  [queryString] => SELECT id, login FROM user LIMIT 0,2
  [id] => 2
  [login] => titi
)
</pre>
<p>Les performances par rapport à <code>FETCH_BOTH</code> sont étonnament bonnes (en comparant avec <code>while + fetch</code>) :</p>
<ul>
<li>Temps de récupération : <strong style="color:green">-18,9%</strong></li>
<li>Taille du résultat : <strong style="color:green">-77,8%</strong></li>
</ul>
<p>Toutefois, ces résultats doivent être considérés avec précaution : puisque ce mode n&#8217;utilise pas la mémoire de la même façon que les autres, mon script de test ne donne pas forcement des résultats pertinants. En pratique, sur une application réelle, pour afficher une liste d&#8217;éléments par exemple, en utilisant <code>FETCH_LAZY</code> plutôt que <code>FETCH_ASSOC</code>, le gain de mémoire est minime (de l&#8217;ordre de 0,05Ko, certes c&#8217;est toujours bon à prendre) et je n&#8217;observe pas de gain de vitesse significatif.</p>
<p>Pour l&#8217;instant, j&#8217;utilise ce mode quand les résultats ne vont pas être utilisés immédiatement, par exemple dans le cadre d&#8217;une architecture MVC où la requête est effetuée dans le modèle (ou dans le controlleur) et les résultats ne sont récupérés et affichés que plus tard dans la vue.</p>
<h3>FETCH_COLUMN</h3>
<p>Ce mode permet de récupérer uniquement une colonne, en précisant son numéro. Il fonctionne avec <code>setFechMode</code>, <code>fetchAll</code> et même directement <code>query</code>. Il fonctionne également avec <code>fetch</code>, mais il n&#8217;est pas possible de préciser le numéro de la colonne à récupérer (défaut 0). Les colonnes sont numérotées à partir de 0.</p>
<p>Attention, lorsque vous utilisez <code>setFetchMode</code> pour préciser le mode, il n&#8217;y a pas de valeur par défaut pour le numéro de colonne. Donc si le deuxième paramètre est absent, PDO retourne au mode par défaut <code>FETCH_BOTH</code>.</p>
<p>Exemple :</p>
<pre>$results->fetchAll(PDO::FETCH_COLUMN, 1) // colonne 1 = le login</pre>
<pre>
Array
(
  [0] => toto
  [1] => titi
)
</pre>
<p>Par rapport à <code>FETCH_BOTH</code> :</p>
<ul>
<li>Temps de récupération : <strong style="color:green">-18,8%</strong></li>
<li>Taille du résultat : Non significatif (une seule colonne retournée)</li>
</ul>
<p>Selon moi, pour une requête avec plusieurs colonnes, ce mode est inutile et présente les même iconvénients que <code>FETCH_NUM</code> (le numéro des colonnes changent si on change la requête, ce qui peut casser toute l&#8217;application). Autant modifier la requête pour ne récupérer qu&#8217;une seule colonne, en plus on économise sur le temps de transfert entre la base de données et l&#8217;application.</p>
<p>Et justement, ce mode devient réellement interressant lorsqu&#8217;il est utilisé pour une requête d&#8217;une seule colonne. En effet, avec <code>FETCH_NUM</code> on obtient un tableau de tableaux d&#8217;une seule case, par exemple :</p>
<pre>
Array
(
  [0] => Array
    (
      [0] => toto
    )
  [1] => Array
    (
      [0] => titi
    )
)
</pre>
<p>Avec <code>FETCH_COLUMN</code>, le resultat est donc nettement plus petit (et directement exploitable, parcequ&#8217;un tableau de tableaux c&#8217;est vraiment pas pratique), et le temps de récupération est légerement plus court :</p>
<ul>
<li>Temps de récupération : <strong style="color:green">-9%</strong></li>
<li>Taille du résultat : <strong style="color:green">-62%</strong> (!)</li>
</ul>
<p>En résumé, pour récupérer une liste de valeurs, utilisez <code>FETCH_COLUMN</code> !</p>
<h3>FETCH_KEY_PAIR</h3>
<p>Nous venons de voir une méthode simple d&#8217;optimisation pour les requetes à une colonne. <code>FETCH_KEY_PAIR</code> est en quelque sorte l&#8217;équivalent pour les requêtes à 2 colonnes : il permet d&#8217;obtenir directement un tableau associatif avec la première colonne comme clef et la seconde comme valeur. J&#8217;adore !</p>
<p>Attention : ce mode ne fonctionne qu&#8217;avec <code>fetchAll</code> (lorsqu&#8217;on l&#8217;utilise avec <code>setFetchMode</code> ça provoque une erreur, voir <a href="http://bugs.php.net/bug.php?id=42917">bug #42917</a>).</p>
<p>Exemple :</p>
<pre>
// SELECT id, login FROM user u';
$results->fetchAll(PDO::FETCH_KEY_PAIR);
</pre>
<pre>
// id => login
Array
(
  [1] => toto
  [2] => titi
)
</pre>
<p>Par rapport avec <code>FETCH_BOTH</code> :</p>
<ul>
<li>Temps de récupération : <strong style="color:green">-14,8%</strong></li>
<li>Taille du résultat : <strong style="color:green">-78%</strong> (!)</li>
</ul>
<p>Ce mode est tout simplement génial dès qu&#8217;il s&#8217;agit de récupérer des couples de valeurs. Un exemple de cas très fréquent pour moi est de récupérer un id et un label dans une table de référence pour construire une liste déroulante dans un formulaire. Utiliser <code>FETCH_KEY_PAIR</code> au lieu de <code>FETCH_NUM</code> (ou <code>FETCH_ASSOC</code>) puis de construire le tableau associatif à la main permet d&#8217;économiser un peu de temps et beaucoup de mémoire. Bref, <code>FETCH_KEY_PAIR</code> c&#8217;est bon, mangez-en.</p>
<h3>FETCH_BOUND</h3>
<p>Ce mode permet d&#8217;extraire les valeurs directement dans des variables PHP, préalablement liées aux colonnes. Personnellement je ne comprends pas l&#8217;interet de ce mode et je ne l&#8217;ai jamais utilisé, mais si ça interresse quelqu&#8217;un, on peut trouver des exemples dans la doc de la fonction <a href="http://fr2.php.net/manual/fr/function.PDOStatement-bindColumn.php">bindColumn</a>.</p>
<h3>FETCH_FUNC</h3>
<p>Ce mode permet de définir une fonction callback pour récupérer les données. Ce mode ne fonctionne qu&#8217;avec <code>fetchAll</code> (si vous essayez de l&#8217;utiliser avec <code>setFetchMode</code> vous aurez droit à un joli message d&#8217;erreur du genre <q>SQLSTATE[HY000]: General error: PDO::FETCH_FUNC is only allowed in PDOStatement::fetchAll()</q>).</p>
<p>La fonction callback reçoit en paramètre toutes les colonnes (<code>func_get_args</code> peut être très utile) et doit retourner le résultat voulu.</p>
<p>Exemple pour émuler le comportement de <code>FETCH_ASSOC</code> :</p>
<pre>
function my_callback($id, $login)
{
	return array('id' => $id, 'login' => $login);
}
$results->fetchAll(PDO::FETCH_FUNC, 'my_callback');
</pre>
<pre>
Array
(
  [0] => Array
    (
      [id] => 1
      [login] => toto
    )

  [1] => Array
    (
      [id] => 2
      [login] => titi
    )
)
</pre>
<p>Le temps d&#8217;execution dépend évidemment du contenu de la fonction. A titre indicatif, avec le callback présenté en exemple, qui émule <code>FETCH_ASSOC</code> prend 70% plus de temps que la version native.</p>
<p>A suivre : <a href="/2007/10/17/les-fetch-modes-de-pdo-4-les-modes-modificateurs/">les modes modificateurs</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cloudconnected.fr/2007/10/15/les-fetch-modes-de-pdo-3-les-modes-speciaux/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Les fetch modes de PDO 2 : les modes orientés objet</title>
		<link>http://www.cloudconnected.fr/2007/10/12/les-fetch-modes-de-pdo-2-les-modes-orientes-objet/</link>
		<comments>http://www.cloudconnected.fr/2007/10/12/les-fetch-modes-de-pdo-2-les-modes-orientes-objet/#comments</comments>
		<pubDate>Fri, 12 Oct 2007 05:42:29 +0000</pubDate>
		<dc:creator>Rémi</dc:creator>
				<category><![CDATA[Non classé]]></category>
		<category><![CDATA[pdo]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://www.the-asw.com/?p=125</guid>
		<description><![CDATA[Suite de ma série consacrée aux modes de récupération de PDO, voici les modes qui permettent de travailler avec des objets. FETCH_OBJ Le tableau retourné contient les résultats sous forme d&#8217;instances de stdClass, contenant les valeurs des colonnes comme des variables publiques. Ce mode ne permet pas de récupérer plusieurs colonnes avec le même nom [...]]]></description>
			<content:encoded><![CDATA[<p>Suite de <a href="/2007/10/10/les-fetch-modes-de-pdo/">ma série consacrée aux modes de récupération de PDO</a>, voici les modes qui permettent de travailler avec des objets.</p>
<p><span id="more-125"></span></p>
<h3>FETCH_OBJ</h3>
<p>Le tableau retourné contient les résultats sous forme d&#8217;instances de <code>stdClass</code>, contenant les valeurs des colonnes comme des variables publiques. Ce mode ne permet pas de récupérer plusieurs colonnes avec le même nom (voir <code>FETCH_ASSOC</code>).</p>
<p>Exemple : </p>
<pre>
Array
(
  [0] => stdClass Object
    (
      [id] => 1
      [login] => toto
    )

  [1] => stdClass Object
    (
      [id] => 2
      [login] => titi
    )

)
</pre>
<p>Par rapport à <code>FETCH_BOTH</code> :</p>
<ul>
<li>Temps de récupération : <strong style="color:red">+7,6%</strong> (à noter qu&#8217;avec <code>while + fetch</code>, ça passe à <strong style="color:red">+30%</strong>)</li>
<li>Taille du résultat : <strong style="color:green">-15%</strong></li>
</ul>
<p>Cette méthode est plus lente que la méthode par défaut (qui est déjà particulièrement lente), et honnetement, je ne vois pas l&#8217;avantage d&#8217;un objet par rapport à un tableau associatif renvoyé par <code>FETCH_ASSOC</code>&#8230;</p>
<h3>FETCH_CLASS</h3>
<p>Ce mode est équivalent à <code>FETCH_OBJ</code> sauf qu&#8217;il permet d&#8217;utiliser une classe personnalisée pour stocker les résultats. Ce mode ne permet pas de récupérer plusieurs colonnes avec le même nom (voir <code>FETCH_ASSOC</code>).</p>
<p>Ce mode nécessite de passer, en plus du mode, des paramètres supplémentaires. D&#8217;après la documentation, il faudrait utiliser la fonction <code>setFetchMode</code> pour passer ces paramètres (ou directement au moment de faire <code>query</code>), mais en pratique, <code>fetchAll</code> les accepte aussi (alors qu&#8217;ils ne sont censés exister)&#8230; Par contre, <code>fetch</code> ne les accepte pas.</p>
<p>Le deuxième paramètre est donc le nom d&#8217;une classe. Si la classe contient déjà des variables correspondantes aux colonnes, PDO les rempli et se fiche pas mal de leur visibilité. Sinon, PDO crée les variables avec une visibilité publique. Un troisième paramètre (optionnel) permet de passer des paramètres au constructeur de la classe. A noter que quand le constructeur est appellé, les variables internes ont déjà une valeur (voir <code>FETCH_PROPS_LATE</code> dans le <a href="/2007/10/17/les-fetch-modes-de-pdo-4-les-modes-modificateurs/">dernier article de cette série</a> si ce comportement pose un problème).</p>
<p>Si la classe n&#8217;existe pas, le résultat est un tableau identique à celui obtenu avec <code>FETCH_BOTH</code> (si un autre mode par défaut est défini, il n&#8217;est pas pris en compte).</p>
<p>Exemple :</p>
<pre>
class MyClass
{
  function __construct()
  {
    var_dump(func_get_args()); // affiche 'foo' et 'bar'
    var_dump($this->id, $this->login); // affiche l'id et le login
  }
}

$results = $dbh->query($sql);
$results->fetchAll(PDO::FETCH_CLASS, 'MyClass', array('foo', 'bar'));
</pre>
<pre>
Array
(
  [0] => MyClass Object
    (
      [id] => 1
      [login] => toto
    )

  [1] => MyClass Object
    (
      [id] => 2
      [login] => titi
    )

)</pre>
<p>Les performances dépendent évidemment de la taille de la classe : plus il y a d&#8217;infos dedans, pire c&#8217;est. Avec une classe entièrement vide (c&#8217;est-à-dire avec meilleures performances possibles), on obtient, par rapport à <code>FETCH_BOTH</code> :</p>
<ul>
<li>Temps de récupération : <strong style="color:red">+6%</strong> (à noter qu&#8217;avec <code>while + fetch</code>, ça passe à <strong style="color:red">+32%</strong>)</li>
<li>Taille du résultat : dépend de la taille de la classe</li>
</ul>
<p>En déclarant un constructeur (même vide), les performances se dégradent énormement parceque le constructeur est appellé à chaque ligne de résultat :</p>
<ul>
<li>Temps de récupération : <strong style="color:red">+55%</strong> (avec <code>while + fetch</code>, ça descend à <strong style="color:red">+36%</strong>)</li>
<li>Taille du résultat : dépend de la taille de la classe</li>
</ul>
<p>Pour faire court : les performances sont catastrophiques par rapport aux modes classiques. Il est peut-être possible d&#8217;utiliser ce mode pour créer un petit ORM, mais pour l&#8217;instant je ne vois pas bien l&#8217;utilité.</p>
<h3>FETCH_INTO</h3>
<p>Ce mode permet de récupérer les données *dans* une instance de classe déjà existante&#8230;</p>
<p>Pour utiliser ce mode, il faut passer en paramètre la variable qui contient l&#8217;instance à modifier. Comme pour <code>FETCH_CLASS</code>, d&#8217;après la documentation il faut utiliser la fonction <code>setFetchMode</code> (ou directement <code>query</code>) pour passer ce paramètre. Et cette fois, curieusement, ni <code>fetchAll</code> ni <code>fetch</code> ne les accepte (avec <code>fetchAll</code> on obtient l&#8217;erreur plutôt originale <q>SQLSTATE[HY000]: General error: Extraneous additional parameters</q>).</p>
<p>La seule manière est donc d&#8217;utiliser :</p>
<pre>
class MyClass { }
$toto = new MyClass();
$results->setFechMode(PDO::FETCH_INTO, $toto);
// fetch
</pre>
<p>Attention, contrairement à <code>FETCH_CLASS</code>, si vous déclarez dans la classe les variables correspondant au nom des colonnes en <code>protected</code> ou <code>private</code>, PDO va retourner une erreur : <q>Fatal error: Cannot access protected (ou private) property MyClass::$id</q>.</p>
<p>Avec <code>fetchAll</code>, on obtient un tableau contenant exactement la même instance de classe autant de fois qu&#8217;il y a de lignes dans la réponse&#8230; L&#8217;instance contient uniquement les valeurs de la dernière ligne. C&#8217;est donc totalement inexploitable. Exemple :</p>
<pre>
Array
(
  [0] => MyClass Object
    (
      [id] => 2
      [login] => toto
    )

  [1] => MyClass Object
    (
      [id] => 2
      [login] => toto
    )

)
</pre>
<p>Si on utilise <code>fetch</code> dans une boucle <code>while</code>, <code>fetch</code> retourne à chaque étape une référence vers l&#8217;instance. Exemple :</p>
<pre>
while ( $r = $results->fetch() )
	var_dump($r == $toto); // affiche true
</pre>
<p>On peut donc utiliser :</p>
<pre>
while ( $results->fetch() )
	print_r($toto);
</pre>
<pre>
MyClass Object
(
  [id] => 1
  [login] => toto
)
MyClass Object
(
  [id] => 2
  [login] => titi
)
</pre>
<p>J&#8217;ai estimé les performances avec <code>fetchAll</code> afin de pouvoir comparer les résultats, mais il est important de noter qu&#8217;avec ce mode <code>fetchAll</code> ne sert à rien&#8230; Par rapport à <code>FETCH_BOTH</code>, on obtient :</p>
<ul>
<li>Temps de récupération : <strong style="color:green">-3,5%</strong> (avec <code>while + fetch</code> ça descend à <strong style="color:green">-9%</strong>)</li>
<li>Taille du résultat : dépend de la taille de la classe</li>
</ul>
<p>Le temps de récupération est plus court que <code>FETCH_CLASS</code> ou <code>FETCH_OBJ</code> car l&#8217;objet est déjà instancié. Mais, même si les performances sont correctes, pour l&#8217;instant je n&#8217;ai pas encore trouvé d&#8217;utilisation concrète pour ce mode.</p>
<p>A suivre : <a href="/2007/10/15/les-fetch-modes-de-pdo-3-les-modes-speciaux/">les modes spéciaux</a> et <a href="/2007/10/17/les-fetch-modes-de-pdo-4-les-modes-modificateurs/">les modes modificateurs</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cloudconnected.fr/2007/10/12/les-fetch-modes-de-pdo-2-les-modes-orientes-objet/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Les fetch modes de PDO</title>
		<link>http://www.cloudconnected.fr/2007/10/10/les-fetch-modes-de-pdo/</link>
		<comments>http://www.cloudconnected.fr/2007/10/10/les-fetch-modes-de-pdo/#comments</comments>
		<pubDate>Wed, 10 Oct 2007 17:48:25 +0000</pubDate>
		<dc:creator>Rémi</dc:creator>
				<category><![CDATA[Non classé]]></category>
		<category><![CDATA[pdo]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://www.the-asw.com/?p=121</guid>
		<description><![CDATA[PDO propose une quantité assez impressionante de &#8220;modes de récupération&#8221; (fetch mode) des données. Entendez par là qu&#8217;il est possible de personnaliser le comportement des méthodes fetch et fetchAll pour obtenir les résultats des requêtes SQL sous des formes très diverses. Si certains modes sont relativement classiques et ont des équivalents avec les précédents drivers [...]]]></description>
			<content:encoded><![CDATA[<p>PDO propose une quantité assez impressionante de &#8220;modes de récupération&#8221; (fetch mode) des données. Entendez par là qu&#8217;il est possible de personnaliser le comportement des méthodes <code>fetch</code> et <code>fetchAll</code> pour obtenir les résultats des requêtes SQL sous des formes très diverses. Si certains modes sont relativement classiques et ont des équivalents avec les précédents drivers MySQL (<code>mysql_fetch_array</code>, <code>mysql_fetch_row</code>, etc.), d&#8217;autres sont totalement inédits&#8230; mais très mal documentés ! Et pourtant, certains sont *très* pratiques&#8230; J&#8217;ai donc décidé d&#8217;écrire une série d&#8217;articles présentant tous les modes de fetch, leur fonctionnement et surtout leurs performances.</p>
<p><span id="more-121"></span></p>
<p>Cette première partie est consacrée à la présentation des tests et aux modes &#8220;classiques&#8221;.</p>
<h3>Présentation</h3>
<p>Le mode de récupération peut être définis de plusieurs façons :</p>
<ul>
<li>directement en paramètre de la méthode <code>query()</code>&nbsp;;</li>
<li>avec la méthode <code>setFetchMode()</code> appliquée à l&#8217;objet de résultats&nbsp;;</li>
<li>en paramètre de la méthode <code>fetchAll()</code>&nbsp;;</li>
<li>en paramètre de la méthode <code>fetch()</code>&nbsp;; dans ce cas le mode s&#8217;applique ligne par ligne, et il est ainsi possible de changer de mode pendant la récupération&#8230;</li>
</ul>
<p>Attention, tous les modes ne peuvent pas s&#8217;utiliser avec toutes les méthodes (sinon ça serait trop simple) : certains fonctionnement seulement avec <code>fetchAll</code>, d&#8217;autres avec <code>fetch</code>, d&#8217;autres encore nécessitent obligatoirement un appel à <code>setFetchMode</code> pour passer des paramètres supplémentaires&#8230; C&#8217;est l&#8217;anarchie et ce n&#8217;est pas du tout documenté sur le site de PHP, alors si besoin est, je précise comment utiliser le mode.</p>
<p>Rappel à propros de <code>fetchAll</code> : il n&#8217;est pas recommandé de l&#8217;utiliser pour traiter de grosses requetes, car cela revient à mettre d&#8217;abord en mémoire la *totalité* des résultats pour les traiter ensuite. En utilisant <code>fetch</code> dans une boucle <code>while</code>, on met en mémoire une ligne et on la traite, puis la suivante réutilise la mémoire occupée, etc.</p>
<p>Les tests sont effectués avec PHP 5.2.4 (certains modes sont très récents). Le code utilisé pour afficher le résultat est le suivant (sauf mention contraire) : </p>
<pre>
$dbh = new PDO($dsn, $login, $password);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = 'SELECT id, login FROM user LIMIT 0,2';
$results = $dbh->query($sql);
print_r($results->fetchAll(PDO::FETCH_<em>FOOBAR</em>));
</pre>
<p>Enfin, je suppose que vous connaissez déjà PDO avant de lire cet article :-)</p>
<h3>FETCH_BOTH</h3>
<p>C&#8217;est le mode de récupération par défaut de PDO, et, selon moi, le moins utile et le moins efficace. Le tableau qui est retourné contient les résultats en double : une fois indexés par le nom des colonnes et une deuxième fois indexés par leur numéro. Personnellement, je n&#8217;utilise *jamais* les deux modes d&#8217;indexation en même temps ; c&#8217;est soit l&#8217;un, soit l&#8217;autre. Du coup, ce mode de récupération ne sert à rien. Pourquoi est-ce que c&#8217;est justement celui là le mode par défaut ? Mystère.</p>
<p>Exemple :</p>
<pre>$results->fetchAll(PDO::FETCH_BOTH);</pre>
<pre>
Array
(
  [0] => Array
    (
      [id] => 1
      [0] => 1
      [login] => toto
      [1] => toto
    )

  [1] => Array
    (
      [id] => 2
      [0] => 2
      [login] => titi
      [1] => titi
    )
)
</pre>
<p>J&#8217;ai mesuré le temps moyen mis à récupérer les résultats d&#8217;une requête de 100 lignes (sur 1000 executions), et la taille du tableau retourné, avec <code>fetchAll</code>, puis avec <code>while ( $r = $results->fetch() )</code>. Puisque c&#8217;est le mode par défaut, les résultats me servent de référence pour comparer avec les autres modes.</p>
<p>Note : depuis PHP 5.2.0 il est possible de modifier le mode de récupération par défaut avec :</p>
<pre>$dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_<em>FOOBAR</em>);</pre>
<p>Ce mode sera utilisé lorsqu&#8217;aucun mode n&#8217;est précisé, mais parfois PDO revient malgré tout à <code>FETCH_BOTH</code>, alors on ne peut pas dire que ça soit très fiable.</p>
<h3>FETCH_NUM</h3>
<p>Le tableau retourné contient les résultats indexés par le numéro de la colonne.</p>
<p>Exemple :</p>
<pre>$results->fetchAll(PDO::FETCH_NUM);</pre>
<pre>
Array
(
  [0] => Array
    (
      [0] => 1
      [1] => toto
    )

  [1] => Array
    (
      [0] => 2
      [1] => titi
    )

)
</pre>
<p>Par rapport à <code>FETCH_BOTH</code> :</p>
<ul>
<li>Temps de récupération : <strong style="color:green">-7,1%</strong></li>
<li>Taille du résultat : <strong style="color:green">-23%</strong></li>
</ul>
<p>Ce mode est le plus rapide et retourne le tableau le plus petit. Mais il n&#8217;est pas toujours pratique de travailler avec le numéro des colonnes. D&#8217;autant plus que le moindre changement dans la requete SQL (suppression d&#8217;une colonne par exemple) risque de casser tout le reste du script ! En clair, à utiliser dans des cas très spécifiques et quand le nombre de colonnes est réduit pour ne pas risquer d&#8217;avoir une application impossible à maintenir.</p>
<h3>FETCH_ASSOC</h3>
<p>Le tableau retourné contient les résultats indexés par le nom de la colonne. Ce mode ne permet pas de récupérer plusieurs colonnes avec le même nom (une seule valeur est retournée, en général celle la plus à droite dans la requête, i.e. la dernière).</p>
<p>Exemple :</p>
<pre>$results->fetchAll(PDO::FETCH_ASSOC);</pre>
<pre>
Array
(
  [0] => Array
    (
      [id] => 1
      [login] => toto
    )

  [1] => Array
    (
      [id] => 2
      [login] => titi
    )

)
</pre>
<p>Par rapport à <code>FETCH_BOTH</code> :</p>
<ul>
<li>Temps de récupération : <strong style="color:green">-4%</strong></li>
<li>Taille du résultat : <strong style="color:green">-20%</strong></li>
</ul>
<p>Les performances sont legèrement moins bonnes que <code>FETCH_NUM</code> (mais toujours bien meilleures que le mode par défaut), mais un tableau associatif est *tellement* plus pratique à utiliser et plus facile à maintenir que ça n&#8217;a pas d&#8217;importance. C&#8217;est le mode que j&#8217;utilise par défaut.</p>
<h3>FETCH_NAMED</h3>
<p>Ce mode un peu particulier est identique à <code>FETCH_ASSOC</code>, sauf dans le cas où plusieurs colonnes portent le même nom dans la requete SQL : <code>FETCH_ASSOC</code> ne va retourner qu&#8217;une seule valeur tandis que <code>FETCH_NAMED</code> va retourner un tableau de valeurs.</p>
<p>Prenons par exemple la requete suivante (complètement inutile, juste pour l&#8217;exemple) :</p>
<pre>SELECT email as id, id, login FROM user LIMIT 0,2;</pre>
<p>Le résultat avec <code>FETCH_ASSOC</code> est identique à ci-dessus, et la première colonne n&#8217;est pas accessible. Avec <code>FETCH_NAMED</code>, on obtient :</p>
<pre>$results->fetchAll(PDO::FETCH_NAMED);</pre>
<pre>
Array
(
  [0] => Array
    (
      [id] => Array
        (
          [0] => 1
          [1] => toto@foo
        )

      [login] => toto
    )

  [1] => Array
    (
      [id] => Array
        (
          [0] => 2
          [1] => titi@foo
        )

      [login] => titi
    )

)
</pre>
<p>Bon, entre nous, si vous avez un jour besoin de ce mode, envisagez d&#8217;utiliser les alias de colonnes en SQL&#8230;</p>
<p>A suivre : les <a href="/2007/10/12/les-fetch-modes-de-pdo-2-les-modes-orientes-objet/">modes orientés objets</a>, les <a href="/2007/10/15/les-fetch-modes-de-pdo-3-les-modes-speciaux/">modes spéciaux</a> et les <a href="/2007/10/17/les-fetch-modes-de-pdo-4-les-modes-modificateurs/">modes modificateurs</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cloudconnected.fr/2007/10/10/les-fetch-modes-de-pdo/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

