Drupal Pipes

chx - February 7, 2008 - 03:29
Project:Drupal
Version:8.x-dev
Component:base system
Category:feature request
Priority:normal
Assigned:Unassigned
Status:needs work
Description

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').

<?php
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);
    }
  }
}
?>

#1

chx - February 7, 2008 - 03:31

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.

#2

cwgordon7 - February 7, 2008 - 05:35

<?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).

#3

Stefan Nagtegaal - February 7, 2008 - 10:00

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?

#4

chx - February 8, 2008 - 15:29

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.

#5

moshe weitzman - February 8, 2008 - 16:27

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()

#6

cwgordon7 - February 12, 2008 - 03:58
Assigned to:chx» cwgordon7
Status:active» needs work

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.

AttachmentSizeStatusTest resultOperations
pipes_01.patch4.63 KBIgnoredNoneNone
pipes.inc_.txt9.95 KBIgnoredNoneNone

#7

starbow - February 15, 2008 - 00:00

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

#8

kbahey - February 15, 2008 - 02:56

Subscribe.

#9

Crell - February 15, 2008 - 06:22

Subscribing.

#10

Wim Leers - February 15, 2008 - 10:12

Subscribing.

#11

catch - February 15, 2008 - 10:55

me too.

#12

Arancaytar - February 15, 2008 - 11:08

Subscribing too.

#13

noahb - February 26, 2008 - 10:13

track

#14

somes - February 26, 2008 - 20:22

yep subing

#15

profix898 - February 27, 2008 - 13:06

Subscribing.

#16

sign - February 28, 2008 - 20:28

Subscribing.

#17

BioALIEN - March 1, 2008 - 05:36

Subscribe.

#18

cpelham - March 2, 2008 - 05:16

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.

#19

Amitaibu - March 3, 2008 - 15:35

Subscribing.

#20

birdmanx35 - March 7, 2008 - 21:14

cwgordon7... any progress?

#21

cwgordon7 - March 7, 2008 - 22:25
Assigned to:cwgordon7» Anonymous
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.

#22

catch - March 7, 2008 - 22:46

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

#23

ezra-g - March 9, 2008 - 15:00

Subscribing.

#24

birdmanx35 - March 9, 2008 - 19:39
Status:active» needs work

There is, in fact, a patch.

#25

cwgordon7 - March 9, 2008 - 20:11
Status:needs work» active

Uh, no, there's not.

#26

Gábor Hojtsy - May 6, 2008 - 13:15

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.

#27

macgirvin - May 6, 2008 - 13:40

Two words. One dimension.

#28

robertDouglass - May 10, 2008 - 18:53

Subscribe.

#29

dropcube - June 5, 2008 - 22:17

Interesting... subscribing.

#30

jscheel - June 26, 2008 - 18:53

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.

#31

drewish - August 30, 2008 - 08:47

subscribing

#32

bdragon - August 30, 2008 - 09:07

subscribe

#33

Summit - November 13, 2008 - 10:23

subscribing

#34

andypost - December 26, 2008 - 17:24

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

#35

chx - December 30, 2008 - 07:19

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/% .

#36

sdboyer - December 30, 2008 - 18:26

subscribin.

#37

chx - January 6, 2009 - 21:12

#38

chx - January 7, 2009 - 19:56

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.

#39

skilip - January 8, 2009 - 10:24

subscribing

#40

R.Muilwijk - January 8, 2009 - 10:31

subscribin

#41

jpetso - January 10, 2009 - 21:23

subsub

#42

chx - February 16, 2009 - 05:20
Status:active» needs review

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.

AttachmentSizeStatusTest resultOperations
pipes.txt2.76 KBIgnoredNoneNone

#43

justinrandell - March 6, 2009 - 17:09

subscribe

#44

Xano - March 6, 2009 - 17:17

Subscribing.

#45

mfer - March 6, 2009 - 18:46

subscribe

#46

Arto - March 6, 2009 - 21:14

Good stuff. Subscribing.

#47

justinrandell - March 7, 2009 - 19:51

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:

<?php
/**
* $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.

AttachmentSizeStatusTest resultOperations
menu_multiple_callbacks.patch2.68 KBIdleFailed: Failed to run tests.View details | Re-test

#48

System Message - March 7, 2009 - 20:10
Status:needs review» needs work

The last submitted patch failed testing.

#49

EvanDonovan - July 9, 2009 - 05:09

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.

#50

catch - September 11, 2009 - 11:27
Version:7.x-dev» 8.x-dev

Moving to D8.

#51

arhak - September 30, 2009 - 23:56

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)

 
 

Drupal is a registered trademark of Dries Buytaert.