Brain-WP/Hierarchy

Possibility to change Template Hierarchy

Closed this issue · 4 comments

I was just thinking if you'd consider adding a possibility to change the template hierarchy. Either to simplify or to extend.

Currently I'd need to extend QueryTemplate and Hierarchy to have a custom findTemplate().

I don't really have a real world use case at the moment. But here just a somewhat weird but simple example:
Imagine I'd like to have a template "before" archive.php, archive-2.php that only kicks in when WP_Query->post_count === 2

In WordPress core (and in this packages as well) it is possible to change the obtained template by using {$type}_template and template_include filters.

In core those filters does not pass the query object as context, because templates in core are only intended to be used with the main query. So your simple example can be achieved with:

add_filter('archive_template', function($template) {

  if ($GLOBALS['wp_query']->post_count === 2) {
      $archive = locate_template('archive-2.php');
      $archive and $template = $archive;
  }

  return $template;
});

and this will work for both core and Hierarchy (when resolving template for main query).

Hierarchy, in pursuit of core compatibility, does not pass query object to those filters, even if the act of resolving templates for arbitrary query objects is one of its main aims, so maybe it would make sense to pass the query object to those filters, or even add package-specific filters that do that, e.g. hierarchy.{$type}_template and hierarchy.template_include.

The second approach would be to add a filter on hierarchy "leaves" here.

The third approach (which I already though about), is to make the entire Hierarchy::$branches array filterable (ensuring all items implement the proper interface after filter).

The 3 approaches are in ascending order of easyness and speed, but in descending order of core compatibility.

Still not sure which approach to take.

Of course I was expecting that you've already thought about that 😉

I was aware that you can just use the {$type}_template filters, but as soon as you use any Finder you'd need to duplicate some logic of those inside the filter because e.g. locate_template() isn't looking in subfolders. So just changing the template hierarchy instead of hijacking the template selection works better and cleaner for any Filter deviating from WP default. So I'd say either option 2 or 3 would be great to have, while of course just manipulating the branches array is the easiest to use.

On the other hand core compatibility is a valid concern. So messing that up also shouldn't be too easy.

What I thought about is extending Brain\Hierarchy\Hierarchy and giving an instance to \Brain\Hierarchy\QueryTemplate because that would also make it easier to use multiple different hierarchies at the same time.[1] When filtering the default Hierarchy this is becoming a bit of a hassle because it relies on timely adding and removing of filters.

But I think first of all it boils down to the conceptual choice of how close to core you want to stay and if deviating should be possible at all.


[1] In case you wonder what this could be good for. I am currently investigating using two instances of QueryTemplate, one to determine the wrapper and one for the actual content template.
So e.g.

templates
   -layouts
         archive.php
         index.php
   -template
         home.php
         index.php
         single.php

So now one QueryTemplate determines the wrapper to use (templates/layouts/*) and inside that I then include the actual template (templates/template/*). And maybe I'd like to have an extended template hierarchy for templates, but trim down the wrappers hierarchy.

So, happy to say that now hierarchy is filterable.

In two ways, actually.

The first is the core filter "{$type}_template_hierarchy" that will be added in WP 4.7. This filter gives the chance to filter the possible templates for each hierarchy "branch".

After that, it is also possible to filter the whole "branches" array, via the 'brain.hierarchy.branches' filter. The obtained array is filtered to contain only names of classes that implement BranchInterface.

Finally, it is possible to disable both kind filtering. That can be done by passing the flag Hierarchy::NOT_FILTERABLE to constructor, instead of the flag Hierarchy::FILTERABLE that is passed by default.

E. g.

$hierarchy_a = new Hierarchy(); // filters will be triggered

$hierarchy_b = new Hierarchy(Hierarchy::FILTERABLE); // filters will be triggered

$hierarchy_c = new Hierarchy(Hierarchy::NOT_FILTERABLE); // filters will NOT  be triggered

Changes are merged on master branch already. Version 2.3.0 is not released yet, becaus eI want to updated the README first. Will close this issue when 2.3.0 is released.

...and 2.3.0 is here.