0

Let's say you have a custom post type custom_type registered in your main plugin file. You also have multiple custom taxonomies registered in your main plugin file. All in all something like this:

add_action(
    'init',
    function () {
        
        register_taxonomy(
            'first_custom_tax',
            'custom_type',
            [
                'description'        => esc_html__(
                    'First custom taxonomy',
                    'my-plugin'
                ),
                'public'             => true,
                'publicly_queryable' => true,
                'hierarchical'       => false,
                'show_ui'            => true,
                'show_in_menu'       => true,
                'show_in_nav_menus'  => false,
                'show_in_rest'       => true,
                'show_tagcloud'      => true,
                'show_in_quick_edit' => true,
                'show_admin_column'  => true
            ]
        );
        
        register_taxonomy(
            'second_custom_tax',
            'custom_type',
            [
                'description'        => esc_html__(
                    'Second custom taxonomy',
                    'my-plugin'
                ),
                'public'             => true,
                'publicly_queryable' => true,
                'hierarchical'       => false,
                'show_ui'            => true,
                'show_in_menu'       => true,
                'show_in_nav_menus'  => false,
                'show_in_rest'       => true,
                'show_tagcloud'      => true,
                'show_in_quick_edit' => true,
                'show_admin_column'  => true
            ]
        );
        
        register_taxonomy(
            'third_custom_tax',
            'custom_type',
            [
                'description'        => esc_html__(
                    'Third custom taxonomy',
                    'my-plugin'
                ),
                'public'             => true,
                'publicly_queryable' => true,
                'hierarchical'       => false,
                'show_ui'            => true,
                'show_in_menu'       => true,
                'show_in_nav_menus'  => false,
                'show_in_rest'       => true,
                'show_tagcloud'      => true,
                'show_in_quick_edit' => true,
                'show_admin_column'  => true
            ]
        );
        
        register_taxonomy(
            'fourth_custom_tax',
            'custom_type',
            [
                'description'        => esc_html__(
                    'Fourth custom taxonomy',
                    'my-plugin'
                ),
                'public'             => true,
                'publicly_queryable' => true,
                'hierarchical'       => false,
                'show_ui'            => true,
                'show_in_menu'       => true,
                'show_in_nav_menus'  => false,
                'show_in_rest'       => true,
                'show_tagcloud'      => true,
                'show_in_quick_edit' => true,
                'show_admin_column'  => true
            ]
        );
        
        register_taxonomy(
            'fifth_custom_tax',
            'custom_type',
            [
                'description'        => esc_html__(
                    'Fifth custom taxonomy',
                    'my-plugin'
                ),
                'public'             => true,
                'publicly_queryable' => true,
                'hierarchical'       => false,
                'show_ui'            => true,
                'show_in_menu'       => true,
                'show_in_nav_menus'  => false,
                'show_in_rest'       => true,
                'show_tagcloud'      => true,
                'show_in_quick_edit' => true,
                'show_admin_column'  => true
            ]
        );
        
        register_post_type(
            'custom_type',
            [
                'description'        => esc_html__(
                    'Just a random post type',
                    'my-plugin'
                ),
                'public'             => true,
                'publicly_queryable' => false,
                'show_ui'            => true,
                'show_in_menu'       => true,
                'show_in_rest'       => true,
                'supports'           => [
                    'title',
                    'editor',
                    'author',
                    'revisions',
                    'custom-fields'
                ],
                'taxonomies'         => [
                    'first_custom_tax',
                    'second_custom_tax',
                    'third_custom_tax',
                    'fourth_custom_tax',
                    'fifth_custom_tax'
                ],
                'delete_with_user'   => false
            ]
        );
    }
);

If we now query custom_type posts via REST by using slugs of the custom taxonomies, we would use something like:

GET https://example.org/wp-json/wp/v2/custom_type?_fields[]=title&_fields[]=id&first_custom_tax=first&second_custom_tax=second&third_custom_tax=third&fourth_custom_tax=fourth&fifth_custom_tax=fifth

And to make this query work, as queries work via term ID in REST, by default, we have this code in our main plugin file:

add_filter(
    "rest_custom_type_query",
    function (
        array           $args,
        WP_REST_Request $request
    ): array {
        
        // Get parameters of the query
        $params = $request->get_params();
        
        // Enable this feature for all of the custom taxonomies
        $custom_taxonomies = [
            'first_custom_tax',
            'second_custom_tax',
            'third_custom_tax',
            'fourth_custom_tax',
            'fifth_custom_tax'
        ];
        
        foreach ( $custom_taxonomies as $custom_taxonomy ) {
            
            if ( isset($params["{$custom_taxonomy}_slug"]) ) {
                
                $args['tax_query'][] = [
                    'taxonomy' => $custom_taxonomy,
                    'field'    => 'slug',
                    'terms'    => explode(
                        ",",
                        $params["{$custom_taxonomy}_slug"]
                    )
                ];
                
            }
            
        }
        
        return $args;
        
    },
    10,
    3
);

And change the query above to:

GET https://example.org/wp-json/wp/v2/custom_type?_fields[]=title&_fields[]=id&first_custom_tax_slug=first&second_custom_tax_slug=second&third_custom_tax_slug=third&fourth_custom_tax_slug=fourth&fifth_custom_tax_slug=fifth

So far so good, this works.

We however now think that it's better for performance, especially after considering the DB Schema of terms and posts, to use the term IDs instead of the slugs for REST queries of posts with custom taxonomy terms. As we're talking about an integer primary key here, instead of a string index (term slug), which is not directly connected to wp_term_taxonomy, table by which the JOIN to wp_posts is most likely made in post queries. Is this assumption correct?

The only second problem we have here is that: We want to make the use of search filters (via the custom taxonomies) as easily as possible for the customer. That's why our search filters consist of multiple HTMLSelectElements, with each option simply having a term ID as the value, and the term name as the label. To simply be able to directly use them within the REST request you'll do. The value of first_custom_tax however may be a combination of three sublevels of filters, e.g. food, healthy and apple. And we can obviously not predict the subset of filters our customers select; one may select filters which result in the food-healthy-apple slug, others may select drinks-healthy-juice, and others maybe drinks-nogas, etc. Hence we cannot predict the term ID for these cases, and some sublevels may be applied to different parent levels (e.g. healthy is available for drinks and also for food).

How would you implement these custom taxonomies, such that you would be able to generate the according termID-termName options within a HTMLSelectElement, and thus be able to query the posts using term IDs for such taxonomies? Is this where you would use custom hierarchical taxonomies? We're unsure about the implementation though.. Or is there a better way?

UPDATE

Regarding the structure of the wp_term_taxonomy table, for hierarchical taxonomies, wordpress will automatically generate a new record for every different hierarchical relationship of parent-child. Hence this does not seem ideal to us; it still won't allow to predict the term ID.

The thus probably simplest approach to us seems to stick to the non-hierarchical implementation of first_custom_tax, and submit the food-healthy-apple slug of that taxonomy to the query controller. Then, before performing the, we get the ID of that term using get_term_by(), and then use the obtained term ID in the final post query.

We though that this would deliver the best possible performance for this usecase, as:

It just appears confusing to us that, get_term_by(), as linked above, executes get_terms() under the hood, but then subsequently also get_term().

According to the docs, get_terms() however executes a WP_Term_Query, and then applies the get_terms filter on the result. A WP_Term_Query itself however already retrieves the terms you query for, hence we don't understand the application of the additional filter.

We then digged further and found that WP_Term_Query::query() actually seems to be the part that is responsible for caching and bypassing DB queries, or more precisely the get_terms() function it executes under the hood.

So in the end we concluded that the fastest way to get the ID of a term via its slug, by making sure that the DB query is bypassed if the cache already holds the according term, is by directly using WP_Term_Query::query, instead of any of get_terms(), get_term_by() etc. functions. Correct?

Hence the only remaining question we have is: How long does the cache of an executed WP_Term_Query actually last? This post here mentions that the cache only lasts for the running PHP process? Ideally, we would like that cache to last forever, as the ID of a created term, as long as it exists, except if the according term will be updated / deleted, which is when the corresponding cache should be busted. Any way of doing so in WP, maybe using WP transients? Or what would be the best performant way to go here, using WordPress?

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.