[development] Actions in core
John VanDyk
jvandyk at iastate.edu
Tue May 1 22:08:01 UTC 2007
What are actions?
-----------------
Warning: please disassociate any thoughts you have about workflow and
actions. They are two separate modules. Workflow implements a state
machine to move a node through various states. That is its main
purpose. Actions only come into play because the workflow module
knows how to assign and fire actions. So, putting any thoughts of
workflow aside, let's proceed.
Actions are like stored procedures that can be loosely coupled with
events in Drupal.
Let's examine a ridiculously simple example. Suppose you want to be
draconian with your Drupal site's comment policy. Any user who leaves
a comment gets blocked immediately. You can implement this quite
easily with the comment hook:
foo_comment($a1, $op) {
if ($op == 'insert') {
global $user;
// block user here...
}
}
What an action does is wrap up this code into a nice package. That
package can then be associated with the nodeapi hook. Or some other
hook. Actions takes this function, which we'll call "Block current
user", and allows you to assign it to any hook-op combination.
We do that by several coding conventions. Here is the skeleton of a
simple action:
/**
* Implementation of a Drupal action.
*/
function action_block_user($op, $context = array(), &$object) {
switch ($op) {
case 'do':
global $user;
// block user here...
break;
case 'metadata':
return array(
'description' => t('Block current user'),
'type' => 'User',
'batchable' => TRUE,
'configurable' => FALSE,
'supported hooks' => array(
'nodeapi' => array(
'delete' => 1,
'insert' => 1,
'update' => 1,
'view' => 1
),
'comment' => array(
'insert' => 1,
'update' => 1,
'delete' => 1
)
)
);
}
}
We've implemented two ops. The 'metadata' op describes the action by
declaring some things, including what hook/op combinations it
supports. The 'do' op is actually where the code runs. We list the
'do' op first in the switch statement for performance, since Drupal
will then encounter it first during execution.
The above is a nonconfigurable action. There's nothing about it you
can change. It will block the current user and that's that. It's
basically a singleton action.
On the other hand, an action like "Send email" needs to know some
things in order to run. It needs a subject, and body, a recipient,
and such. Configurable actions are not available for use until they
have been configured (obviously). They can be configured from the
main actions interface at admin/build/actions. Check out the HEAD
version of actions and install it to play along. Note that I'm
currently developing against Drupal 5 until things are a bit more
stable.
What's new?
----------
All of this is not new. I've been saying the above for years now. So
here's what's new.
1. We now have a central interface to assign actions to hooks through
the browser. There's a new "assign actions" section at
admin/build/actions/assign. I demonstrated that at OSCMS for those of
you who were there.
So you can now assign the "Block current user" action to the nodeapi
view op, and so on. You can't assign it to the cron hook, because
that would be stupid.
2. The actions engine has been pulled out into a separate .inc file
which will live at includes/actions.inc. This is so that, like
menu.module, actions.module can be disabled if you're not using
actions, or are using actions only within your own modules.
What are the current problems?
------------------------------
We are still feeling around for the best way to unify information fed
into actions. The current approach is to pass an action-specific $op
(like 'do' or 'metadata', see above), a context array, and an object.
So when an action gets an object, it can look at the context and know
whether it's receiving a node object or a comment object or whatever.
See the code in default_actions.inc for examples of actions figuring
out what to do based on the context. I think this is a simple
approach that will work.
Another issue is, uh, factoring of Drupal code. Core has a lot of
things that have never been factored out. In our example above, you'd
expect to be able to call a function like user_block_user($uid) so
that the action's 'do' op just becomes a wrapper around existing
functionality. Instead, currently user blocking is done inside
user_save(). So you could say "well, just do user_load(), change the
status, and do user_save() and stop mumbling about factoring!" OK,
that leads me to the next issue.
Recursion. If you take that approach, and you do a user_load() and a
user_save(), lots of hooks are going to fire. If you're not careful,
you might have assigned actions to those hooks which call functions
which call other hooks. The complexity of the execution path can
become an issue. Currently the actions engine keeps a stack and if
more than 25 calls are made to actions_do() it bails out. For more
about recursion, see the beginning of this paragraph.
What is the future look like for actions?
----------------------------------------
I'd like to see actions for each module in core. If we refactor some
code in a smart way, adding actions will just be adding wrappers
around existing functionality. So statistics.module can have a "Clear
access log" action, taxonomy.module can have a "Add title to
vocabulary" action, etc. etc. The current default_actions.inc
contains actions that will be distributed to the various core modules.
The next major hurdle for actions consists of action sets and
conditionals. Action sets are groups of actions that go together.
Conditionals evaluate and determine whether an action set actually
executes. eaton has paved the way for both of these in his voting
actions module. Unless we're very busy bees, that will have to wait
for Drupal 7.
What can you do to help?
------------------------
Install Drupal 5. Download the HEAD version of actions module. Throw
the actions.inc file into your includes directory. Poke around. Write
some actions. Give some feedback.
Cheers,
John
More information about the development
mailing list