Make WordPress Core

Opened 14 years ago

Closed 14 years ago

Last modified 10 years ago

#21309 closed task (blessed) (fixed)

Introduce WP_Post class

Reported by: scribu Owned by: nacin
Priority: normal Milestone: 3.5
Component: General Version:
Severity: normal Keywords: has-patch
Cc: Focuses:

Description (last modified by scribu)

In the wise words of Ryan Boren:

Just do what we did with WP_User. Add some magic methods so we can load things like ancestors on demand and get better cache behavior. Make sure everything is back compat. Do more some other time.

Previously: #12267, #16574

Attachments (27)

21309.diff​ (7.9 KB ) - added by scribu 14 years ago.
21309.2.diff​ (11.5 KB ) - added by scribu 14 years ago.
21309.3.diff​ (12.8 KB ) - added by scribu 14 years ago.
21309.4.diff​ (13.1 KB ) - added by scribu 14 years ago.
21309.5.diff​ (12.8 KB ) - added by scribu 14 years ago.
21309.6.diff​ (12.8 KB ) - added by scribu 14 years ago.
21309.7.diff​ (12.9 KB ) - added by scribu 14 years ago.
21309-ut.diff​ (3.7 KB ) - added by ryan 14 years ago.
21309-global.diff​ (480 bytes ) - added by mdawaffe 14 years ago.
21309-stickies.diff​ (553 bytes ) - added by mdawaffe 14 years ago.
21309.8.diff​ (2.2 KB ) - added by nacin 14 years ago.
stickies-get_posts.diff​ (1.4 KB ) - added by scribu 14 years ago.
21309.9.diff​ (14.6 KB ) - added by ryan 14 years ago.
Make sure get_default_post_to_edit() always returns WP_Post. Remove ref returns.
21309-auto-draft.diff​ (584 bytes ) - added by ryan 14 years ago.
21309-page_template.diff​ (638 bytes ) - added by ryan 14 years ago.
post-to-edit.diff​ (6.4 KB ) - added by scribu 14 years ago.
post-to-edit.tests.diff​ (681 bytes ) - added by scribu 14 years ago.
21309-get_adjacent_post.diff​ (811 bytes ) - added by ryan 14 years ago.
21309-get_adjacent_post-ut.diff​ (1.2 KB ) - added by ryan 14 years ago.
21309.10.diff​ (32.3 KB ) - added by nacin 14 years ago.
Eliminates most direct calls to $post, by making get_post() take an *optional* $post argument.
post-to-edit.2.diff​ (12.9 KB ) - added by scribu 14 years ago.
21309-ancestors-cache.diff​ (1.0 KB ) - added by ryan 14 years ago.
21309.11.diff​ (674 bytes ) - added by SergeyBiryukov 14 years ago.
get_the_terms.21309.diff​ (1.5 KB ) - added by scribu 14 years ago.
get_the_terms.21309.2.diff​ (2.5 KB ) - added by scribu 14 years ago.
21309.12.diff​ (625 bytes ) - added by ryan 14 years ago.
Patch the symptom
21309.13.diff​ (791 bytes ) - added by SergeyBiryukov 14 years ago.

Download all attachments as: .zip

Change History (148)

#1 @scribu
14 years ago

  • Description modified (diff)

@scribu
14 years ago

#2 follow-up: @scribu
14 years ago

  • Keywords has-patch added

21309.diff​ is basically a refresh of ticket:16574:16574.8.diff​:

  • merge _get_post_ancestors() into get_post_ancestors()
  • use get_posts() in get_post_ancestors()
  • introduce WP_Post class with magic method that calls get_post_ancestors()

#3 @scribu
14 years ago

TODO:

  • simplify logic in get_post();
  • unit tests?

#4 @toscho
14 years ago

  • Cc info@… added

#5 @sabreuse
14 years ago

  • Cc sabreuse@… added

#6 @obenland
14 years ago

  • Cc konstantin@… added

#7 @Mamaduka
14 years ago

  • Cc georgemamadashvili@… added

#8 @ryan
14 years ago

Update query.php to cast the stdClass posts in the ::posts list to WP_Post?

#9 @ryan
14 years ago

Use real properties with a get_instance() method as done in ​http://core.trac.wordpress.org/attachment/ticket/12267/wp-post-patch-take-2.diff?

#10 @ryan
14 years ago

Discussed with scribu in IRC and the advantage of the data blob is that we get __get() for all "properties". There we can check the filter property as we do in WP_User and run sanitizers without having to write individual get methods for everything.

Last edited 14 years ago by ryan (previous) (diff)

#11 @scribu
14 years ago

Update query.php to cast the stdClass posts in the ::posts list to WP_Post?

This is already handled if $split_query is true:

$this->posts = array_map( 'get_post', $ids );

but not for the other case. Will fix.

#12 in reply to: ↑ 2 @mikeschinkel
14 years ago

  • Cc mikeschinkel@… added

Replying to scribu:

21309.diff​ is basically a refresh of ticket:16574:16574.8.diff​:

  • merge _get_post_ancestors() into get_post_ancestors()
  • use get_posts() in get_post_ancestors()
  • introduce WP_Post class with magic method that calls get_post_ancestors()

I really like what you've done with this patch.

Can I suggest changing $post to 'protected' so a subclass can access it, and add @property PHPDoc comments so that we can get auto-complete in editors like PhpStorm (and so it won't flag those properties as errors?):

/**
 * Wrapper class to preserve back-compat for $post->ancestors
 *
 * @since 3.5.0
 *
 * @property $ID;
 * @property $post_author;
 * @property $post_date;
 * @property $post_date_gmt;
 * @property $post_content;
 * @property $post_title;
 * @property $post_excerpt;
 * @property $post_status;
 * @property $comment_status;
 * @property $ping_status;
 * @property $post_password;
 * @property $post_name;
 * @property $to_ping;
 * @property $pinged;
 * @property $post_modified;
 * @property $post_modified_gmt;
 * @property $post_content_filtered;
 * @property $post_parent;
 * @property $guid;
 * @property $menu_order;
 * @property $post_type;
 * @property $post_mime_type;
 * @property $comment_count;
 * @property $filter;
 *
 */

#13 follow-up: @scribu
14 years ago

Can I suggest changing $post to 'protected' so a subclass can access it,

You can have another decorator class instead, i.e. have a My_WP_Post class that contains a WP_Post instance, just like WP_Post contains an instance of a stdClass.

add @property PHPDoc comments so that we can get auto-complete in editors like PhpStorm

Sure.

#14 in reply to: ↑ 13 @nacin
14 years ago

Replying to scribu:

You can have another decorator class instead, i.e. have a My_WP_Post class that contains a WP_Post instance, just like WP_Post contains an instance of a stdClass.

If that's the design pattern we're going to suggest for now, should WP_Post be made final to protect us in doing future changes?

#15 follow-up: @ryan
14 years ago

get_pages() also needs its results cast to WP_Post.

#16 follow-up: @scribu
14 years ago

If that's the design pattern we're going to suggest for now, should WP_Post be made final to protect us in doing future changes?

Yes, if we're going to go with composition instead of inheritance, we should use the final keyword. We can remove it later, if necessary.

get_pages() also needs its results cast to WP_Post.

Also, it has a funky 'get_pages' cache, which will need some work.

@scribu
14 years ago

#17 @scribu
14 years ago

21309.2.diff​:

  • call sanitize_post_field() in __get()
  • always cast to WP_Post in WP_Query and get_pages()
  • make WP_Post final and add @property list
  • introduce WP_Post::get_instance() and make constructor private

#18 in reply to: ↑ 16 ; follow-up: @mikeschinkel
14 years ago

Replying to scribu:

If that's the design pattern we're going to suggest for now, should WP_Post be made final to protect us in doing future changes?

Yes, if we're going to go with composition instead of inheritance, we should use the final keyword. We can remove it later, if necessary.

This worries me greatly, but I assume there is a discussion thread that details the decision somewhere? Would you mind providing a link?

Reasons it worries me is I would like to be able do doing something like this:

class Event_Post extends WP_Post {
  var $start_time;
  var $end_time;
  function duration() {
     return $this->end_time - $this->start_time;
  }
}

And then being able to do things like this:

foreach( $posts as $post ) {
  if ( is_a( $post, 'WP_Post') ) {
     // Do something that requires a WP_Post object
  }
}

I can simulate the former with composition, but not in a reliable way, but I can't do the latter without composition or using conventions that not everyone will follow.


Version 1, edited 14 years ago by mikeschinkel (previous) (next) (diff)

#19 in reply to: ↑ 18 @scribu
14 years ago

Replying to mikeschinkel:

This worries me greatly, but I assume there is a discussion thread that details the decision somewhere?

There isn't, so here goes:

With the current stdClass objects, you are forced to use composition already, so there's nothing lost and nothing gained.

Maybe we come up with a more flexible extension mechanism than composition or inheritance, but that's outside the scope for 3.5.

#20 @scribu
14 years ago

Added some unit tests for get_post(): ​[UT939]

@scribu
14 years ago

#21 @scribu
14 years ago

21309.3.diff​:

  • make WP_Post::get_instance() only accept a post id and keep the complicated logic in get_post()
  • make WP_Post::__construct() public, out of necessity, but also since you can change any field anyway
  • clean up get_post_field()

#22 @scribu
14 years ago

How about adding some more magic for custom fields, like we did in WP_User:

$some_value = $post->some_key;

would be equivalent to:

$some_value = sanitize_post_field( 'some_key', get_post_meta( $post->ID, 'some_key', true ), $post->ID, 'display' );

Benefits:

  • increased conciseness and readability
  • automatic sanitization, thus better security
  • consistency with WP_User
Last edited 14 years ago by scribu (previous) (diff)

@scribu
14 years ago

#23 @ryan
14 years ago

Both WP_Post->filter and WP_Post->post->filter exist. I guess because of to_array().

get_object_vars() will no longer return much if fed WP_Post. We use get_object_vars() in get_children(), sanitize_post(), wp_get_recent_posts(), wp_update_post(), _wp_put_post_revision(), wp_get_post_revision(), and elsewhere. I notice that several plugins including the bbPress plugin also do this.

#24 follow-up: @ryan
14 years ago

Magic for ->post_category, tags_input, and tax_input would allow us to deprecate wp_get_single_post().

#25 in reply to: ↑ 24 @scribu
14 years ago

Replying to ryan:

Magic for ->post_category, tags_input, and tax_input would allow us to deprecate wp_get_single_post().

I don't know. We should keep WP_Post->__get() fairly light, as it gets called a lot.

Both WP_Post->filter and WP_Post->post->filter exist. I guess because of to_array().

Hm... I didn't notice that; will investigate.

get_object_vars() will no longer return much if fed WP_Post. We use get_object_vars() in get_children(), sanitize_post(), wp_get_recent_posts(), wp_update_post(), _wp_put_post_revision(), wp_get_post_revision(), and elsewhere.

Right, will have to fix those.

I notice that several plugins including the bbPress plugin also do this.

It was the same situation when we started returning WP_User everywhere. There's nothing we can do, except encouraging the authors to use get_post( $id, ARRAY_A ) instead.

#26 @scribu
14 years ago

On the other hand, WP_User already had a $data property, so it made sense to remove duplication by relying entirely on __get(). Real properties on WP_Post might be a good idea yet.

Last edited 14 years ago by scribu (previous) (diff)

#27 follow-up: @johnbillion
14 years ago

  • Cc Johnbillion added

@scribu
14 years ago

#28 follow-up: @scribu
14 years ago

21309.5.diff​ stores the properties directly on the WP_Post instance, which means that get_object_vars() works again and it also fixes the doubled 'filter' property.

#29 @ryan
14 years ago

How does filtering work for the properties? __get() handles meta, but I'm failing to see what happens for properties. Do we need a filter() method to set the filter and run sanitize_post() on the properties?

@scribu
14 years ago

#30 @scribu
14 years ago

We don't need a filter() method, since get_object_vars() works now. I just needed to add back the sanitize_post() call at the end of get_post: 21309.6.diff​ and ​[UT961].

#31 @ryan
14 years ago

Does the is_a( $post, 'WP_Post' ) branch in get_post() need the filter checks performed in is_object( $post ) now that we aren't using magic methods?

#32 @ryan
14 years ago

​[UT969] demonstrates the need to do the filter checks when WP_Post is passed to get_post().

#33 follow-up: @ryan
14 years ago

What's the extra update_post_caches() call in query.php for? It doesn't check $q[ 'cache_results' ].

#34 in reply to: ↑ 33 @scribu
14 years ago

Replying to ryan:

​[UT969] demonstrates the need to do the filter checks when WP_Post is passed to get_post().

On it.

What's the extra update_post_caches() call in query.php for? It doesn't check $q[ 'cache_results' ].

At some point, get_post() forced another query, even when you passed a raw post column; that's not the case anymore, so I'll remove it.

@scribu
14 years ago

#35 @scribu
14 years ago

21309.7.diff​ introduces WP_Post->filter(), which passes ​[UT969]. Also removed the unneeded call to update_post_caches().

#36 @simonwheatley
14 years ago

  • Cc simon@… added

@ryan
14 years ago

#37 @ryan
14 years ago

In [21559]:

Introduce WP_Post class. Clean up ancestors handling. Props scribu, toppa. fixes #10381 see #21309

#39 @nacin
14 years ago

In [21567]:

Return a variable reference from get_pages(). see [21559], see #21309, fixes #20756.

#12 @nacin
14 years ago

In [21568]:

Restore _get_post_ancestors() in deprecated.php to prevent fatal errors. see #21309.

#13 follow-up: @mdawaffe
14 years ago

Stickies in WP_Query cause fatal errors now.

Steps to reproduce:

  1. Create more than posts_per_page posts.
  2. Make a post from page 2 sticky
  3. View your home page.

PHP Fatal error: Call to undefined method stdClass::filter() in /Users/mdawaffe/Sites/wordpress/wp-includes/post.php on line 404

Attached 21309-stickies.diff.

#14 @mdawaffe
14 years ago

get_post() does not handle the case when no arguments are passed and $GLOBALS['post'] is not a WP_Post. I guarantee code exists that manually sets the global.

To reproduce:

$post = $wpdb->get_row( "SELECT * FROM `$wpdb->posts` WHERE `ID` = 10047" );
$post_id = 0;
var_dump( get_post( $post_id ) );

PHP Fatal error: Call to undefined method stdClass::filter() in /Users/mdawaffe/Sites/wordpress/wp-includes/post.php on line 404

Attached a possible solution in 21309-global.diff. I'm not sure what the best thing to do here is. Should the global be modified to be a WP_Post object? What will that do to anyone depending on odd reference behavior?

@nacin
14 years ago

#15 follow-up: @nacin
14 years ago

get_post() does not handle the case when no arguments are passed and $GLOBALSpost is not a WP_Post. I guarantee code exists that manually sets the global.

Yeah, I'm looking at that same thing. I was playing with 21309.8.diff​. I do think we should definitely modify the global.

Perhaps it is time to break the reference in get_post( &$post ). There's also no good reason to have it return by reference anymore either — objects are byref in PHP5. Doing both of those things should

#16 follow-up: @Viper007Bond
14 years ago

The front page of my site is currently fataling with no plugins enabled and Twenty Twelve enabled.

Fatal error: Call to undefined method stdClass::filter() in [path]\wp-includes\post.php on line 404
Call Stack
#	Time	Memory		Function														Location
1	0.0003	335776		{main}( )														..\index.php:0
2	0.0005	339496		require( '[path]\wp-blog-header.php' )							..\index.php:17
3	0.1825	19693752	require_once( '[path]\wp-includes\template-loader.php' )		..\wp-blog-header.php:16
4	0.1840	19710312	include( '[path]\wp-content\themes\twentytwelve\index.php' )	..\template-loader.php:43
5	0.2176	19792368	get_post_format( )												..\index.php:24
6	0.2176	19792400	get_post( )														..\post.php:611


#17 @Viper007Bond
14 years ago

Trac is going crazy right now with comment numbering so I can't edit my comment but I just realized this is exactly what mdawaffe is talking about, heh. Yes, I have a sticky. :)

I did a terrible job of skimming recent comments. Sorry!

#18 follow-up: @