La classe WP_Query, base de toute requête sur un site WordPress, évolue au fil des versions de notre CMS préféré. Ce sera le cas avec la prochaine version 3.4, comme ça l’a été avec la 3.1. Cependant, il y a une chose que l’on ne peut toujours pas faire facilement : retrouver des articles selon une meta, si cette meta est sous forme de tableau.
Avec une meta « normale »
Tout d’abord pour ceux qui n’ont pas encore compris de quoi je parle, ce que j’appelle « meta » c’est une donnée attachée à un post (post meta), ou à un utilisateur (user meta), etc. Ce sont les données enregistrées depuis un champs personnalisé dans un article par exemple.
Ceux qui ont l’habitude de faire des requêtes avec WP_Query connaissent déjà le système pour retrouver des posts selon une meta :
1
$query = new WP_Query( array( 'meta_key' => 'color', 'meta_value' => 'blue' ) );
Avec ceci nous cherchons les articles ayant une meta « color » dont la valeur est « blue ».
WordPress 3.1 a introduit un système plus complet qui permet de jouer avec plusieurs metas :
0102030405060708091011121314151617
$args = array(
'post_type' => 'product',
'meta_query' => array(
array(
'key' => 'color',
'value' => 'blue',
'compare' => 'NOT LIKE'
),
array(
'key' => 'price',
'value' => array( 20, 100 ),
'type' => 'numeric',
'compare' => 'BETWEEN'
)
)
);
$query = new WP_Query( $args );
Cette requête va chercher les produits dont « color » ne contient pas « blue » ET dont « price » est compris entre 20 et 100.
Pour plus d’exemples et une explication plus complète, je vous renvoie vers le codex.
Si vous jetez un œil attentif au paramètre type (utilisé ci-dessus avec « numeric »), vous voyez ceci :
type (string) – Custom field type. Possible values are ‘NUMERIC’, ‘BINARY’, ‘CHAR’, ‘DATE’, ‘DATETIME’, ‘DECIMAL’, ‘SIGNED’, ‘TIME’, ‘UNSIGNED’. Default value is ‘CHAR’.
On remarque qu’il y a pas mal de possibilités mais… toujours pas de ‘ARRAY’. Alors comment on fait, tonton ?
L’array qui n’en est pas un(e)
Le type ‘ARRAY’ n’est pas disponible, et pour une bonne raison : un tableau ne sera pas enregistré en tant que tableau, mais en tant que chaine de caractères (type ‘CHAR’ ci-dessus). En effet, c’est totalement transparent de notre côté, mais WordPress serialize le tableau avant de l’enregistrer et l’unserialize au moment de nous donner sa valeur.
Ainsi, le tableau array("bar" => "foobar")
sera stocké sous la forme a:1:{s:3:"bar";s:6:"foobar";}
Quand on sait ça, il n’est pas bien compliqué de chercher un article selon une meta « tableau », cela revient à faire une recherche « classique » de chaine de caractères avec une phase de serialization avant :
123456789
$args = array(
'meta_query' => array(
array(
'key' => 'ma-meta',
'value' => maybe_serialize( array("bar" => "foobar") )
)
)
);
$query = new WP_Query( $args );
A noter : WordPress dispose de sa propre fonction de serialization maybe_serialize().
Petite précaution : il faut tout de même faire attention au type de donnée enregistrée dans le tableau :
maybe_serialize( array("bar" => "12") )
donnera a:1:{s:3:"bar";s:2:"12";}
maybe_serialize( array("bar" => 12) )
donnera a:1:{s:3:"bar";i:2:12;}
Dans le premier cas, « 12 » est un string, dans le second c’est un integer, le résultat n’est pas le même.
Astuce : dans un script je récupère une valeur sous forme d’entier, mais cette valeur est stockée sous forme de string dans une meta, comment faire concorder tout ceci simplement ?
12
$a = 12;
$val = maybe_serialize( array("bar" => "$a") );
Encore plus fort
Faire une recherche non pas en fonction du tableau entier mais de la valeur d’une de ses cases, c’est possible ?
Bien sûr Scoobidoo, il suffit de préparer notre valeur de recherche avant :)
Disons que nous ayons une meta « ma-meta » sous cette forme :
12345
array(
"bar" => "foobar",
"foo" => 12,
"die" => "hard"
)
Nous souhaitons trouver tous les articles avec la meta « ma-meta » dont « bar » vaut « foobar ».
Préparons notre terme de recherche :
12
$val = maybe_serialize( array("bar" => "foobar") ); // a:1:{s:3:"bar";s:6:"foobar";}
$val = rtrim( ltrim( $val, 'a:1:{' ), '}' ); // on élague les bords qui nous gênent : s:3:"bar";s:6:"foobar";
Et finalement la requête, il nous faudra utiliser la comparaison ‘LIKE’ :
03040506070809101112
$args = array(
'meta_query' => array(
array(
'key' => 'ma-meta',
'value' => $val,
'compare' => 'LIKE'
)
)
);
$query = new WP_Query( $args );
A noter : lorsque l’on utilise l’opérateur ‘LIKE’, il est inutile d’ajouter des ‘%’ autour de la valeur recherchée (il ne faut tout simplement pas les mettre d’ailleurs, ça ne marchera pas).
Selon 2 valeurs possibles de « bar » ? Facile :
010203040506070809101112131415161718
$val = rtrim( ltrim( maybe_serialize( array("bar" => "foobar") ), 'a:1:{' ), '}' );
$val2 = rtrim( ltrim( maybe_serialize( array("bar" => "toto") ), 'a:1:{' ), '}' );
$args = array(
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'ma-meta',
'value' => $val,
'compare' => 'LIKE'
),
array(
'key' => 'ma-meta',
'value' => $val2,
'compare' => 'LIKE'
)
)
);
$query = new WP_Query( $args );
On notera l’ajout de « relation », réglé à « OR » car nous cherchons les articles dont « bar » vaut « foobar » OU « toto ». Par défaut, « relation » vaut « AND ».
EDITH
Plutôt que d’utiliser rtrim( ltrim( maybe_serialize( ...
, voici une fonction qui permet de faire la même chose (en mieux x)) :
010203040506070809101112
if ( !function_exists('serialize_array_content') ):
function serialize_array_content( $array, $value_only = false ) {
$array = serialize( (array) $array );
$pos = $value_only ? ';' : '{';
return substr($array, strpos($array, $pos)+1, -1);
}
endif;
echo serialize_array_content( array("bar" => "foobar") ); // s:3:"bar";s:6:"foobar";
echo serialize_array_content( "foobar" ); // i:1:0;s:6:"foobar";
echo serialize_array_content( array("bar" => "foobar"), true ); // s:6:"foobar";
echo serialize_array_content( "foobar", true ); // s:6:"foobar";
Conclusion
La technique peut paraitre un peu compliquée au début, mais avec un peu d’habitude ça passe très bien.
Il faudra cependant à faire bien attention au type de valeur de meta à retrouver (l’histoire de string et integer), et dans le cas de tableaux à plusieurs niveaux, éviter d’avoir plusieurs fois le même index de case (« foo » => xxx à plusieurs niveaux) sinon cela va considérablement compliquer la tâche.
See ya!
Commentaires
Commentaire de Antoine Brossault.
Super ! Ton article ma donné un très bon coup de main sur une requête d’un CPT avec une une meta_query sur des dates…
Avec les dates il faut faire super gaffe à la valeure d’entré dans le post_meta. Le mieux est de l’avoir en 20150102 (pour le 1er janvier 2015) comme ca on peut utiliser des comparateur numérique.
Voilà ce que donne un code qui marche pour une requête sur des dates.
010203040506070809101112131415
Commentaire de Grégory Viguier.
On peut aussi utiliser le type DATETIME ou DATE dans les meta queries ;)
Commentaire de Thomas.
super article, exactement ce qu’il me fallait… en partie :)
Je cherche à faire un BETWEEN sur une valeur du tableau de la meta. Or ces valeurs sont toujours au format string, c’est ça ?
Impossible de faire de comparaison de chiffre ?
Merci !
th