I've had a couple of conversations recently on the question of context vs. configuration for plugins. The basic question revolves around whether a given piece of dynamic information for a plugin should come from the context or from the configuration object. And if there's a case where it could be either, whose responsibility is it to decide that?
Dmitri made the argument that since configuration could "mask" values in the context, a plugin should only get a configuration object and the config object should have the context object within it. Then the plugin asks the config object for information and the config object may or may not defer to the context internally.
I don't recall the full details but he made a valid argument. I'll leave it to him to remind us of the details. :-) I believe it mostly revolved around centralizing the logic for if (use config) { $config->stuff(); } else { $context->stuff(); }
However, I then had a long conversation with Earl in which we concluded that no, context and configuration are too separate. They should not auto-mask each other. For one thing, configuration is static per instance while context will (may) vary per request.
That said, there still needs to be a way for the config object to influence what context value is used at all. For that, we came up with two possibilities:
1) Before instantiating a plugin, we allow the config object to manipulate the context object. (Remember we're talking about it being mutable when created and then being locked.) That lets the config object do most of the mocking needed. Pseudo-code would be something like:
<?php
$config = new Configuration($data_from_storage);
$ctx = $context->mock();
$config->alterContext($ctx);
$t = $ctx->lock();
$plugin = new Plugin($ctx, $config);
$plugin->doStuff();
?>2) Use indirect key name definitions via the config object. That is, the context object will have various keys in it, but the plugin won't ask for them directly. Instead it will ask the config object which ones to ask for. To wit:
<?php
// Instead of this:
$this->context['node'];
// We'd have this:
$this->context[$this->config->contextKey('node')];
?>And then we can configure what contextKey('node') will return via the UI, array definition, or whatever.
The challenge there is that it makes defining what context keys a given plugin will depend on more difficult, which is critical for ESI. Although, perhaps that would force us to move it from a definition hook to the config object, which makes it more dynamic... so that may not be a bad thing after all.
Of course, these two approaches are not mutually exclusive so there's no inherent reason we can't do both. Or at least I can't think of one off hand.
The other realization is that this logic is not specific to blocks. Any type of plugin could potentially be this complex, and want context-based caching. Case in point, views query plugins. They would cache based on the incoming arguments to the View. Or text format filters. Those would want to cache based on the incoming node/field. Etc. So this is an issue that should be commonly addressed in the base system.
Lots more to think about here. Discuss. :-)

Comments
Quick thoughts
A remark that matters to no matter what approach. we should be able to check $configuration->is_enabled() or something so we don't need to load plugins if the configuration object tells us it's not needed.
It seems that the second approach needs more registry and will have a context object that is bigger, and maybe knows a little too much.
Also, i don't know what the limits of a plugins capability exactly is, but it seems like plugins should be able to - besides returning filtered and manipulated confuration aspects - alter the context object before the actualy are invoke an execution method? And if yes, is the context locked in this state?
But this would mean that every object is allowed to alter every objects properties.
And last, a plugin is asked to do something while analyzing the context and configuration. Then it could be handy to have the plugin notifying the context object what has been done. Or am I already off the line here?
God object
I think you're drifting into God Object territory, even moreso than the context already is. :-) Thing of context as just parameters on lots of steroids. It's not intended as a global state tracking object, so plugins shouldn't be responding through the context object to other systems. (At least I don't think so.)
Allowing every object to alter every object's properties directly is what we don't want to do. We want to potentially allow that only indirectly, before the plugin is instantiated.
Agreed
Then i fully agree and maybe interpretted it wrong :)
So basically communication between the objects is a plugin with parameters, where the plugin itself will need to calculate the correct needed values? This will be where configuration overrides global context properties or vice versa i persume.
Maybe
That's kind of what we're still trying to figure out. :-) There's 3 objects involved.
1) The plugin: This is the actual behavior logic itself.
2) The context/butler: This is the information about the request, and things derived from the request.
3) The configuration object: This is user-supplied configuration that distinguishes one instance of a plugin from another.
The plugin object gets passed both a context and a config. The open question is where the line is between the config and context objects, and what their interaction is.
Hard question to answer
Can only speak for my self and my experiences building my own "interpreter" for the input URL. ; in short this is what I decided to use in boost via a hook with a modified weight so boost's hook always runs last (thus default); hook_boost_menu_router(), function is boost_boost_menu_router(). Good example in here of loading up some extra info is panels
<?php// Handle panels
if ($router_item['page_callback'] == 'page_manager_page_execute') {
$subtask_id = array_shift($router_item['page_arguments']);
$page = page_manager_page_load($subtask_id);
$task = page_manager_get_task($page->task);
if ($function = ctools_plugin_get_function($task, 'page callback')) {
$router_item['page_callback'] = $function;
}
$router_item['page_type'] = $page->task;
$router_item['page_id'] = $page->name;
return $router_item;
}
?>
Communication
I am Pro the fact that the plugins can be addressed for instance come in later on due to a weight property. That's what i meant before when bringing up the pushing back to the context object. However, i would not let the configuration object do anything. That's a parameter object that can only expose values to the plugin itself. The plugin then may choose to push a value to the context object, so this value could come from the configuration object, but only the plugin should know this. This would give the extensibility a plugin needs.
So configuration and context are totally loose coupled and have nothing to do with each other. I did the same in heartbeat module with a Stream and StreamConfig for drupal7. I say this because i just refactored it so the configuration is never asked anything, only from the Stream itself. Since the stream knows stuff about the number of messages and such, its invoker that needs to build html as well, can be notified (push back) so the builder knows. Here i lack a context object so dirty calculation is needed in the builder. This is where drupal8 will rock, i hope.
But then that context object: Sometimes it seems as a choice has been made here. This is the most important key in the whole butler concept imo. The steroids part, i don't see that clearly. A context object would get values of a response, i read elsewhere. As contributed module can contribute to the request and thus context, the locked state seems very usable IF there is a context pre (bootstrap) phase where everything is registered before it's locked. E.g.:
- core caluculated all basic things (output type, user stuff, node stuff, ...)
- og comes in and has to be able to set a group id so every module or plugin could use this new added context-concepts.
- a user relations module comes around and says hey, for the logged-in user, i want to add a userlist for the friends.
- Even further, that user relations module detected that their is a displayed user involved, so it wants to be able to calculate the friends for that displayed user as well.
This last one is the tricky one i think. Suppose we are in a node somewhere and the site builder has a concept of showing something for the displayed user. Or a panel that is able to expose it's author as displayed user so it could later be retrieved by the context object.
So here further in the drupal process a displayed user is calculated. I would be almost necessary to ask this stuff to the context object. Here i ask myself: wouldn't the context object be in a locked state already.
An example i have so much difficulty with in drupal6: Heartbeat as activity module wants to show a stream of activity of the friends for a displayed user, calculated somewhere. So you see lots of global context things should have been done here. On that site, a chat module is enabled that has the same problems: Which userrelations module is enabed, What are the friends
So that's why i brought up in previous posts to have very difficult and exotic use cases described. So we don't miss anything.
Context or State?
I am looking for a little insight to put this into a Drupal Context
I also agree that configuration and context are losely coupled at best. I think of configuration as the information that is used to define starting context for an module. System design will determine if a module will need to pull configuration changes, configuration changes will be pushed to the module, or the changes will only be used on the next system or module restart.
Now this may be way off base but I think of the use of context sorta like a CMS, there are modules that want to publish context and there are modules that want to subscribe to context. A Modules context is dynamic and ceases to exisit when the module has stopped running. Another case is one in which a module creates or changes system context that will need to be persistent for other modules even after the module that created it has ceased to exist.
Good description
I like that description, Mike. Yes, there is a publisher/subscriber type relationship going on between a Context Handler and a Plugin, mediated by the Butler.
Another, similar way of describing it is that Context is information that depends on the HTTP Request. Configuration is information that depends on the database state (or code-exported form thereof).
The tricky part comes in when, as above, you may care about one of them or not depending on the other.
Mediation
If this can be looked at as a publish/subscribe model then the publishers of context only needs to conform to the publishing rules for the system it is a member of. Its not up to the publisher to be able to identify how the system context information is to be used. Why does the Butler have to mediate if it can in fact look like a publish and subscribe model? Sorry I came in late Butler was not part of this post, ref for Butler explaination?
Drupal is an event driven system "Change of state (COS)" or "Change of context" are just a diffferent class of events that are external to my context. I think that a module would have the ability to subscibe to push or pull subscribe for systenm context information. In any case I think that it is the responsibility of the module subscribing to the COS to determine its relevance or the need for the module to have subscribe pull additional system context information to determine actions to be taken. I think that this starts to point to a question if this is a design model that is suitable and if so what methods are needed. IE Context manager with a published list of system context vaiables with the ability of modules to publish system context.
Not changes
The idea of the Butler object is to mediate requests for context in a centralized way. It's not to manage changes of state. Changes of state are already well handled by hooks so I don't think we need to do anything there.
That is the long and short of it. :-)
Not just for blocks
You correctly identify caching as one of the main beneficiaries of this core change. I hope to soon be expanding what I store in the boost_cache table so that the cache expiration logic will be better then ever. Pulling the page number of of the URL as well as arguments make for much smarter logic. Quick example is the taxonomy term view; In boost I currently track 3 levels but I think I will need 6; extra 3 for the argument, query string and page number. Example hierarchy for this url
example.com/taxonomy/term/8?page=3would bepage_callback: view
page_type: taxonomy_term
page_id: page
page_args: 8
page_querystring: NULL
page_number: 3
This level of granularity will allow me to flush the first 3 pages of the taxonomy view for term #8 when a node tagged with that term is created. On cron I could flush the rest of the pages in that view. This solves unnecessary cache expiration of the later pages (page 4-99) in the view and allows for a higher cache hit rates without compromising too much.
Simple question
When you say Configuration Vs. Context, I don't really see the point here. How do you define "configuration" ? If I refer to what my brain says, configuration is what the admin want is site to be, not really some contextual information.
Do you mean by configuration something that looks like "preferences for users", e.g. system variables that each users can override (except here you replace the "user" word by the "context" one) ?
Pierre.
Exactly
The difference between the two is somewhat subtle. :-)
For the moment, let's consider "context" to be "things derived from the HTTP request, directly or indirectly" and configuration to be "things the admin setup via the GUI to be the case at certain times."
The catch, of course, is that at any given point you may want a piece of data from one or the other of those, eg, the "current node" being viewed vs. the "node the admin specified in the UI." The code shouldn't care which is which; it just wants "a node".
Your definition of context is
Your definition of context is exactly what I understood.
Ok, so that's actually the same as "preferences" but for contextes. You expect your contextes to interact with configuration variables, is it a good pattern? Context should only be information somehow, and business specific code should adapt itself depending on it, don't you think?
You don't really configure the path you are in or the node you are displaying. Or then you have misplaced variable maybe. Tell me if I'm wrong but, yes, I could imagine some stuff playing with variables as we can see with overrides in Dev Seed modules, but they do it exactly because there is actually no context implementation in core, it's more a hack than a real pattern.
However, I can imagine some specific use case where variable would be overiden (maybe a "developer" context where you activate some kind of statistic aggregation).
Pierre.
Not quite preferences - I
Not quite preferences - I think what Crell means (at least what I understand) is that context includes:
a. Information derived from the HTTP request
b. Information that comes from the Drupal site itself (i.e. this node is the German translation of this other node).
This is not preferences but rather context information that is relevant to constructing the page but not derivable from the HTTP request.
Understood that, but I really
Understood that, but I really think that the "Configuration" word usage is abusive. May be you probably meant "Default behavior" Vs. "Context override" ?
EDIT: What I meant is by choosing the right words, you will definitively have the right answers in already existing patterns and solutions. This one sound interesting and I really would like to understand it well. Can you give me some practical use cases?
Pierre.
I think its about time
I look at configuration as a static definitaion which is used to define a startup or initial context at time equal 0:0:0. I think that context also is defined by context owner as well the runtime scope of visability. After time 0:0:0 runtime context is dynamic and the ability to alter context follows a trditional owner and permission model to manage the context elements as well as scope. I beleive that I can change a context value whos initial condition was defined by configuration without the being reflected in the configuration.
Yes that stills tickle me a
Yes that stills tickle me a bit, maybe the "configuration" word is ambiguous in my mind. I still don't clearly see uses cases in fact, and I think that's what really missing in head. This discussion must have launched a long time ago, after some IRL chat, and because of this, I think revelant information is missing here, I can't debate code or abstraction that are here to solve a problem without knowing what is this problem.
Pierre.
Something I've been looking
Something I've been looking for as I read through this is how effective the perspective of the Context module and the Features module are in identifying what Context and Configuration mean here. The conversation (quite rightly) has been rewinded, but now I'm hazy on where this is trying to go.
I tested implementing using the chain of responsability pattern
I did a simple (working, actually) implementation over the actual D7 stable of what could be a router context. This implementation is somewhat wrong because I did too many abstractions (I didn't differenciate router and business contextes, see http://groups.drupal.org/node/130419).
In my implementation, I use the chain of responsability pattern for override, which means that more than one context can coexists, and must be pushed in an order of importance by modules or the router. When you fetch the context using a static registry (could have been a singleton pattern here, but I wrote it fully static) you have the latest context pushed. When you call a method on it, if it doesn't handle it, it just implements the chain of responsability pattern by callling it's parent same method.
It creates a graceful override, with many levels (potentially you can have a inifinite number of contextes) each one of them are being called in the importance order until one handles the called method.
It also does provide a namespace based autoloader instead of using the core's one. The core autoloader is defective by design because it won't scale. The more modules will you use the more slow it will be and the more it will consume memory. Namespace based (namespace as module namespace, not PHP 5.3 namespace) works with a naming convention, and its execution time is linear.
See this patch
Pierre.