Make WordPress Core

Opened 14 years ago

Closed 14 years ago

Last modified 11 years ago

#15063 closed enhancement (wontfix)

Add a "context" property to WP_Query as a 2nd param to its constructor

Reported by: mikeschinkel's profile mikeschinkel Owned by:
Milestone: Priority: normal
Severity: normal Version:
Component: Posts, Post Types Keywords:
Focuses: Cc:

Description

Through WordPress there's often a need to run a special query using WP_Query but to also modify it using filters like post_where and post_join. Unfortunately it's very hard to get the context right and to only run those filters when needed.

I'd like to propose a new property on WP_Query that adds a context which we could optionally set when we create WP_Query objects, i.e.:

$query = new WP_Query('post_type=movie','my-movie-list');

Then in a filter could do something like this:

function posts_where($where,$query) {
	if (isset($query->context='my-movie-list')) {
		// Modify the where clause here...
	}
	return $where;
}

Core could then start using it for when calling WP_Query to identify the context of the query. This could go a long way to helping up make our sites more robust.

I'll submit a patch if I get interest.

-Mike

Change History (23)

#1 @markjaquith
14 years ago

  • Resolution set to wontfix
  • Status changed from new to closed

If you're making the WP_Query, you can just do:

$myfoo->set_context( 'my-movie-list' );
$query = new WP_Query( ... );
$myfoo->reset_context();

Or, even just extend WP_Query. For instance, we have "GO_Query" at GigaOM that does all sorts of caching and magic.

#2 @mikeschinkel
14 years ago

Mark:

The use-cases I'm most concerned about are not when I'm using the WP_Query but when someone else is such as in core or plugins.

I'm doing all my work with CMS-related sites and I'm finding I probably spend >50% of my debugging time tracking down a bug that happened because I didn't get the limiting context correct in global filters. I'm looking for ways to make plugin code more robust.

By analogy I am basically trying to go from protecting myself against all the potential alternate needles in the haystack and instead wanting to focus on a single specific needle that is known. Instead of looking for and testing for "artifacts" i.e. $postnow, $typenow, $_GET, $_POST, $_SERVER['REQUEST_URI'] etc. etc. I'd like to be able to tested for a know identifier in every reasonable context.

Filters called by WP_Query is but one place where where context is badly needed, but its one of the bigger ones.

If you don't like this proposal, can we explore other ways to allow to to pinpoint context with known markers? This was less important in the blog days because you had nailed most patterns, but with CMS use-cases the number of potential patterns has increased exponentially.

-Mike

#3 @mikeschinkel
14 years ago

  • Cc mikeschinkel@… added

#4 follow-up: @Denis-de-Bernardy
14 years ago

Seems to me that always passing the wp_query object is good enough. You can add an ID property to it if necessary before running get_posts(). Suggesting we re-open if the object is not systematically passed, too.

#5 in reply to: ↑ 4 @mikeschinkel
14 years ago

Replying to Denis-de-Bernardy:

Seems to me that always passing the wp_query object is good enough.

Can you explain to me how? And I'm not sure even what you mean, "passing the wp_query object?"

You can add an ID property to it if necessary before running get_posts().
Suggesting we re-open if the object is not systematically passed, too.

I don't follow. Can you give some code examples to what you mean?

#6 follow-up: @hakre
14 years ago

In PHP you can just add public members to a class att runtime.

$query = new WP_Query();
$query->context = 'named';

now in a hook, you can check for the query object's context:

action_callback( $query ) {
  if ( empty($query->context) || 'named' !== $query->context ) 
    return;
  // your context specific code here
}

#7 in reply to: ↑ 6 ; follow-up: @mikeschinkel
14 years ago

Replying to hakre:

In PHP you can just add public members to a class att runtime.

Yes of course, but you are missing the point. Clearly I can do that for my own code, but I can't do with the objects someone else creates (without modifying their code.)

The point is that we need a standard way to inspect content in other people's code in which we have no access to the object creation, i.e. in core and in 3rd party plugins.

-Mike

#8 in reply to: ↑ 7 @mikeschinkel
14 years ago

Replying to mikeschinkel:

The point is that we need a standard way to inspect content...

s/content/context/

#9 follow-up: @Denis-de-Bernardy
14 years ago

Well, if it's the main loop, yipou can always check for $wp_query === $wp_the_query. For everything else, assume it's a plugin, and not yours, unless the context, or ID, or whatever, is specified.

#10 in reply to: ↑ 9 @mikeschinkel
14 years ago

Replying to Denis-de-Bernardy:

Well, if it's the main loop, yipou can always check for $wp_query === $wp_the_query. For everything else, assume it's a plugin, and not yours, unless the context, or ID, or whatever, is specified.

Yes, there is (almost) always something I can check. The problem is reliably discovering what that "something" is in each case.

Currently you have to program by artifact and by assumption, i.e. "I'll check for $pagenow=-'edit.php' and $post_type=='movie' that'll be good enough, right?" only to find out that after the site is deployed I also needed to check for something else.

The current situation is much like trying to prove there is not a needle in that haystack vs. just finding it.

In a blog world, this isn't a big issue because there are only a few use-case patterns and WordPress has mostly nailed them. But with WordPress being used as a CMS the number of patterns to address are at least an order of magnitude greater, if not two and WordPress is a long way from having nailed them.

It doesn't create a very good impression of WordPress when I tell my clients "Well, I can't be 100% sure that the code I wrote won't have an issue until after you've used it for a while to see if I missed any assumptions." I want to be able to say "I know that code works, period" but without some sort of explicit context to inspect that's not possible. (Note, I'd be happy to see other implementations than what I proposed, but we need something.)

Said another way, it's why local variables are useful vs. everything being global. There once was a time when programming languages didn't have local variable scope; what a nightmare that was.

#11 follow-up: @mikeschinkel
14 years ago

As I think more about this the context property proposed should probably be an array treated as a stack for adding and removing, and a collection for inspection.

#12 in reply to: ↑ 11 ; follow-up: @scribu
14 years ago

Replying to mikeschinkel:

As I think more about this the context property proposed should probably be an array treated as a stack for adding and removing, and a collection for inspection.

Seems to resemble the WP_Query object itself, doesn't it?

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

Replying to scribu:

Seems to resemble the WP_Query object itself, doesn't it?

How so? How is a property with information to be annotated to the object that it currently doesn't contain the same as the object itself?

-Mike

#14 follow-up: @scribu
14 years ago

That's exactly the point. You can already annoate information to WP_Query yourself.

#15 in reply to: ↑ 14 @mikeschinkel
14 years ago

Replying to scribu:

That's exactly the point. You can already annoate information to WP_Query yourself.

And you are exactly missing the point; we need to introduce a way that others will annotate WP_Query in a standard way (especially in core) so we can start to depend on it.

My issues don't arise when I'm in full control of the code but when instead I'm trying to modify code someone else wrote using a global filter. In those cases I cannot annoate information to WP_Query myself.

#16 follow-up: @prettyboymp
14 years ago

I have a couple of reservations about adding a 'context' as a 2nd parameter. First, it can be added as a query_var passed into WP_Query that is just as easy to access as if it were a direct property of WP_Query. The second is that 'context' describes the theme context, but not really the data that needs to be returned. A query_var that related more to the filter would be better here.

$query = new WP_Query('post_type=movie&my_list=1');
function posts_where($where,$query) {
	if ($query->get('my_list')) {
		$where .= //my new restraint
	}
	return $where;
}

This not only better describes the filter, but allows you to potentially pass in several query modifiers instead of just one that context would represent.

#17 @scribu
14 years ago

prettyboymp: my thoughts exactly.

#18 follow-up: @scribu
14 years ago

  • Milestone Awaiting Review deleted

So, since you won't be able to assign meaningful contexts automatically anyway, might as well encourage plugin authors to do:

get_posts( array( 'post_type' => .... 'context' => 'my_plugin_get_those_things' ) );

You could then do:

$context = $wp_query->get('context');

etc.

#19 in reply to: ↑ 16 @mikeschinkel
14 years ago

Replying to prettyboymp:

I have a couple of reservations about adding a 'context' as a 2nd parameter.

Thanks for sharing your specific concerns.

First, it can be added as a query_var passed into WP_Query that is just as easy to access as if it were a direct property of WP_Query.

True, but I equally have reservations about storing it in query_vars:

1.) Less important but still a concern is that storing context in query_vars turns it into a reserved word and thus can no longer be used for actual queries. I think it's reasonable to expect that people might want to create "Context" custom post types and/or have use-cases where the term "Context" was in their problem domain vs. the technical domain. And this use of context feels to me to be more on peer with query_vars than being one of many query vars.

2.) More important is what I wrote in comment 11 which is I think we need multiple contexts much like you can put multiple CSS classes on a single HTML element and be able to inspect each separately. If it's stored in a query_var this really doesn't work; it probably needs it's only mini "context" API for adding and inspecting.

The second is that 'context' describes the theme context, but not really the data that needs to be returned. A query_var that related more to the filter would be better here.

I both disagree and concur at the same time. I don't agree that my example was specific to the theme context but I do agree that we need to know the context of the query (you call it "filter" but I think you really meant "query", right?)

Basically we need to be able to pinpoint a logic path and be able to say "I want my filter to apply to *that*, and that only!"

This not only better describes the filter, but allows you to potentially pass in several query modifiers instead of just one that context would represent.

And here I think we are in sync regarding the need to have multiple contexts as CSS has multiple classes. Maybe what we need is a WP_Contextand a global $wp_context instead?


So I should have done this up front so let me give some examples:

http://example.com/wp-admin/edit.php?post_type=page

WP_Query is called on line 912 in wp_edit_posts_query() and has the following values in $wp_query->query_vars:

  • post_type="page"
  • order="asc"
  • orderby="menu_order title"
  • posts_per_page=-1
  • posts_per_archive_page=-1

So there's no real context for me to draw on except to inspect $pagenow='edit.php' and $_GET['post_type']='page'. It would be much nicer if context were set on line 912 and I could just inspect $wp_query->context to see if it were equal to 'admin-page-list'

Moving on to the page editor:

http://example.com/wp-admin/edit.php?post_type=page

On line 4452 in wp_get_post_autosave() WP_Query gets called and assigns the following values:

  • name="2-autosave"
  • post_parent="2"
  • post_type="revision"


Now I can guess that if name is preg_match('#^([0-9]+)-autosave$',…) and post_status=='inherit' and $pagenow=='post.php' and isset($_GET['post']) and 'isset($_GET['action']) && $_GETaction?=='edit' that I'm getting the post autosave from the admin page but is would be immensely easier if I were simply able to inspect $wp_query->context=='get-post-autosave'`.

On the dashboard:

http://example.com/wp-admin/

On line 485 of /wp-admin/includes/dashboard.php the function wp_dashboard_recent_drafts() calls WP_Query with query_vars of

  • post_type="post"
  • post_status="draft"
  • author="1"
  • posts_per_page="5"
  • orderby="modified"
  • order="DESC"


Again, nothing besides artifacts that tell me this was being called for the dashboard's recent posts. $wp_query->context=='dashboard-recent-posts' would be better.

Of course in the prior example, 'dashboard-recent-posts' is being called in the context of a metabox but how would I know that? The function do_meta_boxes() on line 2830 of /wp-admin/includes/template.php exports no context information. Having this use a global $wp_context could really help.


Anyway, I don't feel like I know what the fully correct solution is yet but I do now that there is a problem; I'm hoping others will see that problem and help come up with a workable solution.

-Mike

#20 in reply to: ↑ 18 @mikeschinkel
14 years ago

Replying to scribu:

So, since you won't be able to assign meaningful contexts automatically anyway, might as well encourage plugin authors to do:

get_posts( array( 'post_type' => .... 'context' => 'my_plugin_get_those_things' ) );

You could then do:

$context = $wp_query->get('context');

It's not really helpful unless whatever it is becomes a generally accepted best practice, and core implements it.

#21 @kevinB
14 years ago

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

#22 @kevinB
14 years ago

  • Cc kevin@… added

Related: #17019

Note: See TracTickets for help on using tickets.