warning I realised, late enough not to correct it, that I'm using a particular case of wishful thinking - using any php callable in $callable(...) expressions. The reality is abit uglier. We need to use either
call_user_func($callable,...); call_user_func_array($callable,...) How I wish php could do that out of the box, especially when it actually does it, just not in the short syntax. Anyone happy to hold my hand to do a php patch for that feature? I'm very unfamiliar with the internals, sorry.
notice As Jared notes in his comment calling functions/methods with $ in a lot of cases you can use $obj->$meth(...) instead of call_user_func($callable,...);. Indeed you can use the following code as part of the remedy
list($object,$method) = some_call()
$object->$method(...)
unfortunately this discriminates against functions, which are lighter to use than their method brethren
and a mention The above two used to live on the top pattern page until I moved them here
<php
class example {
private $ifs = array( 'sin', 'cos' );
function x( &$acc ) {
$acc['t'] += 1;
$acc['x'] = $acc['x'] + $this->ifs[ rand(0,1) ]( 1/ $acc['t'] );
return $this;
}
}
$ex = new example();
$something = array('t'=>0, 'x' => 0 );
$ex->x($something) -> x($something) -> x($something);
print 't: ' . $something['t'] . ' x: ' . $something['x'] . "\n";
?>
and when you run it:
vlado@cow:~/php design patterns$ php php accumulator_passing.php
t: 3 x:1.34692254127
vlado@cow:~/php design patterns$ php accumulator_passing.php
t: 3 x: 1.64809122021
vlado@cow:~/php design patterns$ php accumulator_passing.php
t: 3 x: 2.66401049301
// fixed-point combinator
function Y(Lambda &$le) {
return lambda(get_defined_vars(), 'Lambda $f', '
return $f->call($f);
')->call(lambda(get_defined_vars(), 'Lambda $f', '
return $le->call(lambda(get_defined_vars(), \'$x\', \'
return $f->call($f)->call($x);
\'));
'));
}
function find_best($better, $coll) {
foreach($coll as $key=>$value) {
if( is_null($best) || $better($value,$best[1]) ) {
$best = array($key, $value);
}
}
return $best;
}//if gen and gen2 are generators
function merge_data() {
try {
do {
$data[] = 10*gen() + 5 + 4*gen2();
...
} while( some_condition( ... ) );
} catch ( $e ) { .... }
return $data;
}
$case($arg);
$object->$method($arg);
call_user_func($calleable, $arg1, $arg2);
function autoload( $classname) {
list($path,$file) = loadpath( $classname );
require_once "$path/$file";
}class rep_facade {
function __construct() {
$this->firewall = new firewall();
$this->reader = new reader();
$this->evaluator = new evaluator();
$this->printer = new printer();
}
function rep($text) {
$text = $this->firewall($text);
$ts = $this->reader($text);
$result = $this->evaluator($ts);
return $this->printer($result);
}
}
$product_type = which_product();
$probuct = new $product_type();
class fluent_class {
var $state_var;
function self_next( $arg ) {
....
return $this;
}
function other_next( $arg ) {
....
return $this->another;
}
}
$obj = new fluent_class();
$obj->self_next(1)
->self_next(2)
->other_next(3)
->something_else(4);
//a generator implemented with iterators
function generator($it=null, $cb=null) {
static $itor;
static $callback;
if(!isset($itor)) ($itor = $it; $callback=$cb; }
if($valid()) { $out = $callback($current); $next(); return $out; }
//or if you are not sure about the type of the above callables ($valid, $next,..)
if( call_user_func($valid) ) {
$out = call_user_func( $callback, $current);
call_user_func( $next );
return $out;
}
else throw( new some_exception );
}
class algorithm {
var $parameters=array();
function __construct( $select, $update, $eval) {
$this->parameters['select'] = $select;
$this->parameters['update'] = $update;
$this->parameters['eval'] = $eval;
}
function run($arg) {
$selections = $this->parameters['select']($arg);
$updates = $this->parameters['update']($selections);
return $this->parameters['eval']($updates);
}
}
//or with more syntax, not the parametrisation
function algorithm( $select, $update, $eval ) {
$args = func_get_args();
$args = array_slice($input, 3);
$selections = call_user_func_array( $select, $args);
$updates = call_user_func_array( $update, $selections);
return call_user_func_array( $eval, $updates);
}
class hooks {
private $cache = array();
function run( $func, $arg ) {
foreach(array('before','at','after') as $hook)
foreach($this->cache[$func][$hook] as $f) { $f($arg); }
}
function hook($hook, $func, $cb) {
$this->cache[$func][$hook][]=$cb
}
function before($func, $cb) {
$this->cache[$func]['before'][]=$cb
}
function at($func, $cb) {
$this->cache[$func]['at'][]=$cb
}
function after($func, $cb) {
$this->cache[$func]['after'][]=$cb
}
function around($hook, $func, $cb) {
$this->cache[$func]['around'][]=$cb
$this->cache[$func]['after'][]=$cb
}
}
//and usage
$hs = new hooks();
$hs->hook('before','label','before_a_func');
$hs->hook('at','label','core');
$hs->run();
//or using the individual methods
$hs = new combinator();
$hs->before('label','before_a_func');
$hs->around('label','tracer');
$hs->run();
class tag {
//ts is the current token stream
//this is a variant of partial evaluation
var $args;
function __construct( &$ts) {
while( not_class($arg = next($ts)) ) {
$this->args[] = $arg;
}
}
//evaluate the
function run( &$context) { ... }
}
//example usage
$ts = array("tag","php","design patterns");
$op = current($ts);
$prog = new $op();
$prog->run( new context() );
class alt_iterator {
private $st = array();
function next() { ... }
function current() { ... }
function valid() { ... }
//... put the rest here
function protocol() {
return array(
array($this,'next'),
array($this,'current'),
array($this,'valid'),
.....
);
}
}
//an example use
function do_them($itor, $callback ) {
list($next, $current, $valid, ...) = $itor->protocol();
while($valid) {
$callback( $current );
$next();
}
}
//for simplicity handle only functions with one argument
class promise {
private $args = null;
private $func = null;
function __construct($func, $args) {
$this->func = $func;
$this->args = $args;
}
function evaluate() {
call_user_func($this->func,$this->args);
}
}
function delay( $func, $arg ) {
return array(new promise($func, $arg), 'evaluate');
}
//example use
...
$chunks[] = delay('a_printer',$a_variable);
...
foreach($chunks as $chunk ) {
if(is_callable($chunk)) {
$chunk(); //force - this does it's own printing in this example
} else {
print $chunk;
}
}
function db_get_something( $x ) {
static $cache = array();
if(empty($cache[$x])) {
$cache[$x] = //some db query result;
}
return $cache[$x];
}class db_get_somethning {
private $cache = array();
function func() {
if(empty($this->cache[$x]))
$this->cache[$x] = //some db query result;
return $this->cache[$x];
}
}
class scope {
var $parent;
function __call( $name ) {
if(isset($parent[$name]) { return $parent[$name](); }
throw( new someException() );
}
function let( $name, $value ) {
if(is_object($value)) {
//hygiene
if(isset($this->$name)) { $this->$name->parent = null; }
$this->$name = $value;
$this->$name->parent = $this;
}
}
//alternative let
function let($parent, $name, $class) {
$parent->$name = new $class();
$parent->$name->parent = array($parent,$name);
}
?>
//initialisation
$hs = new hooks();
$observer = new interested_party();
$change_method = array($object, 'setter');
//setup change point(can be done externally from $change method)
$hs->at('change', $change_method);
//express interest
$hs->after('change', array($obesrver, 'callback'));
$hs->run($arg);
class p_eval {
private $args = null;
private $func = null;
function __construct($func,$args) {
$this->func = $func;
$this->args = $args
}
function evaluate($arg) {
call_user_func($this->func,array_push($this->args,$arg));
}
}
//usage
$f = array(new p_eval('a_function',array('one')), 'evaluate');
....
$f($arg);
class alt_iterator {
private $st = array();
function next() { ... }
function current() { ... }
function valid() { ... }
//... put the rest here
function protocol() {
return array(
array($this,'next'),
array($this,'current'),
array($this,'valid'),
.....
);
}
}
//an example use: iterator protocol
function do_them($itor, $callback ) {
list($next, $current, $valid, ...) = $itor->protocol();
while($valid) {
$callback( $current );
$next();
}
}
class Fluenter
{
private $obj;
function __construct($obj)
{
$this->obj = $obj;
}
static function MakeFluent($obj)
{
if ($obj instanceof fluent)
return $obj;
else
return new fluenter($obj);
}
function __call($method, $args)
{
$result = call_user_func_array(array($this->obj, $method), $args);
if (is_null($result))
return $this;
else if (is_object($result) and ($result instanceof $fluent))
return $result;
else
throw new RuntimeException(
"Fluent::__call called method $method ".
"and expected a null return or a non-fluent object, ".
"got (".gettype($return).") $return instead.");
}
}
function &get_instance() {
static $instance;
if(empty($instance)) {
$instance = new SingletonClass();
}
return $instance;
}class Singleton {
private static $instance = null;
private static function __construct() { }
public function get_instance() {
if(empty(self::$instance) { self::$instance = new Singleton(); }
return self::$instance;
}
}
$new_state="edit";
$new_protocol="edit";
//change the behaviour
list($view,$update,$get, $set) = $object->protocol($new_protocol);
$logger = "a_logger_function";
$logger($message);$strategy = "a_logger_class";
$log_object = new $strategy;
$log_object->log(message);