The basic idea is to execute a chain of callbacks with the output of one as the input of the next. For example, node_load gets 123 as an argument (from the URL), returns a $node. Pass this to node_access which either returns the $node or FALSE (needs patching but if (node_access($node)) wont change). Pass this to node_show. Pass the return value of node_show to theme('page').


callbacks => array(
  'menu_load' => array('callback' => 'node_load', 'arguments' => array(1), FALSE => MENU_NOT_FOUND),
  'menu_access' => array('callback' => 'node_access', FALSE => MENU_ACCESS_DENIED),
  'menu_page' => array('callback' => 'node_show'),
  'menu_render' => array('callback' => 'theme', 'arguments' => 'page'),
);

function drupal_run_callbacks($callbacks, $last = NULL) {
  $callback = reset($callbacks);
  while ($callback) {
    $function = $callback['callback'];
    if (isset($callback['arguments'])) {
      $arguments = array_merge(menu_unserialize($callback['arguments']), array($input));
      $input = call_user_function_array($function, $arguments);
    }
    else {
      $input = $function($input);
    }
    unset($transition);
    // TRUE or FALSE.
    $transition_key = (bool)$output;
    if (isset($callback[$transition_key])) {
      $transition = $callback[$transition_key];
      if (isset($callbacks[$transition])) {
        reset($callbacks)
        while ((list($key, $callback) = each($callbacks)) && $key != $transition);
      }
      else {
        return $transition;
      }
    }
    else {
      $callback = next($callbacks);
    }
  }
}
CommentFileSizeAuthor
#47 menu_multiple_callbacks.patch2.68 KBAnonymous (not verified)
#42 pipes.txt2.76 KBchx
#6 pipes_01.patch4.63 KBcwgordon7
#6 pipes.inc_.txt9.95 KBcwgordon7

Comments

chx’s picture

Note that the function would need a return $input to make it really useful, also the keys of callbacks array are totally arbitrary and not even used in the above.

cwgordon7’s picture

<?php
function hook_callbacks() {
  return array(
    'node_load' => array('input' => array('integer'), 'output' => 'node', FALSE => MENU_NOT_FOUND),
    'node_access' => array('input' => array('string', 'node'), 'output' => 'boolean', FALSE => MENU_ACCESS_DENIED),
    'node_show' => array('input' => array('node'), 'output' => 'html'),
    'theme' => array('input' => array('string', 'args'), 'output' => 'html', 'defaults' => array('string' => 'page')),
  );
}

function drupal_run_callbacks($callbacks, $outputs = array()) {
  $callback = reset($callbacks);
  while ($callback) {
    $function = key($callbacks);
    if (isset($callback['input'])) {
      $args = array();
      foreach ($callback['input'] as $argument) {
        if (isset($callback['args'][$argument])) {
         $args[$argument] = menu_unserialize($callback['args'][$argument]);
        }
        elseif (isset($callback['defaults'][$argument])) {
          $args[$argument] = menu_unserialize($callback['defaults'][$argument]);
        }
        else {
          return FALSE;
        }
      }
      $outputs[$callback['output']][$callback['destination']] = call_user_function_array($function, $args);
    }
    else {
       $outputs[$callback['output']][$callback['destination']] = $function();
    }
    // TRUE or FALSE.
    $transition_key = (bool)$output;
    if (isset($callback[$transition_key])) {
      return $callback[$transition_key];
    }
    else {
      $callback = next($callbacks);
      foreach ($callback['input'] as $argument) {
        if (isset($outputs[$argument][key($callback)])) {
          $callback['args'][$argument] = $outputs[$argument][key($callback)];
        }
        else {
          return FALSE;
        }
      }
    }
  }
}
?>

Another very rough draft. This one assumes some nonexistent code that lets the administrator define which callback gives its output to which input(s).

Stefan Nagtegaal’s picture

chx, I'm sure you overthought this very well, and there are plenty of use cases for this.. Unfortunatly, I can't come up with one.. Can you share some use cases?

chx’s picture

Want to get back the results in some other format than the full themed page? Just alter the last callback. Want to restrict access more? Piece of cake. We can do tricks like execute a query and then just call node_feed with said result as the next callback. If you want, say, an Atom feed, change that step. Reusability and hackability at its best.

moshe weitzman’s picture

As a conceptual model, this is terrific. We'll work some more on how this actually gets patched into drupal.

callback is a bit of a loaded word in drupal already. perhaps we create a new word. i pondered a bit and i think 'steps' works for me. the array element that refers to a function can still be called callback. just rename to hook_steps and drupal_run_steps()

cwgordon7’s picture

Assigned: chx » cwgordon7
Status: Active » Needs work
StatusFileSize
new9.95 KB
new4.63 KB

Here's an initial patch, just for an update. The framework is pretty much there, may need some tweaking. What's needed next is implementations of the hooks, and then we can cut maybe 10% of all code from core modules ;). Any feedback would be appreciated. Note: this has no UI yet, but it will (and hopefully an excellent one, too).

Sorry that the .inc file is attached as a txt, I was having patching issues. It just needs to be placed in the includes directory.

starbow’s picture

Subscribing - I am keeping an eye on this in case it can provide a clean foundation for #218830: Popups in Drupal 7: Plugable renderers for generating content

kbahey’s picture

Subscribe.

Crell’s picture

Subscribing.

wim leers’s picture

Subscribing.

catch’s picture

me too.

cburschka’s picture

Subscribing too.

bcn’s picture

track

somes’s picture

yep subing

profix898’s picture

Subscribing.

sign’s picture

Subscribing.

BioALIEN’s picture

Subscribe.

cpelham’s picture

Could this be used to create views or rss feeds with much more complex logic than is now possible, for instance to output 10 nodes, teasers, list, whatever, containing 3 nodes from one source, 2 from another, and 5 from a third, but all sorted together according to whatever sort criteria one specifies?

Meaning it would take the output of three views as input for a fourth view.

amitaibu’s picture

Subscribing.

birdmanx35’s picture

cwgordon7... any progress?

cwgordon7’s picture

Assigned: cwgordon7 » Unassigned
Status: Needs work » Active

chx and I discussed this and decided that we have fundamentally different ideas. He will continue to work on this thread, and I will take mine into contrib.

catch’s picture

Minor code style comment - I see a couple of if ifs there where && would be clearer.

ezra-g’s picture

Subscribing.

birdmanx35’s picture

Status: Active » Needs work

There is, in fact, a patch.

cwgordon7’s picture

Status: Needs work » Active

Uh, no, there's not.

gábor hojtsy’s picture

Interesting concept, but I'd wonder how would this perform. The themes are already pluggable, so renderer changes are possible without this "If you want, say, an Atom feed, change that step" is not technically new.

macgirvin’s picture

Two words. One dimension.

robertdouglass’s picture

Subscribe.

dropcube’s picture

Interesting... subscribing.

jscheel’s picture

So this basically becomes a filter chain, right? That way you can branch off at any given link in the chain? It would be interesting to be able to branch off, then reconnect further down the chain, effectively by-passing only the one section of the chain that you want to bypass. Wow, I said chain a lot.

drewish’s picture

subscribing

bdragon’s picture

subscribe

summit’s picture

subscribing

andypost’s picture

Looks like workflow-rules and in conjunction with triggers can be very usefull

chx’s picture

Recording progress. http://www.drupalbin.com/4621 is some new code. Writeup (which wont likely to make sense without checking that pastebin):

  1. where we take the input from (done)
  2. where we store results (done)
  3. how we determine what's the next step (not done, the code is not relevant/working)
  1. the arguments is an array and if one of the arguments happens to be also an array then that array is a list of indexes into a $context which initially holds 'user' and 'arg' keys. So arguments => array('view', array('arg', 1)) will pass 'view' and arg(1) as arguments. another example is array(array(user, uid)) which will pass the $user->uid to the callback function.
  2. store that's a tricky one! you specify indexes to the same context variable. so you can store a node as array('node') and then you can change one field of that with setting store to array('node', 'field', 0, 'value')
  3. transition is an array, where the keys are the step names, the values are either 'default' or arrays, the array can be another of these callback/arguments thing or i am thinking of some simple 'op' => '=', 'arguments' => array(FALSE, 'result') arrays which would compare the result to FALSE

Now, the menu definitions would not change that much because most of the magic (esp the transitions) will be added as defaults. I might move back the path into a key as I want more to make possible to have several steps array defined on node/% .

sdboyer’s picture

subscribin.

chx’s picture

chx’s picture

We just discussed this with pwolanin and I was afraid of unserializing a huge array for every single link. So we now think that if the steps are only the known defaults which defines a menu entry with pretty much the same features as like now, then the first two steps, for example we can call them menu_load and menu_access, these will be stored like they are now so that the common case will be fast.

skilip’s picture

subscribing

R.Muilwijk’s picture

subscribin

jpetso’s picture

subsub

chx’s picture

Status: Active » Needs review
StatusFileSize
new2.76 KB

Another step :p hopefully in the right direction. Still not integrated into Drupal but I implemented the transition part and set to CNR because I would like indeed the code to be reviewed and agreed upon before I continue. Writeup in #35. transition, for now, is just a simple compare.

Anonymous’s picture

subscribe

xano’s picture

Subscribing.

mfer’s picture

subscribe

Arto’s picture

Good stuff. Subscribing.

Anonymous’s picture

StatusFileSize
new2.68 KB

discussed this with chx at the DCDC code sprint. maybe i'm just missing stuff, so i'm going to post so i can be corrected.

i like the idea of chainable menu callbacks, but i don't know if building a general purpose pipe system is the way to go. do we really want to build a system that can just arbitrarily chain together functions? functions in php don't just read from STDIN and return a string to STDOUT :-(

how do we decide which step to take next if we are building up the chain ahead of time? aren't we going to want to do that at runtime?

how about a simpler approach that just allows for multiple callbacks?

here's a way to do that that is simpler, that chx doesn't like, but i'll let him explain why.

- it requires a set api for callback functions:

/**
 * $page - you add bits to this, and its to be passed to drupal_render_page($page)
 * $context - info about the request, and anything else liked loaded nodes etc via. the menu system 
 * can load stuff in here magically like it does now so modules don't need to. we can also make a 
 * something like hook_load_context so modules can get in on this.
 */
function foo_page_callback(&$page, $context) {
}

- it makes these functions not reusable as general purpose functions. in my opinion, this is not a problem, or at least, the solution to this (a system that can arbitrarily chain together functions with any signature and return values to any other function) is worse

- drupal_get_form, and anything else that is a callback and also something else, wouldn't work. i don't think this is a bad thing, but chx disagrees, not the first or last time i'm sure :-)

we can take the same approach to adding multiple access callbacks, much smaller scope patch, less powerful.

anyway, my US$0.02.

attached is a patch that shows a possible way to do the multiple page callbacks stuff. not complete, but i hope it makes some of what i'm saying clearer, even if to make it easier to explain why this approach wont fly. don't want to hijack this issue, so i'm happy to take this elsewhere.

Status: Needs review » Needs work

The last submitted patch failed testing.

EvanDonovan’s picture

Would this concept of multiple page callbacks (I'm thinking especially of the patch in #47) do anything that could help fix my issue #501372: RDF module conflicts with overridden taxonomy_term_page() implementations?

Essentially my problem is that various contrib modules (Panels 3 via the Delegator module, RDF, Taxonomy Translation (i18ntaxonomy), and Taxonomy Manager) want to do various things to alter taxonomy_term_page(), but ultimately want to return a display of nodes which have a particular taxonomy term.

I would love to be able to chain these implementations together, essentially altering the taxonomy_term_page() but not overriding it 100%. But I don't know enough of what these does to know if it's actually related, or if that's just a pipe dream.

catch’s picture

Version: 7.x-dev » 8.x-dev

Moving to D8.

arhak’s picture

note that Drupal have abrupt exit calls which might attempt against this feature
for instead, one callback ending in a drupal_goto (or cron) would halt remaining callbacks

I think Drupal should avoid using exit calls and let every function end naturally #592664: function calls should end naturally (avoid exit calls)

mustanggb’s picture

Subscribe

valthebald’s picture

Version: 8.x-dev » 9.x-dev

Bumping to 9.x

chx’s picture

Status: Needs work » Closed (won't fix)

nah, the whole idea is obsolete with routes.

Version: 9.x-dev » 9.0.x-dev

The 9.0.x branch will open for development soon, and the placeholder 9.x branch should no longer be used. Only issues that require a new major version should be filed against 9.0.x (for example, removing deprecated code or updating dependency major versions). New developments and disruptive changes that are allowed in a minor version should be filed against 8.9.x, and significant new features will be moved to 9.1.x at committer discretion. For more information see the Allowed changes during the Drupal 8 and 9 release cycles and the Drupal 9.0.0 release plan.