#WordPress Un Comparateur FIND_IN_SET pour les meta_queries

Depuis un bout de temps, j’ai démarré la « fabrication » d’un prototype d’une application Web qui utilise WordPress comme cadre de développement. Ce prototype est donc un plugin qui pour profiter de toutes les fonctions liées aux « posts » de WordPress crée des post types et entretient des liaisons entre ces derniers.

Tant que cette liaison est du type 11, no problémo : il suffit de stocker l’ID de l’un dans un post_meta de l’autre. A partir du moment où cette liaison est du type 1N, ça se complique !

Naissance du besoin d’un comparateur FIND_IN_SET

En effet, lorsque la valeur est unique, il est simple de lister les post_type_b attachés au post_type_a ou à une liste potentielle de post_type_a. Exemple :

$value_to_find_in_meta = 2;

$meta_query = array( 
 	array(
		'key'	  => '_meta_field_key' /* valeur unique */,
		'value'	  => $value_to_find_in_meta,
		'compare' => '='  /* ou IN */
	)
);

$query = new WP_Query( array( 'meta_query' => $meta_query ) );

En revanche, lorsqu’on doit stocker une liste de valeurs dans le post_meta d’un post_type_b correspondant aux différents ID de post_type_a qui lui sont attachés, les comparateurs classiques des meta_queries de WordPress ne sont plus d’aucune utilité :(. Or, si on s’amuse à jouer des requêtes MySQL, l’opérateur FIND_IN_SET nous permet d’atteindre l’objectif recherché. Une rapide recherche « meta_query find_in_set » vous dirigera vers ce gist proposé par Mike Schinkel et qui vise à ajouter précisément un comparateur FIND_IN_SET aux meta_queries de WordPress.

De mon côté, j’utilise une technique équivalente : passer un comparateur inexistant dans la liste proposée par WordPress pour ensuite modifier la meta_query que WordPress conçoit dans WP_Meta_Query->get_sql(). Exemple :

$value_to_find_in_set = 2;

$meta_query = array( 
 	array(
		'key'	  => '_meta_field_key',
                /* meta_value is a comma separated list of value eg: '1,2' */
		'value'	  => $value_to_find_in_set,
		'compare' => 'FIND_IN_SET'
	)
);

$query = new WP_Query( array( 'meta_query' => $meta_query ) );

Ma différence par rapport à Mike, c’est que je ne souhaite pas que chacune des WP_Query de WordPress emprunte mon inspection d’un éventuel comparateur FIND_IN_SET. Cette inspection n’intervient que si et seulement si cette WP_Query contient une meta_query. Pour cela je filtre get_meta_sql, comme illustré ci-dessous.

function find_in_set_meta_query( $meta_sql = array(), $meta_queries = array() ){
	$has_find_in_set = array();

	// Do we have a 'FIND_IN_SET' meta_query ?
	foreach( $meta_queries as $key => $meta_query ) {
		if( !empty( $meta_query['compare'] ) && $meta_query['compare'] == 'FIND_IN_SET' )
			$has_find_in_set[$key] = $meta_query['key'];
	}

	// If no find in set, no need to carry on!
	if( empty( $has_find_in_set ) )
		return $meta_sql;

	preg_match_all( "/meta_key\s=\s'(.*)'\sAND\s(.*)'\)/", $meta_sql['where'], $matches );

	foreach( $has_find_in_set as $index => $meta_key ) {
		if( $meta_key == $matches[1][$index] ) {
			preg_match( '/CAST\((.*)\sAS/', $matches[2][$index], $matched_meta );

			// Replacing regular meta_query with 'FIND_IN_SET' one
			$meta_sql['where'] = str_replace( $matches[2][$index] . "'", "FIND_IN_SET( {$meta_queries[$index]['value']}, {$matched_meta[1]} ) ", $meta_sql['where'] );
		}
	}

	return $meta_sql;
}

add_filter( 'get_meta_sql', 'find_in_set_meta_query', 10, 2 );

VoiliVoilou, j’ai pensé que ça pourrait intéresser certains d’entre vous. N’hésitez pas à me proposer des améliorations :)

PS : je n’ai découvert la technique de Mike qu’après avoir conçu la mienne. J’avais pensé un temps suggérer à WordPress un enhancement, mais ma découverte tardive du script de Mike m’aura permis de constater que ce n’était pas une voie dans laquelle souhaitait se diriger WordPress comme vous pourrez le constater dans sa discussion avec Otto.

5 réflexions au sujet de « #WordPress Un Comparateur FIND_IN_SET pour les meta_queries »

  1. Willy

    Waw, ta méthode est très intéressante !
    Elle m’aurait été vraiment utile à une époque où j’ai eu le même « souci » de code. Mais en fait il y a un moyen de contourner le problème, enfin dans mon cas : stocker les ids des posts en relations dans plusieurs metas ayant la même clé. Un ID = une meta, et il n’est pas interdit pour un post d’avoir plusieurs metas du même nom. À partir de là les WP_Query de WordPress redeviennent facilement exploitable :-)

    Une autre limite que je rencontre souvent, par contre, et à laquelle je n’ai pas trouvé de solution : faire une tax_query appellant les posts qui appartiennent à un terme A et excluant ceux appartenant à un terme B… Connais-tu une méthode pour ça ? (à part utiliser post__not_in)

    Répondre
    1. Avatar de imathimath Auteur

      Merci @BenHen75 et @Willy

      J’avais pas pensé à mettre plusieurs meta du même nom !! Thanks :)
      Sinon, pour la tax_query, je viens de remarquer qu’il n’y avait pas de filtre disponible pour la méthode WP_Tax_Query->get_sql(). Etrange d’en avoir un pour les metas et pas pour les taxos.. Je viens d’essayer cette WP_Query et ça semble fonctionner :

      $args = array(
        'post_type' => 'post',
        'tax_query' => array(
          'relation' => 'AND',
          array(
            'taxonomy' => 'category',
            'field' => 'slug',
            'terms' => array( 'test' )
          ),
          array(
            'taxonomy' => 'category',
            'field' => 'slug',
            'terms' => array( 'uncategorized' ),
            'operator' => 'NOT IN'
          )
        )
      );

      $query = new WP_Query( $args );

      Répondre
      1. Willy

        Ah oui effectivement… Il semble que j’ai « craqué »… en plus je viens de voir que ce code est dans la doc :-|

        Merci ^^

        Et effectivement, un petit filtre sur get_tax_query ne serait pas mal ; bizarre qu’il n’y en ai pas…

        Répondre
  2. Amaury

    Hello,

    Je ne remets pas en cause l’expérimentation que tu réalises avec les meta_query, ni même la pertinence de la méthode « find_in_set ». C’est toujours intéressant de faire par soi-même.

    Mais en ce qui concerne la mise en relation entre les types de contenus, il existe 2 solutions pertinentes, industrialisables et maintenables à mes yeux.

    La plus simple (et le plus limité fonctionnellement), c’est le plugin que j’ai développé à ce sujet Relation Post Types (RPT) et qui permet de créer des relations entre le type de contenu. (N – N)

    Pour aller encore plus loin, il existe également Posts 2 Posts (P2P) développé par scribu et qui permet de faire des tas de choses (1 – N / N – N / N – 1 ), bref une tuerie pour les développeurs.

    P2P : http://wordpress.org/plugins/posts-to-posts/
    RPT : http://wordpress.org/plugins/relation-post-types/

    Répondre

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>