In one of my projects (crumbs) I am playing with a new (ctools has something similar afaik, but still somewhat different) mechanism for hooks, that is based on classes and plugin objects. Plugin objects give us some local state that lasts longer than one hook invocation, but not as long as a static function variable. And aside of that, they give us a namespace for method names.
Plugin objects are created either based on their class name (scan for $module . '_class_' . $pluginName), or by some factory function (in case of crumbs this is a static class method, but could also be a classical Drupal function hook). They can be dropped and created new, each time we want to flush their local state. Or we just give them a reset() method.
Now I wonder if we should do something similar for Drupal bootstrap and page building.
We could let each module define one or more plugin objects for each of the scope/lifetime context components, with the respective context component(s) as a constructor argument, and with methods for hooks that somewhat belong to this context component.
<?php
class mymodule_plugin_World {
function __construct($world) {..}
// hook_boot
function boot() {..}
function nodeapi($node, $user, $op) {
// $user wants to do $op on $node.
// $op can be "save", "update", etc. "view" would be done by some another plugin that knows about $theme.
...
}
}
?>And maybe the world plugin could be used as a factory for other plugins?
I know we said goodbye to "every module is a class", and this was very wise.
But this idea is somewhat different.
And at this moment it's pure brainstorming.
Comments
Modules can be objects, but
Modules can be objects, but they should probably better be used to register other components, such as specific node behaviors, specific views plugins, specific other-business-stuff-implementation, therefore all that woul remain in module themselves would be some declarative method that points to other OOP specific components. If you do this, you'd have to reconsider a lot of things, like maybe declare your components in .info files instead of using functions maybe.
Core hooks are being used to implement a lot of existing design patterns using one technical solution, so before starting writing them as OOP code, you should probably identify each one of those first.
Pierre.
Modules can be objects The
The idea here is rather to let a module provide one or more objects that will then act as some kind of a listener subscribed to some kind of event. Just like a normal hook implementation could be considered as being statically subscribed to a specific global event. The difference is that the plugin object can maintain local state information between calls, or injected on construction. And of course, the factory could choose between different classes to construct that object, depending on some dynamic information.
Yep. And this registration could happen in factory hooks.
I imagine this being not a good idea.
A mechanic that declares component objects (which then act as plugins) does need some factory logic. It does need to pass around parameters, and make some choices depending on the current page, the current user, and information loaded from the database. This is not easy to express in a .info file.
In classical Drupal, the factory hook implementation will typically get all these dependencies from global state. (And honestly, a lot of the PHP OOP frameworks I've seen do the same, they get the db connection from a singleton or global registry, etc.)
As I understand, the idea of the butler project is to make these dependencies available as objects that can be passed around.
So the factory function could receive a butler object (or several specific context objects) as parameter, and then uses them to build the requested plugin objects.
Some of the plugin objects can even act as factories themselves, that provide new plugin objects for other purposes.
Btw, in "crumbs" I have chosen to make the factory hook separate from the plugin class.
In any case there is some aspect of a listener. Unlike a "chain of responsibilites", module_invoke_all() will always call all hooks, not matter if one of them says it has been "successful". Just, the hooks do not have to be subscribed at runtime. Instead the subscription is static, as long as the module implementing the hook is enabled.
In some cases the hook implementation does return something ("info" hooks like hook_menu etc), or it will manipulate the parameters that were passed around ("alter" hooks). I guess this would still qualify as being a listener, but maybe you know a better term?
A lot of them just manipulate some global state - with all the typical side effect mess that we have to expect.
I wonder, in some cases a design pattern name is used to identify a technical solution, and in other cases to identify a more general concept.
When I hear "singleton", I always think of a class with a private constructor, and a static method to lazily provide the one object instance. This clearly is technical details, nothing abstract in there. When I hear "policy" I always think of an object injected into another object, that can be asked by that other object how to behave in situation xy.
If you want to identify one of the patterns (that are otherwise used to describe OOP techniques) in a Drupal hook, then you need a more abstract understanding of these. I would be curious if you can name some! (for whatever well-known hooks you can think of atm)
hook_menu() provides the
hook_menu() provides the router items registry, it determines the "view rendered" to use (known as delivery callback in D7), this looks like dependency injection / adapter pattern (core needs one and only implementation depending on the context, and take the pragmatically set one for the current one).
Somtimes, a hook is being called only on one module, this is totally the adapter pattern. All the hook_*_info() hooks provide data for a global registry, but they sometimes register specific implemenations, hook_entity_info() provides both an entity type registry and the specific controller implementation to use at the same time, while hook_element_info() provide specific elements implementation.
When a lot of modules implements some hook_page_build() or hook_preprocess_page() in order to alter the breadcrumb (simple and may be wrong example) they condlict with each other, in fact a chain of command|responsability may be appropriate in here.
These are two examples, most hooks are being used for data alteration, these are not the observer pattern (listiner pretty much the same thing) because they actually alter the observed data. In most case they do it for a reason, and this reason itself can be seen in a much global pattern (not the hook itself).
If you conceive the modules as OOP components, it's being totally logical using simple template method pattern (basically, method override and implementation, interfaces and abstract stuff) but in many cases, modules are being loaded for nothinig when running the framework, that's why I'd prefer to see them as a component registry more than a living object (a lot of framework describe their component in a non runtime language, such as xml or simple ini files), register the metadata somewhere and use it only when needed.
EDIT: Typically, the hook_menu() has no real reasons to live in code itself, it can be some files parsed at cache rebuild time, menu is not meant to be rebuilt often on a production site (a moving menu will kill your drupal, since it does thousands of inserts when rebuilding it).
Re-EDIT: But I'm arguing a lot here, in fact I'm quite OK with hooks() as they exists, they are a lot of others problems to solve in actual Drupal implementation before trying to remove them. I think that hooks are good when they are used to register components. What I don't like somehow are the good old hook hooks with 'op' parameter (which actually are removed from D7 and splitted into many functions) because they often are meant to behave like a global "some object" handling component. If a field type was an interface, and if the field api module had given an abstract and some defaults implementations there, it would be so much easy to write your own implementation by extending the actual existing components (at least the class hierarchy would provide a really good help for code reading and comprenhension).
I'm saying this because the entity controller oop abstraction is a good thing, but it remains unfinished, and because fields are implemented elsewhere, in a procedural fashion where the oop entity controller is pretty much linked to it (both has no reasons to live without each others) makes the code a bit messy (this my opinon), and even without hooks, I think this is a problem to solve (the entity controler could attach, detach, a do the crud itself instead of letting partial implementations live everywhere in the code, node, taxonomy, they all implement a part of the loading, and the full save, but rely on the incomplete controller implementation).
Various EDIT's : typo errors.
Pierre.
I think using pattern names
I think using pattern names does only make sense if given only that name you can make a good guess about the implementation. Or at least, to an alternative implementation that could replace the existing one. I very much doubt that when we try to use GoF pattern names for Drupal stuff.
The data might be stored in a registry or cache, but in some case it might just be used and thrown away. The hook implementations do not care what happens with the result. They do not actively push information, they just give information when asked for it.
As I understand, "registry" means a way to store information in a container that is globally or locally available for read and write, during its full lifetime. Which is not exactly what happens with info hook information.
The term might be more appropriate for the choice of theme function. You try different candidates, and then choose the first that exists.. but not sure about that one.
Crumbs plugins are definitely an example for "chain of responsibility", if we look at the findParent() and findTitle() methods. Once a parent or title is found, we can ignore further plugins.
For alter hooks, the idea is that every function can manipulate different aspects of the data that is passed around. In case of conflict, the last function called wins. This is a pattern for itself, and deserves a new name imo.
hook_menu() implementations (hook_router() would be more accurate) do a lot of dynamic stuff. Such as, looking at existing items from a database, etc. Not possible in an info file.
I think other frameworks can afford to make this static, because there it is assumed that the site builder will write site-specific router info files. In Drupal, on the other hand, modules make their router declarations without even knowing the specific site that will be built. And sometimes the router paths depend on backend configuration, stored in the database.
Btw, I imagine a class-oriented Web-MVC like in Zend or symfony would totally kill Drupal as it is. I am very happy that some things are allowed to remain in functions, and don't need to be in classes.
The nasty thing is that splitting them up creates even more chances for nameclash. We desperately need namespaces...
In some cases this could be replaced with plugin objects with different methods. Such as $node_plugin->node_validate(), $node_plugin->node_load(), etc. But then again, load and validate are quite separate concerns, and do not necessarily need to be dealt with by the same plugin object.
It would be interesting, but we also need to be careful. Class hierarchies tend to be quite unflexible if you want to alter them from a separate module. But it could work if done right.
Despite this discussion, I
Despite this discussion, I think we agree on a lot of things.
Hum this is true, they still behave like this. This what I meant when I said that they generally expose components that belong to a more global ((core|plugin susbsystem|pattern).
Exactly, drupal_static() is a registry (it's actually the best example in the whole Drupal core), it probably should be renamed as drupal_static_registry() more than drupal_static() which actually has no sens for any beginer with Drupal.
A registry may not be available for read and write for everyone, but you are right, hook_*_info() purpose is more to give a list of components or behaviors for a higher level layer than really build a registry. This is still a registry of components and behaviors for this higher level layer.
It probably already has one. I think that the "alter everything" in Drupal is dangerous, it's where you start loosing track of what is really your site. The more module you have altering things, the more you have random things happen. Personnally, I use the *_alter() only when I have no other solution, it's for me the last resort when no "clean" pattern allows me to do what I wanted to do.
the more alterations you do, the less predictible will be the runtime, and the less you will be able to put in place and use efficient caching policies, because of the (random|arbitrary) results you obtain.
Yes, that's actually true, it was a poor example I choose here.
<
blockquote>
I think other frameworks can afford to make this static, because there it is assumed that the site builder will write site-specific router info files. In Drupal, on the other hand, modules make their router declarations without even knowing the specific site that will be built. And sometimes the router paths depend on backend configuration, stored in the database.
<
blockquote>
True, but is this really something "good"?
<
blockquote>
Btw, I imagine a class-oriented Web-MVC like in Zend or symfony would totally kill Drupal as it is. I am very happy that some things are allowed to remain in functions, and don't need to be in classes.
<
blockquote>
Yes, I agree. But complex things has to be named using the right words. I'm using for example the menu router itself, it's a router, it also should use a dispatcher instead of doing all the work here, which would then instanciate the right view rendered (or may be call it formatter, or layout) depending on the context. The router shouldn't be called the "menu router" because it cause confusion for a lot of developers or beginners, I was working with a guy today that hardly understood the difference between the "visible (custom or not) menus" that we can put in blocks, and what the router is really is, until I took (some) time to explain it to him, it's not natural.
Also true. Namespaces are killing the procedural code as it grows. Actually the node and validate are indeed much different a not mandatory related, but as all those hooks seems to be node behavior alterations, they probably should be called "behaviors", whatever. Some of these ops are related, validate, save and form probably should always live together.
The hook_node_load() as probably no reason to live anymore since now, you can pretty much use fields for everything (even if I'm not really that fond of fields, at least it centralize the CRUD, which is really good because it can also centralize caching and provide easily a global and efficient caching policy).
Indeed, this is discutable, I choose this example because I hate the field module procedural code. Fields really has a huge code base, separated into multiple fields, using a lot of hooks, therefore it's code is quite ugly to trace (even sometime impossible). Here using OOP code would allow a better readability, and probably would avoid developers confusion and give more maintainability.
Furthermore, as soon as the code grows, using OOP code will force you to encapsulate each layer, and make it the more obscure you can for others layers, leaving you the only choice of well defining the entry points of you API, it's a good exercise that would make the full design clean and clear. When you read OOP code, the design speaks for itself long before you even started to read the implementations, and that is really a good thing.
Class hierarchies are inded unflexible, except if you design a loosely coupled design, based on interfaces and dependency injection (which are quite fashionable now). I really think that the more you complexify your design (and D7 exploded in complexity compared to D6) the more you have to solify and fix the design, else it will really be a maintain nightmare.
You have good points, really. My whole conclusion is probably that for most problems the core developers encounters, there already is a solution somewhere, and if they tend to generalize it's maybe because they are good. Using a common design pattern does not means that you have to implement it by the rules, it means that you have to give the right name so people understand what you actually are doing. The more the Drupal core code grows, the more you have to factorise, else you will reach a point where, as soon as some core developers will leave the project and start being busy with their family in the real life, you will start to be unable to maintain whatever they did. It's a fact and it can happen. Node for example is the right example of what should be the full entity API in fact (why don't taxonomy terms have a uid?, why users can't have bundles? why all the core entities do not really share their forms, why isn't there a single and common entity_view($entity, $view_mode) method for each one of them). All entities are actually exactly the same thing, they all should share CRUD and stuff like that (which actually is attempting to make fago with the Entity API, but he is doing it alone, therefore it's pretty much obvious he will make evident design errors).
drupal_render() uses a pseudo decorator pattern (weird implementation, but working), drupal_static() is a registry, the menu router is almost a full MVC implementation, using a router, a dispatcher, multiple view rendered, entity controllers are the begining (really unfinished) of a factory pattern, and fields, if it was more integrated to it, would probably look like a mapping/storage implementation (actually it's almost this).
If all this words were rightly used, you will have view renderers being naturally able to convert any mapping in they own formalism (example, a node becaming a JSON or XML export) only be using loosly coupling between the mapping (fields and entity are data, and can be mapped somehow) which could be read in a generic fashion by the view rendered itself (which would have nothing to do with entities, but which would be able to read mappings), and tadaaaa, you have a full data export mecanism in almost nothing in term of code weight. And this, coupled directly into the MVC pattern, and you would solve the need for any other exporting facility such as ctools (which I really don't like), a part of features (the one that exports taxonomy and such) and other stuff (like the yamm module I made).
EDIT: Graa.. formatting cause me some trouble.
Pierre.
Hooks as classes
I started brainstorming some code that could turn hooks into classes. I think this is a powerful concept. It could allow for things like each hook implementation specifying the order in which it is invoked with respect to other modules. Also, we no longer have to rely on naming conventions since hook implementation can be determined by reflection. With respect to context, a hook implementation could determine in what context it should be invoked at all. For example, a form alter hook may only need to alter one form. Rather than invoking the hook for every form, we only load the class on pages that include the form that needs to be altered. Hopefully the code is pretty self-explanatory:
<?php
class DrupalHook {
public $args;
function __construct() {
$this->args = func_get_args();
}
function invoke($module) {
}
function invoke_all() {
$results = array();
foreach ($this->get_implementations() as $hook_name) {
$hook = new $hook();
$hook->args = $this->args;
$results[] = $hook->execute();
}
}
/**
* Get a list of all classes that extend this hook.
*
* @return array
*/
function get_implementations() {
return array();
}
/**
* Specify a list of hooks that must run before this hook.
* This helps us get around having to set module weights to ensure hooks are
* invoked in the correct order.
*/
function invoke_after() {
}
/**
* Invoke the hook.
*/
function execute() {
}
/**
* This is called for any classes that want to alter another hook's data.
*/
function alter(&$data) {
}
}
class HookMenu extends DrupalHook {
/**
* Methods specific to HookMenu could go here. Otherwise, this just serves
* as a way to define a hook that modules may implement.
*/
}
class NodeMenu extends HookMenu {
function execute() {
$items = array();
$items['admin/content/node/overview'] = array(
'title' => 'List',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
return $items;
}
}
class MyCustomAlterHook extends NodeMenu {
public $alter = TRUE; // Specify that we want to alter NodeMenu's data.
function alter(&$items) {
// Alter data like we now do in hook_menu_alter().
$items['admin/content/node/overview']['title'] = 'My Custom Title';
}
}
class DrupalModule {
abstract function get_hooks() {
}
}
class NodeModule extends DrupalModule {
/**
* Register hooks.
* This may not even be necessary since we can probably figure this
* out by loading all classes within the module directory, but this
* gives us a place to register other things related to the module.
*/
function get_hooks() {
return array(
'HookMenu' => array('NodeMenu'),
);
}
}
?>
Honnestly, considering how
Honnestly, considering how much I love OOP code, this does not sounds good. Using classes only for using classes is no benefit at all. If you switch to OOP, you have to abandon hooks, at least abandon the form in which they exist. If you use such code you will only rewrite the hook using classes without any benefit from the OOP programming (I cannot see any existing design pattern here). If you want to replace the hooks using OOP code, use the real design pattern they originally replaced (chain of command, observer/observable, dependency injection, adapter and such). Rewriting the good old template method pattern (which hooks actually are) using classes is something dump, template method is just an alias for abstract methods.
Pierre.
Hook Improvements
Yes, OOP for the sake of OOP is not worth it. I wasn't expecting any of my code to be used. It was just a brainstorm on some features to make hooks "smarter". Leaving hooks in their current form (functions) rather than converting them to classes would be a good thing. Perhaps having some classes that allow us to contain additional data about hooks as well as allow us to register hooks would be beneficial. This pattern does exist in other frameworks. Compare with Django signals: http://docs.djangoproject.com/en/1.2/topics/signals/.
Some limitations of the current hook system are:
This is quite expensive. For example, every hook_form_alter is invoked for every form regardless of anything else. Giving modules the ability to attach or detach their hooks based on context would be excellent. There are plenty of modules out there that don't need to be loaded on every page. A way for a module to say, "Only bootstrap me on taxonomy administration pages" would be excellent. Another example may be "If the type of node being used in the current context is 'poll', load these 3 hooks and nothing else." Separating this kind of logic from the hook implementation is particularly useful because contextual information can be cached.
Drupal basically has three types of hooks:
Abstracting these different types of hooks may also allow us to use them more efficiently. For example, "Hooks that provide information" can typically be cached, so the caching mechanisms can be built-in to core and the module maintainer doesn't have to worry about implementing it. There is no reason for every hook_menu function to be bootstrapped on every page load. Hooks like these can be stored in their own file that is only loaded when the cache needs to be rebuilt.
Perhaps having some classes
I'd rather leave the "generic" discussion about hooks, because hooks do some many different stuff within so many different use case. I'd prefer a less dynamic framework where entry points are registered components or may be even some descriptive files. What I mean is that most hooks have almost different use cases, so I'd prefer to discuss those use case one by one.
I totally agree with this! Form alteration is ugly and unperformant. I think that the global hook_form_alter() should not even exists (but it still resolve some edge cases, edge cases should also disappear). But the hook_form_FORM_ID_alter() is very efficient, I almost never use the global hook_form_alter().
I think it has a lot more than this. As soon as you start working with modules (even the Field API itself), some hooks basically provides an OOP structure via a procedural implementation (Field API still uses hooks with 'op' parameter, except it's being called 'type' instead...). It has almost the same structure as if it was pure OOP code, but it's not OOP code, it's hooks.
hook_block() is also a good example. A block should have the view() configure() and save() method, using a base implementation or a good interface, it would probably more easy to read and write (hooks are splitted therefore it makes it quite obfuscated when functions are mixed into a .module file among a lot of others).
Even if I think there is so much more than only three basic hook types, I couldn't agree more with this statement. This is why some OOP code here could be really good, provide basics through the design, and let the developers worry only about final and specialized implementation.
Pierre.