Conversation
* decouple from filesystem (compile_string vs. compile) * decouple from PostGetter - instantiate QueryIterator directly
Some functionality will eventually move out into PostFactory or up into Timber\Timber itself.
These are broken because the top-level Posts API is currently broken. We need to switch over to using Factories across the board, and then move off of relying on QueryIterator at all.
|
ORIGINAL COMMENT FROM @gchtr 2020-08-17 #2310 (comment) Up until here, what you write makes sense to me. But I also have to say that the exact inner workings of all the classes that are in involved here were always a bit hard to keep track of for me. So if we can reduce the complexity here, that would be great 👍.
Yes! I think I already partially worked on this in #2145. Maybe you could build on that.
Doesn’t this have big implications on caching if the actual work is only done after fetching cached posts in PHP templates during And then this is probably also related to handling posts in multisite environments (see #1434 and #2207). And from what I see in issues and StackOverflow questions, a lot of developers are relying on dumping post objects to see what’s in there. This of course wouldn’t be reliable anymore. I’m not saying I wouldn’t add lazy-loading, but we need to check what it means for other parts of Timber and probably add documentation about this in various places. A related example: we already fetch meta values lazily. A lot of developers were used to work with
I’m all in favor of simplifying this. What I figured when doing more complex stuff with iterators is that as soon as we have factories ready, we can move some logic regarding globals into One specific example is this class: https://github.com/mindkomm/timber-integration-woocommerce/blob/master/lib/ProductsIterator.php. We discussed this in mindkomm/timber-integration-woocommerce#6. With factories, we can get rid of it completley and a WooCommerce product object might solve WooCommerce globals like this: class Product extends Timber\Post {
public function setup( $loop_index = 0 ) {
parent::setup( $loop_index );
wc_setup_product_data( $this->ID );
return $this;
}
} </ORIGINAL COMMENT> |
Awesome! I will take a look there, thanks!
I don't think I understand what you mean. :) Which code are you referring to as "fetching cached posts," and how is that affected by this change? I agree we'd want to document the effects of the change if it actually affects the behavior of the API. But as far as I can tell this will be completely transparent to the caller. That is, by the time you have a Again, maybe I'm missing something...wouldn't be the first time. :) I'm also cool with being cautious and reverting the lazy instantiation piece for now. If I'm right and it's truly transparent, that means we can roll it out after 2.0 lands.
Makes sense to me! Good idea. |
Also fix the seemingly unrelated testMenuItemMetaProperty(), which was failing because $item->id (rather than $item->ID) was unset. Not sure why this was not an issue before...
I think the behavior is not really affected by this change. I realize it’s probably something related to Timber in general. $posts = Timber::get_posts( [
'post_type' => 'book',
] );
var_dump( $posts );If we dump the collection, this might not contain all the data we need for posts, because posts didn’t run through a factory yet. For example, I couldn’t check here whether the correct Post class from the class maps was used. And about the cache. Imagine a custom cache mechanism: if ( false = get_posts_from_cache() ) {
$posts = Timber::get_posts( [
'post_type' => 'book',
] );
set_cache_for_posts( $posts );
}This will cache the results from the query, but not the posts themselves, because they didn’t run throught a factory yet. Caching only the query might not be want you want. Maybe you want to cache all the data. This apparently is no problem when using Another example is maybe if you want to use post data in JavaScript: $posts = Timber::get_posts( [
'post_type' => 'book',
] );
// Don’t do this.
$posts_json = wp_json_encode( $posts );
// Do this.
$posts_json = [];
foreach ( $posts as $post ) {
$posts_json[] = [
'ID' => $post->ID,
'link' => $post->link(),
// And other data you need…
];
}
$posts_json = wp_json_encode( $posts_json );That is all related to issues like #2207.
I also want to confirm I’m not missing something :D. I guess I’m merely thinking out loud and I should handle this in a different issue, which is about documentation, because it has effects which cause issues many developers have run into. Let’s keep it lazy! I will have a look at the rest later today. |
|
@gchtr gotcha. I looked into this and you're right about The good news is that JSON serialization and on-demand eager instantiation are both easy to implement. See latest updates, particularly the Quick aside about your multisite example - it got me thinking so I wrote up #2312 . Feeling much better about the state of this PR now. I'd still like to get @jarednova's input on the general direction, but for now my only remaining question is: should I also implement laziness on |
|
@gchtr @jarednova I think I was mistaken and we actually do want to use exactly the same logic from If that's the case, it's good news because it means there's even less to implement, and the latest few commits should round this out (minus any outstanding details)! |
|
I really really like the way we’re going here 🎉
I wouldn’t do that either, because I already see new issues popping up because of it.
I looked at it, very nice! Thanks man!
Right, good idea! We should definitely also have an example that explains that removing data would make sense, too, because you don’t want to fill the HTML of a page with data that has to be downloaded but that you will never need. |
|
@jarednova @gchtr yeah, consider this ready. I will follow up with a deprecation notice in |
|
@gchtr correct, I did not. |
|
@jarednova did you want to give this another look before I merge this? |
|
@acobster almost! I wanted to investigate the remaining failing tests and caught an issue that I wanted to make sure you were aware of. In b0efe7d I made a few updates to fix some test deprecation notices (easy). However, the test for I didn't know if this was already tackled in another branch or PR so I created my own to demonstrate and (potentially/hopefully) resolve in #2315. Can you review that and see if you agree with those changes? That seems to me to be the only last thing we need to tackle on this piece |
|
@jarednova your changes absolutely make sense, and I'm actually already working on those in a separate branch built off of this one. I'm not going to merge it in to avoid merge conflicts, but I'll keep your PR open so you can verify against the new branch once I push it. |
|
Great! In that case let's call this one 👍 — please do the honors of merging into the factories branch so we can follow along w that other stuff |
@jarednova @gchtr OK, let's try this again. This is the second attempt at addressing #2308. This is a continuation of #2310 which messed up the history of the branches with a bad merge.
Summary
Right now, the logic for building and running
WP_Querys is spread aroundPostQuery,PostGetter,QueryIterator, and elsewhere. Part of the goal of Factories is to consolidate all of that logic in one place:PostFactory. To allow that,PostQueryneeds to give up some power, because right now it queries for posts itself viaPostGetter::query_posts(). But if the Factory already knows what it's dealing with, there's no need for this - the factory can simply instantiate aWP_Querydirectly and wrap it with aPostQuery. Then there's exactly one class responsible for querying for collections of posts.What this means
PostQuerynow extendsArrayObjectdirectly and implements thePostCollectionInterface.PostCollectionclass completely - just need to make sure the::maybe_set_preview()logic makes it out of there.QueryIterator. More on that below.Timber\PostQueryexcept when passing aWP_Query.WP_Queryin an array (i.e.[ 'query' => $wp_query ]) when passing to the constructor. Thepost_classoption is a precursor to the new Class Maps design and themerge_defaultseems like something that would better be implemented as an option toTimber::get_posts().WP_Postobjects they wrap, theTimber\Postinstances themselves are not actually created until they're needed. SeePostsIteratorfor how this works.PostGetter. I want to be careful here as a lot of high-level functionality expected from theTimberclass is tested at the level ofPostGetterinstead, so I just want to be extra mindful about keeping that test coverage without lifting redundant tests up to theTimberlevel.A note about
QueryIteratorAs far as I can tell, this class has four responsibilities:
WP_Querythat (mostly) lives inPostFactorynowPostFactoryare various query filter callbacks, namelyfix_number_posts_wp_quirk,fix_cat_wp_quirk, andhandle_maybe_custom_posts_page. This stuff should probably be movedWP_Queryresults, which we already have inPostQuery.Paginationobjects, which again, we can do directly inPostQuery(which is added in this PR).If I'm missing something here and we need this class after all, please let me know! But if we can move out the logic we want to keep I'm hoping we can just get rid of it.
The Plan
This PR isn't quite ready to merge but it seemed like a good stopping point to have this wider design discussion, since the high-level pieces are there. But quite a few tests are broken or skipped. I imagine I'll keep skipping many of them until I'm able to switch the Posts API altogether to using Factories, but some are probably easy fixes.
I'm leaning towards getting this to a good transitional point with Collections and merging this in then, before fully switching to Factories. Just don't want the PR to get too huge. Thoughts?