Phase 1: Context

Events happening in the community are now at Drupal community events on www.drupal.org.
You are viewing a wiki page. You are welcome to join the group and then edit it. Be bold!

This is mostly a repost with some tweaking based on the previous discussion thread. The biggest difference is the adoption of a global "context stack" to handle legacy components. I've also merged Phase 1 and Phase 2, since both are part of the context system itself.

Step 1:

During bootstrap, we create a context object instance of a class with a defined interface. This object will initially contain information derived from the HTTP request itself. The context object will be responsible for ensuring sane defaults are returned in unusual cases.

  • GET parameters
  • POST parameters if any
  • Cookie values
  • Session information
  • $_SERVER information
  • Additional information available directly from the HTTP request or equivalent

It will also include the following derived information that other code may assume is always present. The context object will be responsible for ensuring sane defaults are returned in unusual cases.

  • Current language
  • Requested path (both raw and de-aliased)

The context object will also offer centralized access to the following derived contextual information that MAY be lazy-loaded on request.

  • Current user (Because this is explicitly driven by session/cookie in the Request)
  • Level-1 loaded entities (i.e., what is currently loaded by %node and similar menu callbacks)
  • Pager state
  • Others that may be found during implementation

The context object will not be alterable. Once a given element of context has been determined, it will remain constant for the remainder of that context object's lifetime.

The context object will, however, provide a mechanism to wrap itself in a mock context object that allows for overriding of selected context elements at creation time only. This mechanism is modeled on the mocking mechanism present in many testing frameworks. The wrapped context object may then be passed on to other routines which will then not know that those elements have been mocked.

Every time a context object is mocked, the newly mocked object will be pushed into a central context stack. When a mocked context object is no longer needed (either explicitly or it goes out of scope or some other trigger causes it to be necessary) it will be popped off of the global stack.

Dries has suggested investigating similar Java-based systems that implement a concept of context for dependency injection and will provide resources for further investigation.

Step 2:

A global accessor mechanism, most likely a drupal_get_context() function, will be provided. This accessor will do nothing except return the current context object so that modules and core APIs may act upon the context as appropriate. The "current context object" is defined as the top-most object in the central context stack described above.

Object-oriented systems that require the central Drupal context will be designed to accept a context object as a constructor parameter. It will be considered a bug for such a system to call the drupal_get_context() function directly.

Procedural systems may if appropriate be adapted to accept a context object as a parameter. Alternatively, they may be adapted to call drupal_get_context() in their first line to gain access to the current context. That distinction will be made on a case-by-case basis.

Step 3:

All existing uses of one-off context in core will be removed in favor of the central context object. These include, but are not limited to, arg(), drupal_get_normal_path(), the global $user and $language variables, the $pager_* globals, all references to PHP super globals ($_GET, $_POST, etc.), and numerous other instances of scattered context. This functionality will be available in a more robust and abstracted form in the context object. Once all uses of such scattered context are expunged, the existing mechanisms will be removed. (That is, arg() will be removed from core in this step. There will then be a party that includes ponies.)

Step 4:

A backport of the context object will be maintained for Drupal 7 such that forward-looking modules may leverage it instead of the existing disparate mechanisms, allowing for cleaner code and a smoother transition to Drupal 8 when the time comes.

The Drupal 7 implementation of the context object will likely be a simple wrapper around the existing functions and globals in many cases. However, as long as the Interface for the object remains consistent between the Drupal 8 core and Drupal 7 contrib implementations that is not a problem.

Step 5: Extended context

A registration system will be provided to allow modules to register components with the context object that will answer additional contextual requests on demand, such as:

  • Current Organic Group(s)
  • Current user-blog
  • Book module navigation location
  • Forum hierarchy
  • Any others that a module chooses to register itself for.

The exact mechanism for such registration has not yet been defined. The context object will be responsible for ensuring sane defaults are returned in unusual cases or if a given additional context is not registered.

The Services module for Drupal 7 may serve as a testbed for this effort, as the maintainers have indicated that it would greatly simplify their implementation.

Discussion points

  • Is the "context stack" a good middle-ground between "global pile'o'crap that's still surprisingly flexible" (current Drupal) and "pure OO dependency injection theory that is 10x more testable than what we have now"? Or is it too complicated?
  • If it's too complicated, what's the alternative?
  • How exactly does the registration system for extended context work? If two modules try to register themselves at the same time, for the same piece of information, how do we resolve that?

Comments

For this part I prefer the

dmitrig01's picture

For this part I prefer the order of things to be like http://groups.drupal.org/node/64178. Note that the end result is the same, the process is just different.

"Is the "context stack" a good middle-ground between "global pile'o'crap that's still surprisingly flexible" (current Drupal) and "pure OO dependency injection theory that is 10x more testable than what we have now"? Or is it too complicated?"

Looks good to me.

I am strongly in favor of the

merlinofchaos's picture

I am strongly in favor of the global context stack, assuming the performance penalty of doing it isn't too bad.

Not too bad

Crell's picture

At least the quick implementation I threw together to see if we could didn't look like it had any serious pain points in it. Most of the objects being created have little data in them on creation, so don't take much time. The only challenging part performance-wise is unrolling the stack back to a specific point. That requires having an array indexed by spl_object_hash(), which I think is pretty fast but I've not benchmarked it myself. Sam can probably weight in here. And of course there's a loop, but it is unlikely to iterate more than a few times. (I'd love it if there were an array operation that would do it for us, but I'm not sure if there is. May take some benchmarking.)

My bigger concern is the DX issue, as it introduces a new tracking variable that has to be kept track of as well when mocking.

Wow. Much to my surprise,

sdboyer's picture

Wow. Much to my surprise, spl_object_hash() is actually kinda slow. Roughly 6-7 userspace function calls, or 3-4 creations of a stdClass object.

However, if the need is to have an array keyed on object hashes, then...well, we could hope for PHP5.3 for D8, as the SPLOS (say it out loud, it's fun!) implements ArrayAccess as of PHP5.3. Which means this code works:

<?php
$a
= new stdClass();
$splos = new SplObjectStorage();
$splos[$a] = "i'm a value keyed on an object instance";
print
$splos[$a]; // "i'm a value keyed on an object instance"
?>

In other words, that object hashing is all done internally. Much faster. The SPLOS is also iterable, and does some other nice things. Of course, if we have 5.3, we might just want to use a http://us2.php.net/manual/en/class.spldoublylinkedlist.php .

If we don't have PHP5.3, we could always simulate the behavior in userspace. I've got such an implementation here: http://github.com/sdboyer/svnlib/blob/master/src/lib.inc (SplObjectMap, right at the top).

Funny side note: I glanced through the email notification of your post, and "spl" jumped out at me faster than any other word. Shows where my heart lies :P

Stack slicing

Crell's picture

Well, what we really need is a way to uniquely identify a specific context object in the stack, and then say "if it's in there, strip out that and everything above it; if not, do nothing". spl_object_hash() and a loop was just the first approach that came to mind. We may be able to get away with something simpler; that would take some investigation. (Would just a sequential integer ID work in this case? I don't know.)

If the operation isn't being

sdboyer's picture

If the operation isn't being run very frequently, it's not THAT big of a deal. I can't visualize the code this would apply to, though, and given how critical this is, it'd probably be prudent to invest mental energy in seeing if we can just do it with numbered indexes.

Numbered indexes

dmitrig01's picture

Numbered indexes should be fine but this is an implementation detail that can be worked out later.

Why a stack?

ronald_istos's picture

I am sure I am missing something very obvious here so please bare with me. If I got things correctly there is not just one context object but the possibility of a "real" one + mock objects that are created based on some criteria and to satisfy needs such as testing (although theree are plenty of cases for mocking context).

Now, why are these mock objects placed in a stack? Are these mock objects to be usedin the order they are placed in he stack or is this simply not a LIFO structure? If not why call it a stack and not something else?

Is it that one context leads to the creation of another, hence they are intricately tied and if an older context becomes no longer relevant then all the ones above it are irrelevant as well?

Thanks, Ronald

Context hiding

Crell's picture

There will be parts of the system that are difficult or impossible to explicitly pass a context object to, so instead they get their context object by asking for the current global context object via drupal_get_context() or some such. If the context has been mocked for whatever reason, then we need to affect that call as well. The easiest way to do that is to have a stack of context objects, each one wrapping or mocking the one below it. Then calling code cannot, ever, access anything but the top-most context object. When we are done with the mocked object, we pop it off the stack and the next time we ask for the current context we get the next object down instead, which is what we want.

The stack is for "hiding" low-down context objects from the system to ensure that we are mocking context system-wide.

Does that make more sense?

Meeting notes

Crell's picture

Highly relevant discussion here: http://groups.drupal.org/node/70738

Please read it for context in this thread. :-) (No pun intended.)

Butler to handle api contributions

Stalski's picture

In reply to

Any others that a module chooses to register itself for.

but like the link above, a use case that matters.

I have feature request that i think would be done by the butler. This comes from a problem i have in drupal6 with contexts living in API contributions. I gave up the thought that i could pull it off in drupal6 since it's rather a big change. But after reading this, it's exactly the right place to bring this up.

ModuleA is a user relations API. ModuleB is a module who needs the contexts of "friends of a user".

In drupal6, you need a sort of api detector to know if there is a moduleA that could invoke a "get_friends" request. From there moduleB could then know which method or query it has to perform to get those friends. As i don't like the if/else statements for this and i shouldn't have to care how my integration for all the moduleA's should be done.

So in my opinion, moduleB should be able to use registered user relations to just fetch the friends. I tried this out with a APIFramework module that pulled off the abstraction i needed. E.g.:

$context->get('userrelations')->invoke('get_friends');

Goal:

  • ModuleB should not care whether there is a moduleA or how it provides a result.
  • ModuleB can call direct functions to whatever the context comes up with.
  • ModuleA has to register the fact that it has something to contribute to the context, much like I read about OG implementations.
  • Additionally ModuleA should implement the methods from an interface for the type of context it registers. (user relations : get friends, og : get group node, ...).
  • The requests of this kind i think are lazy loaded.

Could such a thing be done within this scope?

Here is what Java does

dries's picture

Here is what Java does relative to the request context: http://download.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRe... as well as HtppRequest.setAttribute() and HttpRequest.getAttribute.

See also http://java.sun.com/j2ee/tutorial/1_3-fcs/doc/Servlets.html and http://download.oracle.com/javaee/1.3/api/javax/servlet/ServletContext.html.

You probably will deal with

pounard's picture

You probably will deal with multiple contextes at the same time, I cross posted this issue in another post.

Primary HTTP context, such as carrying parameters and the dispatcher accessors should not be the same as the business contextes. You have only one HTTP request, but you can have an OG context, a node context, etc etc etc. I thinks these are two different concepts and not one, and they should be separated, in words as in code, it would be a less more confusing I think.

The menu items may tell the right dispatcher to use (more than that, the right router context implementation) while the business context can be happened later in the execution flow (even during the menu callback execution itself).

Here is where I crosse-posted: http://groups.drupal.org/node/130419

EDIT:

How exactly does the registration system for extended context work? If two modules try to register themselves at the same time, for the same piece of information, how do we resolve that?

The later or the first wins, I don't really see a choice here.

Re-EDIT (and Re-EDIT: typo):

Is the "context stack" a good middle-ground between "global pile'o'crap that's still surprisingly flexible" (current Drupal) and "pure OO dependency injection theory that is 10x more testable than what we have now"? Or is it too complicated?

Why not just attempt using pure OO and see what happens? Nothing complicated here as soon as you are sure of what you are trying to do.

Pierre.

Web Services and Context Core Initiative

Group organizers

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds: