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 Geoffrey @ Geoffrey.Crofte.fr.
Super article Greg, merci !
J’ai testé il y a quelques temps une recherche avancée de ce type sur un CPT. De mémoire j’avais abandonné cette solution de WP_Query avancée mais je ne sais plus pour quelle raison.
Faudrait que je remette le nez dedans avec tes explications, j’y vois plus clair maintenant :)
Au plaisir !
Commentaire de Julio Potier @ BoiteAWeb.
Haaaa la meta query, souvent une belle galère et toi tu la fait passer dans du beurre, bravo, je garde en fav (en fait, je vais renommer mon onglet « Favoris » et écrire « Greg » comme ça je dirais « Je garde en Greg, ça me servira »
/clap
Commentaire de Greg.
@BoiteAWeb : lol
@Geoffrey et @BoiteAWeb : merci :)
Commentaire de sebastien.
il y a juste un truc que j’ai pas compris, ou plutot un truc que je ne visualise pas : dans quel cas une meta aurait besoin d’un type tableau concrètement ?
Commentaire de Greg.
@sebastien
Tous ?
Personnellement je ne travaille qu’avec des metas/options sous forme de tableau quasiment.
Trois avantages :
– Ça évite leur prolifération dans la base de données : la table des metas est alors moins encombrée.
– Imagine que tu ais 30 metas : ça veut dire 30
get_post_meta()
pour les récupérer toutes, 30update_post_meta()
pour les mettre à jour, et 30delete_post_meta()
pour les supprimer (et en plus il ne faut pas en oublier). C’est quand même bien lourd, alors qu’il en suffirait d’un seul avec une meta sous forme de tableau.– Il y aurait aussi d’autres cas où par exemple tu as un nombre indéterminé de metas (générées par ton plugin) à récupérer mais tu les veux toutes, ou alors le nom et/ou le contenu de tes metas ne sont pas fixes, etc. Ça reste possible de les récupérer mais ça complique pas mal la chose, et peut aussi requérir plusieurs requêtes au lieu d’une.
Donc selon moi, utiliser des metas sous forme de tableau est un gain de performance et de facilité d’utilisation (enfin, quand on a pris un peu l’habitude en tout cas ;)).
Commentaire de sebastien.
tout d’abord : je viens de venir sur ton site et c’est un autre site type « site parking » qui s’affichait. Tu as été hacké ? J’ai testé plusieurs url de ton site et c’était pareil partout.
Pour répondre : merci de toutes ces précisions, c’est très claire et j’ai bien compris. Mais ce que je voulais dire surtout, c’est que je vois bien comment créer une clé et une valeur avec un champ personnalisé mais je ne vois pas comment créer un tableau…
Commentaire de Greg.
Ho étrange pour le site parking :o
Ok, c’est simple pour créer une meta tableau, c’est la même chose qu’avec une meta non-tableau :
1234567
Commentaire de sebastien.
ok je vois mais concrètement dans l’admin ça se passe comment ? Tu créé un champ pour chaque ligne du tableau ? Et t’enregistre la clé et la valeur de chaque champ dans un unique tableau c’est ça ?
Commentaire de sebastien.
ça vient de recommnecr (le site parking) je t’envoie une copie ecran
c’est quoi ton email ?
Commentaire de Greg.
N’étant pas un adorateur du spam, je préfèrerais éviter de mettre mon adresse email en clair sur le blog ;) Essaies avec le formulaire de contact :) Merci.
Commentaire de Greg.
ok je vois mais concrètement dans l’admin ça se passe comment ? Tu créé un champ pour chaque ligne du tableau ? Et t’enregistre la clé et la valeur de chaque champ dans un unique tableau c’est ça ?
Oui il faut un champ pour chaque ligne du tableau, ça ne change pas par rapport à la méthode traditionnelle.
Version simplifiée (3 possibilités) :
12345678
La 3ème étant ma préférée car les valeurs sont plus simple à récupérer (en fait, pour la 2ème c’est la même chose) :
12345678
Il manquerait des vérifications avec
isset()
mais en gros ça donne ça.Par acquis de conscience j’ai quand même ajouté une couche de sécurisation/nettoyage des valeurs retournées ;)
Commentaire de sebastien.
je t’ai envoyé un mail via le formulaire mais pas possible de joindre un fichier ^^
Commentaire de sebastien.
mais oui bien sûr ! excellent ! Merci bcp !
Commentaire de sebastien.
pour info : j’ai cherché avec google image si il y avait des trucs qui ressemblaient à ton site tel que je l’ai vu hier et effectivement il y en a (la plupart sont liés au mot virus, pour ce que j’en ai rapidement vu) je te laisse te faire ton idée. Le lien vers la page de resulats de google : http://goo.gl/1kydv
Commentaire de sebastien.
PS : non en fait y’a qu’un resultat avec le mot virus, j’ai parlé un peu vite
Commentaire de Greg.
L’idée au début était que tu m’envoies un mail pour que j’ai ton adresse, et que je puisse te répondre… Et puis j’ai percuté que j’ai ton mail dans l’administration des commentaires ^^
Bon, j’ai pas eu ton email mais pas grave, j’ai l’image dans ton lien google. Merci :)
Commentaire de guru_press.
Salut Greg!
J’ai lu ton article et ça m’a sauvé bien du temps dans le passé. Mais je suis coincé avec une formule un peut pareille. Et j’arrive pas à trouver la porte de sortie..
Voici mon probleme:
J’ai une base de meta(par exemple):
Je veux recherche dans chauqe ligne 20 et 10 à l’aide d’un search. Ou au moins une seule valeur.
Voila ce que j’avais
01020304050607080910111213141516
Il me retourne les valeur 2 fois. par exemple pour 20 et 10, il retourne 4 valeurs au lieu de 3.
Post 5 qui contient 20
Post 5 qui contient 10
Post 8 qui contient 10
Post 3 qui contient 20.
Ma question est, est-il possible de les chercher par groupe (comme Post 5 qui contient les deux valeurs) en premiers et après afichier là où il y a les valeurs simples sans répéter.?
Commentaire de Julio Potier @ BoiteAWeb.
Bonsoir
Je pense que tu devrais additionner les meta_query au lieu d’en faire une par valeur, quelquechose comme :
0809101112131415
Vérifie les parenthèses etc, j’ai rien testé ;]
Fait nous un retour !
Commentaire de Greg.
@guru_press
Déjà je te conseillerais de remplacer :
3
par :
3
Étrange résultat, chaque post ne devrait apparaitre qu’une fois, étant donné que dans get_posts on trouve ceci :
226622672268
C’est à dire que les posts ayant le même ID apparaitront qu’une fois (puisque dans ta requête,
$this->meta_query->queries
n’est pas vide).Donc là je vois pas ce qui peut poser problème, il faudrait creuser plus loin.
EDIT : grilled par Julio. Bien vu pour le foreach mal placé, je pense que ça vient de là ;)
Commentaire de guru_press.
Parfait! Good!,
Ton code ne les duplicatent plus.
Mais y-a-t-il un moyen de les affichier par orderby là où il y a plus de valeur dans un champ. ex: 10,20 en premier post #5 et 20 post#8 et 10 post#3.
???
Un genre de orderby.
Commentaire de guru_press.
Merci! Greg.
J’ai utulisé la ligne 3 ton code mais l’autre fonctionnais pas. Mais celui de Julio m’a aidé.
Il reste juste que je peux pas les orderby là où il y a plus de valeur trouvé dans un champ. Il orderby automatiquement par le post->ID le plus élevé.
Penses tu pouvoir m’aider.
Commentaire de Crunch.
Très bonnes explications ;)
Cependant j’ai une petite question : comment faire si je veux avoir une liste d’articles avec plusieurs clés mais sans vouloir une valeur spécifique ? Il faudrait juste que la valeur des clés comporte quelque-chose tout simplement, une petite astuce ?