rsanchez / Deep

A set of Eloquent models for ExpressionEngine Channel Entries.
http://rsanchez.github.io/Deep/
MIT License
51 stars 14 forks source link

Fix to prevent double hydration #30

Open gstjohn opened 6 years ago

gstjohn commented 6 years ago

Hey @rsanchez, while using Deep we started to see pulling models for complex entries take a fairly significant amount of time to populate (on the order of 50 secs). I dove into the specifics of the addon and discovered that due, in part, to Eloquent that models are hydrating twice. See query log:

select * from `exp_channels`;
select * from `exp_channel_titles` inner join `exp_channel_data` on `exp_channel_titles`.`entry_id` = `exp_channel_data`.`entry_id` left join `exp_fm_search_channels` on `exp_channel_titles`.`channel_id` = `exp_fm_search_channels`.`channel_id` where `exp_channel_titles`.`entry_id` in ( "68362" ) and (select count(*) from `exp_categories` inner join `exp_category_posts` on `exp_category_posts`.`cat_id` = `exp_categories`.`cat_id` where `exp_channel_titles`.`entry_id` = `exp_category_posts`.`entry_id` and `exp_categories`.`cat_id` in (586, 60)) = 0;
select * from `exp_channel_fields` order by field_type IN ('matrix', 'grid') DESC, field_type IN ('playa', 'relationship') DESC, `field_order` asc;
select * from `exp_grid_columns` where `field_id` in ( "439" , "531" ) order by `col_order` asc;
select * from `exp_channel_titles` inner join `exp_channel_data` on `exp_channel_titles`.`entry_id` = `exp_channel_data`.`entry_id` inner join `exp_relationships` on `exp_relationships`.`child_id` = `exp_channel_titles`.`entry_id` where `exp_relationships`.`parent_id` in (68362) order by `order` asc;
select * from `exp_channel_grid_field_439` where `entry_id` in (68362) order by `row_order` asc;
select * from `exp_channel_grid_field_531` where `entry_id` in (68362) order by `row_order` asc;
select * from `exp_files`;
select * from `exp_upload_prefs`;
select * from `exp_grid_columns` where `field_id` in ( "439" , "531" ) order by `col_order` asc;
select * from `exp_channel_titles` inner join `exp_channel_data` on `exp_channel_titles`.`entry_id` = `exp_channel_data`.`entry_id` inner join `exp_relationships` on `exp_relationships`.`child_id` = `exp_channel_titles`.`entry_id` where `exp_relationships`.`parent_id` in (68362) order by `order` asc;
select * from `exp_channel_grid_field_439` where `entry_id` in (68362) order by `row_order` asc;
select * from `exp_channel_grid_field_531` where `entry_id` in (68362) order by `row_order` asc;
select * from `exp_files`;

You'll see that there are 4 queries that repeat and this is due to the call to $builder->getModels($columns) and then later to $builder->getModel()->newCollection($models) in Builder.php::get(). Both of these end up calling for a newCollection() on the model which on the Entry model results in multiple calls to $this->hydrateCollection($collection).

To fix this issue, I added a state check to the Collections which indicates if they have been hydrated. What this does is allows the call to $builder->getModels($columns) to do the initial hydration it needs to do, but skips it on the second pass while calling $builder->getModel()->newCollection($models). The isHydrated() method is not cached which would allow for any non-hydrated additions to the collection to classify the entire collection as unhydrated and would force another set up queries and population of the models.

Let me know if there is anything further I can do here, but we saw it make a 50% speed improvement in our application. Thanks!