2.x Refactor context and improve compatibility with third party plugins#1766
2.x Refactor context and improve compatibility with third party plugins#1766
Conversation
|
@jarednova Could I get your overall opinion and your opinion on the points in the Discussion section before completing this pull request? |
|
Sure thing! will wade into today |
|
@gchtr this is a great solution to (2) very old problems. My outstanding Qs are around the areas you highlight: 1. To cancel or not to cancel?My thinking here is because 95% of the time people will just use the default query, and most of that other 5% will be about query modifying ("I want 20 posts per page instead of the default"). Thus, this is for the <1% of times. Given that 2. CacheI have to admit, I'm rusty on some much of the caching use/logic. I think I understand the theoretical advantage to option 2, but also think like it seems more complicated than the potential benefit. As you note, even as pro Timber developers (indeed, we make the darn thing) it's an edge case beyond what we'd even need. I'd prefer to err on the side of simpler and wait for the people to demand a more in-depth solution. Overall this accomplishes ...
I'd call that a huge 🎉 . Let's clean up the rest of the |
Passing a post directly will make it easier to call the function, instead of passing the post inside an array.
|
Okay @jarednova, thanks for having a look at this. I think it’s now ready to be merged.
|
|
🎉 |
Ticket: #1639
This pull request refactors Timber’s context and improves compatibility with third party plugins.
Improving compatibility for third party plugins
As lined out in #1639, Timber’s current behaviour can lead to compatibility problems with plugins such as BuddyPress or WooCommerce, because…
the_posthook is called whenever aTimber\Postobject is instantiated.I figured that a way to fix both problems would be to use Timber’s context function, because that’s the function we always call in a template. Using a WordPress hook like
template_redirectcould be tricky, because developers probably will set different posts as the main post for singular templates.That’s why I added the necessary logic to mimick WordPress’s behavior for singular templates:
$wp->query->in_the_loop = true.loop_starthook.$wp_query->setup_postdata().And I removed the call to
the_postinsideTimber\Post::get_info()and moved it to a context function.A context guide
I added a new Context Guide in
docs/guides/context.mdthat will explain the concept of the context in Timber. It’s probably best to read through it in addition to what is written here.Rename function to
context()To be consistent with the naming that we applied in the rest of Timber’s classes, I renamed the
get_context()function tocontext(). Theget_context()function will be marked as deprecated.New functions
Next to
Timber::context(), I added three functions to better handle the different context caches.Timber::context_global()– Used for getting the global context.Timber::context_post()- Used for getting the context for a singular post.Timber::context_posts()– Used for getting the context a post collection for an archive.Template based contexts
The context changes based on which template is displayed. For singular templates, it will set
postin the context, and for archive templates it will setpostsin the context.This way, Timber will make assumptions about what you’re gonna do in your template. Instead of setting up your template like this:
archive.php
You could now set it up like this:
archive.php
(Little known fact: You wouldn’t have to use
$context['posts'] = new Timber\PostQuery();in the current version of Timber either. Timber did always setpostsin the context.)Of course there are a lot of cases where we need to adapt the default. That’s where context arguments are coming in.
Arguments for the context
The context now accepts an arguments array. Currently, there are three arguments supported:
post,postsandcancel_default_query.posts
The
postsargument can be an array of posts, aTimber\PostQueryor an array of arguments that will be passed toTimber\PostQuery. If it’s set tofalse, Timber will not setpostsin the context.archive.php
Up until now, there was no easy way to adapt the default WordPress template query. Now, if
postsis an array of arguments, it will merge this array with the default arguments that WordPress uses for its template query. So, in the example above, you will get the default list of posts for an archive, just without child posts.It’s also possible to pass a
Timber\PostQueryobject toposts. Then, no merging will happen.cancel_default_query
By default, parameters passed with
postswill merge with the default WordPress post query. If thecancel_default_queryparameter is set totrue, merging will be disabled. Instead, the default query will be overwritten with the parameters passed inposts.post
This can be a post ID, a
WP_Postobject, aTimber\Postobject or a class instance that inherits fromTimber\Post.single.php
You now might be tempted to try something like this:
While this might seem fine, it’s probably not a good idea. Setting
postafter runningTimber::context()will not set up the necessary globals for compatibility with third party plugins.As outlined in #1639, we can’t set up the globals inside
Timber\Post, because that leads to other problems. It’s important to tell Timber for which post it should set up globals, in case a different post ID is used.But how do we ensure that developers won’t use the example above, because it has become so prevalent in most Timber examples? And what if figuring out the post to display depends on what the context returns?
The only solution I can come up with now could be to use
Timber::context_post()separately:Caching
Timber::context()can be called more than once in a page load, for example inside hooks that display only a small component of a page. That’s why it has a cache. But because the context can now be dynamic, I see two ways to go:Option 1 – Only cache global context
If we only cache the global context and don’t cache template contexts, we’d probably have to advise developers to always set the
postandpostsparameters tofalsewhen they useTimber::context()inside actions, because otherwise additional queries would run.Or we could make it a standard to only use the global context via
context_global():Option 2 – Cache everything separately
If we introduce separate caches for global variables and template contexts, there would be four caches:
$context_cache: A cache for all global context variables (site,request,theme,user,http_host,wp_title,body_class) as well as context data set through thetimber_contextfilter.$context_chache_post: A cache for post data in singular contexts.$context_chache_posts: A cache for post collection data in archive contexts.$context_args: A cache for the arguments passed to context.The result for
Timber::context()will be put together from these caches.It should still be possible to run
Timber::context()with different arguments after a cache is already set. To be able to ignore the post and posts cache by passing in new arguments, there’s the$context_argscache. This cache will save the arguments from a call toTimber::context(). Whenever arguments are passed toTimber::context(), it will compare the arguments with the arguments saved in this cache and will either return the cache or the result for the new arguments, ignoring the cache. If a new query runs based on different arguments, this query won’t be saved in the cache. The cache from the first run will persist, including the query saved in there.Does this logic make sense? How should the context cache behave?
Personal opinion
I’d settle for Option 1, because I think it might be easier to describe and understand. In my 3+ years developing themes with Timber, I only ever required the global context inside hooks.
New filters
I first thought about using a lot of filters to change the context defaults, but then I realized it’s not really necessary. One new filter would suffice:
timber/context/argsWith this filter, we can change the arguments that are passed to
Timber::context(). When added globally in functions.php, it will affect all queries. But it could also be added in a template file like single.php or archive.php to affect only certain templates. Here’s an example:functions.php
You could also use some logic here:
Discussion
The changes introduced here might be opinionated, but I thought it’s probably easiest if I propose a solution that we can then discuss.
cancel_default_query,run_default_query,overwrite_default_queryormerge_default_query?cancel_default_query(or any other name we choose) also be used to tell Timber to not setpostin the context?Testing
I added tests, but testing is a little hard, because Unit Testing doesn’t work well with Conditional Tags, because it’s not really meant to be used for this.
Todo
Timber::get_context()is used instead ofTimber::context().