[wp-hackers] Taxonomy Mirrored Posts Best Practice?

The WordPress Web Warlock wordpress at web-warlocks.net
Tue Aug 3 19:41:23 UTC 2010

En/na Michael Pretty ha escrit:
> We have a couple more projects lined up where we need a many to many 
> relationship between objects and have decided that for us the best 
> option is to bite the bullet and create a new table. (...) I haven't 
> yet worked out all the details of how it is going to work other than a 
> table that stores parent_id, child_id and relationship_type, with lots 
> of caching. At this point it just seems like it is going to be the 
> only way to provide the many to many relationship that is both stable 
> and provides decent performance.

If I didn't misunderstand, this resembles a feature I've developed and 
under progress/improvement/completion; I called those "description 
posts". It's just a name, it doesn't imply any specific custom post type.

A description post links to its term template —not the single.php for 
the post itself—, and are mostly used as extended term descriptions. 
They may be listed in regular term templates, along "regular" posts, and 
you can have more than a description post per term (so, for instance, 
different authors can give a definition for a term).

Instead of storing what in a graph would be the link, that is, the 
relationship itself, I'm using and storing one or both possible 
directions of that relationship (in separate places/tables), from which 
the relationship itself can be built if needed. I'll elaborate further.

I needed access from both ways. I would need quick access *from the post 
side*, to know which term(s) the post pointed to, to f.i. get the link; 
as I told, they were meant to link to a term. And I'd need, too, good 
access from the *term side*; as I said, I was using them as term 
descriptions, so there should be an easy way to retrieve the description 
posts in the template (I use a wrapper for get_posts). I had no visible 
need, though, for "the relationship itself" (the relationship regardless 
of the point of origin, so to speak), so I priorized the "exits" of each 
"origin point" above the relationship data, which could be built from 
both origin points.

So, on the one hand, there is a special meta box for the postmeta. There 
are several different (non-unique) keys, for the different taxonomies. 
There is _query_cat , _query_post_tag, _query_{taxonomy-slug}, &c, 
mimicking the args for WP_Query... In practice, all those posts point 
just to one category, though, because I don't use taxonomies, but a 
custom replacement. And, though only a value is used, this meta box and 
keys may have other uses; that's why they're non-unique. When updated, 
the meta box save function does not only update (or delete) the 
postmeta, but also the data in the other table, unless it's post_type 
Page (I'll explain why later). I use it with a function that returns a 
single array of term_id and taxonomy, scanning the relevant postmeta 
keys in a given order. With a bit more of code, I think it'd be able to 
return many arrays of term_id and taxonomy (say, just a $single flag?), 
and filter the order of the keys; I haven't had need for that yet, but I 
have some theorical use cases: for instance, a partly-predefined search 
results Page template (which also would be a good use case for multiple 
values per key; say, you're pre-querying some "content categories" with 
category__and, like "all movie reviews written in English": "movie 
reviews" and "written in English" would be the categories).

On the other hand, the direction/relationship *from the term* is also 
stored in another table, a termmeta table, with the same typical 
meta-table structure (it maps to term_id, not to term_taxonomy_id). I 
was using it already for a lot of things, had all the API for it already 
written, and it behaves much like wp_options (loads once and caches a 
lot of data). So I suppose you could store this "term side" in 
{wp_}options... after all, the term hierarchy is stored there (to 
mention an example of "termmeta" stored in wp_options). As in the meta 
box, when the term is updated with the edit form, the postmeta is 
updated, too. (Right now, though, it only allows for deletion, so 
updates have to be made from the post side. As I told, it's still under 
improvement :) ).

In any of the save functions (hooked to save_post or edit_term_meta) you 
can do whatever filters and actions you might need (like adding the 
termmeta when a new postmeta is added, &c). Also, it's quite possible 
that this technique requiers no additional queries, for all the data is 
previously cached somehow (by postmeta and options or, in my case, 
termmeta tables). You're dealing (mostly) with core data (term_id and 
post_id, not some, say, "custom_relationship_id"), so it's easy to 
integrate that with the API, namely, get_posts where post__in is 
termmeta for $term_id, and get_term_link for the according postmeta of 
$post_id. Or query_posts( array( 'category__and' => get_post_meta( 
$post->ID, '_query_cat' , true ) ) );. Etc.

I'd suggest you that, since you're already adding a table, make it 
wp_termmeta and have it available for other term-related uses. For 
instance, if you're using custom post types as "a regular post with 
added custom fields", is better IMHO to make the custom fields 
term-based, not post-type based, because a post can belong to several 
terms, but can only be of a single post type. (These "custom fields by 
term" are obviously "termmeta" data. And a darnly useful one, I might 
add, "piling up" all the custom fields for all the terms). Also, that 
support and APIs you make (say, get_term_meta, get_post_as_term_link...) 
will be available for future projects —harder to do with the 
often-too-specific post-type-specific code.

I have included this feature in a termmeta plugin (which does the other 
things I said I was already doing), not yet released, but you can check 
it out at my domain; all posts in wordpress/descriptions are like that 
(WordPress/Contents/Descriptions, if you're browsing from the menu at 
the right; for search and SEO vs. navigation/browsing purposes, the 
paths/permalinks aren't the same as the category structure, which has 
lots of indexs: see below about linking terms to Pages). In other 
(localhost) sites, there is more than a single "description" category, 
there are "descriptions of X"; most possibly I'll break down the 
"description" category in my domain, and have "function reference", 
"plugin reference" & so. If you're interested in the code, drop me a 
mail or just ask here.

There's still more to it (tangentially related, at least).

I have category.php doubling also as a Page template; another of the 
termmeta features is to link a term to a Page, and if it's linked, the 
get_term_link function returns —via hook— the permalink to that Page, 
not the "default" permalink to the term. Obviously, the Page must "know" 
somehow which term is supposed to show, and it uses the same meta fields 
and meta boxes that the "description posts" use. But this relationship 
is one-way only —so, in my approach, one-way-only relationships are not 
a bug, but a feature.

So, you can have actually a somewhat complicated-to-make-it-easy 
relationships path: The "description post" links to a term, which "is" 
actually a Page (representing that term and using the term template), 
which in turn includes the description posts (because they're the 
extended description for that term), besides the actual Page content. 
So, both posts of both post types (post and Page) represent somehow the 
same term, but in different ways. That is, the *direction* of the 
relationship is strongly relevant, and this is really a big conceptual 
difference with your approach as I understood it: it looked to me like 
your relationships would be always two-way, and the direction depended 
on the point where you were retrieving the relationship from: if you're 
looking at the A-B relationship from A, the direction would be 
necessarily "to B" and vice versa. I'm doing it the opposite way: after 
checking the "exit paths" from A, and getting B as exit path, the 
relationship is bidirectional if B also points to A, one-way if otherwise.

Variations of this behavior are supposed to be implemented via the 
different hooks (get_term_meta, update_term_meta, some in the meta 
boxes, &c). So, quite in the good ol' WordPress style, add the 
additional data as wp_{whatever}meta, do it some way, and allow lots of 
hooks and filters to override it and do it differently :).

More information about the wp-hackers mailing list