This is a 3rd proposed solution for handlers/call routing for plugins in core. It goes along with #362747: Create a call routing system (fix broken dynamic includes) and #363787: Plugins: Swappable subsystems for core.
Here is an overview of what's going on...
Concept
This solution is similar to #363787: Plugins: Swappable subsystems for core and uses the same naming conventions. The idea is to return objects for pluggable systems such as caching, sessions, and others.
There are 2 hooks modules can implement which are hook_handler_info and hook_slot_info. hook_slot_info defines the pluggable systems such as cache. hook_handler_info defines the possible pluggable systems to use in a slot.
The 3 functions used in this system are handler_set, handler_get, and handler. handler_set sets the configruation of the handler to use for a slot. handler_get retrieves the handler configuration information for a slot. The handler function returns a handler object for the pluggable system. For example:
$cache = handler('cache');
$data = $cache->get($id, 'cache');
$data = handler('cache')->get($id, 'cache');
Configuration Storing
Currently the configuration for handlers is stored in the variables array so it can be defined in settings.php via the $conf array. handler_get and handler_set manage the data stored in the handlers variable.
This means that the cache and sessions systems can use this before the database is available.
I'm also considering moving the configuration from $conf to $handlers in the settings.php file, storing the configuration in a table called handlers, and caching/loading that data in a manner similar to the variable system does with variable_init. This would cut down the clutter in the variables table and still cache the configuration. Moving to this would add one cache call to most page loads.
Multi-routing
Crell wanted to do multi-routing so a call to a system could be routed to multiple plugins. With some modification that can be done here without a much extra code.
Objects vs. Functional
I implement this as a class/object based system because many of the pluggable patches currently existing against head are class based. While chx patch #362747: Create a call routing system (fix broken dynamic includes) can be used to return objects it's not intuitive. factory functions that return objects for uses like this is a common practice I've seen. I think, though I could be wrong, it would be easier from a DX point of view for developers.
This first patch is rough and a concept patch. It's quite large because the cache system has been updated to use this patch which moved it from a set of functions to a class.
| Comment | File | Size | Author |
|---|---|---|---|
| #19 | simple-handlers_19.patch | 31.24 KB | mfer |
| #12 | handlers_simplified.patch | 27.56 KB | chx |
| #9 | variable_function.patch | 26.96 KB | chx |
| #8 | handlers_simplified.patch | 26.74 KB | chx |
| #6 | handlers_simplified.patch | 26.69 KB | chx |
Comments
Comment #1
chx commentedThere is a lot to like here. However, the single line functions (like cache_get) need to distill target information from parameters, which for cache is going to be $table, but for fields it'll be $field->type. This approach will let the casual Drupal developer need not to bother with OOP but those who actually provide the handlers will get an interface (yes we need interfaces) and that's good. The system is simple enough. I will work on this (a lot) today.
Comment #2
mfer commentedAn example of hook_handler_info and hook_slot_info:
Comment #3
chx commentedThis is indeed a simple system, it's like 30 lines. A little in bootstrap.inc for
handlerwhich determines the necessary class and instantiates it, loading a file as necessary, a little in common.inc for the builder and a little in system.module defining the 'cache' slot. 'cache' conversion is for demonstration. The rest can be in followup patches.Comment #5
chx commentedFixed default.settings.php which has the nice effect of making the thing working :) also fixed a bit in the API docs.
Comment #6
chx commentedThis version is much closer to mfer's and has a set/get too! hook_slot_info now only provides info. I have called the other hook_handler though. If we want , we could store only the class not an array('class' => $class) for further memory savings.
Comment #7
mfer commentedOne huge thing is missing for this patch... tests. If no one gets to them before tomorrow I'll spend some time on that.
The comments to the interface drupalCacheInterface need to be updated. They look like straight copy/paste from cache_get/set/clear_all and refer to those function names.
In the return array from handler_get should it be 'defaults' or 'default'? The plural on defaults might be confusing.
We have crud functions for handler_set/get, what about handler_del to delete a configured handler. For example, handler_set is used to set apc for cache_filter. Now you want to go back to the default configuration. Shouldn't you be able to do a handler_del for a slot/target? Or, is there already a good way to do this I'm just not seeing?
Comment #8
chx commentedAddressed mfer's concerns.
Comment #9
chx commentedPosted the wrong patch, handler_del was left out.
Comment #10
mfer commentedThis looks good besides the missing tests. I'll work on those tomorrow.
Comment #11
neclimdulI didn't realize this got split into another issue. So, this is kinda an updated post from the other issue because at this point this is patch is getting closer to #363787: Plugins: Swappable subsystems for core. I still have some serious concerns about storing the $handler information in the variable system. We could potentially be storing a good bit of information in this array. Handlers seem to be the sort of thing that could be used in all sorts of contrib modules. Imagine if Views, CCK and ubercart or ecommerce end up using them and you have a site using all of those handlers... on top of the core handlers... that could be a fairly large variable.
Also, I think we are also losing out by not passing the target or any other contextual information to the handler constructor. For example, if the target for the is the cache table then why would you also pass it to the functions?
vs
Comment #12
chx commentedI somewhat doubt that this array will become large. These handlers are used when there are changeable implementations of one given functionality. That should be quite rare outside of core and even core has only a few (cache, session, path, mail, http, password and come Tuesday, field storage). And most of these core ones (at least cache, session, field storage and path if aliases are used) are needed on every page request anyways.
And we pass around $table because there is no point in having a separate object for every target. That said, we currently do store a separate object for every target, so here is a patch which does not do that, which, I guess, saves memory in the default case compared to the previous patch which held a separate cache object for every target. It's still mandatory to pass in $table to every caching method because in the default case you wont get a target and in such a case one object needs to be able to handle everything. Note that for example field storage will pass around $field but the $target will be $field->type so it's just coincidence that for cache an argument and target is the same. I can imagine though some handler modules (esp for field storage) which are able to handle only specific targets and those can throw an exception in their constructor now because the constructor gets the target for specific targets and nothing for the default case.
Comment #13
neclimdulTrying not to push this patch into killing a horde of kittens here but really, in the context of handlers, the goal of the clearing method should just be to clear. If you want to "clear all" (which only really clears 2 caches...) then IMO it should be a function that calls 2 handler calls. Its a special case, and there shouldn't be anything keeping the system from storing those 2 caches in different handlers. It seems like the goal of the handler methods should encompass very specific functionality only, not special cases.
I'm also not convinced that passing contextual information to the constructor isn't the right way and keeping individual objects isn't the right way, or even a way that will be required for some implementations. I have to concede that it could eat a lot of memory though.
Comment #14
damien tournoud commentedI see no real difference with Crell's approach.
What I said in the other issue still stand:
I like this as a pattern, but I believe we don't actually need the common implementation. Let's agree on the general "handlers pattern" (ie. a strategy pattern based on interfaces, with a plain factory function specific to each subsystem), and convert each subsystem one by one, based on their specific needs.
Comment #15
neclimdul@Damien I agree. I believe that is why Crell's patch doesn't actually include an implementation.
No really related to this issue but I agree with Crell that you're mistaken about the cid being part of the target. I think we can agree to discuss that on a different issue though.
Comment #16
neclimdulI should clarify, I agree we shouldn't be arguing final implementation.
However there are several things that are quite different in these two patches. Most drastic is the storage of information and how the handlers interact with the system. And partially because of the later point and because of some design decisions , the basic implementation is somewhat important.
Comment #17
chx commentedWe need hook_handler() because we want to make it possible to write an UI to pick between handlers in a followup patch. We are fine with handler() because the wrapper functions will contain the logic to find out a useful target for the given slot. Right now the logic is target == table but for fields we will use
handler('field_storage', $field->type), for drupal_http_request we will usehandler('http', $base_url)and so on. We are not exposing handler() itself to end users. Maybe we should rename to _handler?Further cleaning of caching is a followup issue.
Comment #18
mfer commentedTests seem to find problems and writing the tests found a problem using the variable system. If you look at variable_init you can see that after the variables are loaded from either cache or the variables table it does:
This overwrites the variable stored in the database with the key => value from the $conf array in the settings.php file. So, by configuring the cache in here (or anything else) which is required for it to work we overwrite the settings from the database.
Working on a solution...
Comment #19
mfer commentedHere's an updated patch with tests and a number of fixes/changes.
Configuring the handlers via $conf['handlers'] for handlers available when the database is offline has an issue outlined in #18. To fix this I added a new variable called $handlers to the default.settings.php file to hold the handler settings. It holds the same information $conf['handlers'] used to. I added a function called handler_init which runs after variable_init and pulls $conf['handlers'] into $handlers.
There are now tests for handlers, multiple handlers, and the CRUD functions to configure the handlers.
There are a number of other changes such as the CRUD functions and handler() no longer locally caching the information in $handlers instead just grabbing it from the global scope. This fixed an issue with stale data.
Still missing is some documentation for the default.settings.php file to explain handlers.
Comment #20
mfer commentedTo respond to some of the comments about the approach:
Comment #22
mfer commentedSee #363787: Plugins: Swappable subsystems for core. Closing this one up. Crells difference in approach solves some of the problems we have here more cleanly and is now similar to this.