1

Wordpress has a nice api system. I'm struggling in creating an equal flexible one using a more traditional MVC interpretation. A typical view class could be like this:

class View
{
    public function set($name, $value)
    {
         $this->data[$name] = $value
    }

    public function __get($name) ... you know how it works
}

A use case would be a controller adding an order contain order lines:

$view->add('order', $orderModel);

Then the template file could have something like

foreach ($this->order->getOrderLines() as $orderLine)
{
    ?><div><?php echo $orderLine->getItemName(); ?><div><?php
}

At least this is what is commonly seen in many PHP MVC frameworks. I'm open to alternative implementations that solve the question:

How to add an event system like wordpress has? Eg a filter OrderLineItemName.

koen
  • 13,349
  • 10
  • 46
  • 51
  • 1
    What are you trying to filter in your view? WordPress filters are called during (or just prior to) the rendering. – funwhilelost Jan 20 '10 at 23:40
  • @infamouse: Anything can be a candidate. – koen Jan 21 '10 at 05:39
  • That doesn't really help mate. You'd need to give a more concrete example of what you want to do. Nevertheless, I'll try to answer your question. – J. Martin Jan 21 '10 at 14:17
  • The example would be that getItemName() does what wp_title() does: create a filter event. A plugin can subscribe to the event and has the change to filter the title string. – koen Jan 21 '10 at 14:37
  • My answer is more or less what they do. Here is the relevant line from the wp codebase: $title = apply_filters('single_cat_title', $cat->name); which then does this: $value = call_user_func_array($the_['function'], array_slice($args, 1, (int) $the_['accepted_args'])); And so on and so on. You need to jump around a bit in the code to follow it. – J. Martin Jan 21 '10 at 15:28
  • If you want to implement your own event system, you could, It seems alot of overhead for what you want to accomplish in the view, but in the end, you may have a kick butt plugin system. To do it, you'd have to create a master EventDispatcher class which is aware of all listener classes. In the end though, it'd be a glorified version of the below and alot of work... – J. Martin Jan 21 '10 at 15:30

1 Answers1

2

Okay,

I am not 100% exactly what you want to do, but I have an idea.

Maybe you mean something like this:

class View {
    public $hooks = array('getsomeVar');
    public $hooks_functions = array();
    public $attributes = array();
    public function __set($k,$v) {
        $this->attributes[$k] = $v;
    }
    public function __get($k) {
        if (isset($this->attributes[$k])){
            $hooks = $this->get_functions_by_hook('get' . $k);
            if (!empty($hooks)){
                foreach ($hooks as $klass=>$methods) {
                    if (class_exists($klass)){
                        $class = new $klass();
                        foreach ($methods as $method) {
                            if (method_exists($class,$method)){
                                $this->attributes[$k] = $class->$method($this->attributes[$k]);
                            } 
                        }
                    }
                }
            }
            return $this->attributes[$k];
        } else {
            throw new Exception($k . " is not a view variable");
        }
    }

    public function register_filter($name,$class,$method) {
        if (in_array($name,$this->hooks)){
            $this->hooks_functions[$name][$class][] = $method;
        } else {
            throw new Exception($name . ' is not a valid hook');
        }
    }

    public function get_functions_by_hook($name) {
        if (array_key_exists($name,$this->hooks_functions)){
            return $this->hooks_functions[$name];
        }
        return array();
    }
}
class MyPlugin {
    public function fix_string($str) {
        return str_replace("ruby",'php',$str);
    }
}

$v = new View();
$v->someVar = 'ruby is great';
$v->register_filter('getsomeVar','MyPlugin','fix_string');
echo $v->someVar;

or you can use this method, which is more event like. Again sample code, but you should be able to cannabalize it.

class EventDispatcher {
    public static $listeners = array();
    public static function registerListener(&$instance) {
        if (!in_array($isntance,self::$listeners)){
            array_push(self::$listeners,$instance);
        }
    }
    public static function dispatchEvent($name,&$value) {
        foreach (self::$listeners as $listener) {
            if (method_exists($listener,'interests')){
                $funcs = $listener->interests();
                if (array_key_exists($name,$funcs)){
                    foreach ($funcs as $f) {
                        $value = $listener->$f($value);
                    }
                }
            }
        }
    }
}
class Plugin {
    public static function registerPlugin($class_name) {
        if (class_exists($class_name)){
            EventDispatcher::registerListener(new $class_name());
        }
    }
}
class Model {
    public function __construct() {
        EventDispatcher::registerListener($this);
    }
    public function special($value) {
        echo "I got called too!\n\n";
        return $value;
    }
    public function interests() {
        return array(
            "getsomeVar" => "special",
        );
    }
}
class View {
    public $attributes = array();
    public function __set($k,$v) {
        $this->attributes[$k] = $v;
    }
    public function __get($k) {
        if (isset($this->attributes[$k])){
            EventDispatcher::dispatchEvent('get' . $k,$this->attributes[$k]);
            return $this->attributes[$k];
        } else {
            throw new Exception($k . " is not a view variable");
        }
    }
}
class MyPlugin {
    public function fix_string($str) {
        return str_replace("ruby",'php',$str);
    }
    public function interests() {
        return array(
            "getsomeVar" => "fix_string",
        );
    }
}
Plugin::registerPlugin('MyPlugin');
$model = new Model();
$v = new View();
$v->someVar = 'ruby is great';
echo $v->someVar;

This is just some sample code, I don't do it this way at all, but it seems like it may be what you are talking about.

Cheers, Jason

Addendum:

Most of this stuff is about the WP codebase.

WP accesses variables set in the global scope that are modified like so:

function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
    global $wp_filter, $merged_filters;

    $idx = _wp_filter_build_unique_id($tag, $function_to_add, $priority);
    $wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args);
    unset( $merged_filters[ $tag ] );
    return true;
}

It has some other functions like, has_filter etc...

function apply_filters($tag, $value) {
    global $wp_filter, $merged_filters, $wp_current_filter;

    $args = array();
    $wp_current_filter[] = $tag;

    // Do 'all' actions first
    if ( isset($wp_filter['all']) ) {
        $args = func_get_args();
        _wp_call_all_hook($args);
    }

    if ( !isset($wp_filter[$tag]) ) {
        array_pop($wp_current_filter);
        return $value;
    }

    // Sort
    if ( !isset( $merged_filters[ $tag ] ) ) {
        ksort($wp_filter[$tag]);
        $merged_filters[ $tag ] = true;
    }

    reset( $wp_filter[ $tag ] );

    if ( empty($args) )
        $args = func_get_args();

    do {
        foreach( (array) current($wp_filter[$tag]) as $the_ )
            if ( !is_null($the_['function']) ){
                $args[1] = $value;
                $value = call_user_func_array($the_['function'], array_slice($args, 1, (int) $the_['accepted_args']));
            }

    } while ( next($wp_filter[$tag]) !== false );

    array_pop( $wp_current_filter );

    return $value;
}

This is not an event driven system, it's a method lookup table which is just a giant hash that looks up the user defined functions to call.

apply_filters, and all plugin like functions are called procedurally as the code is rendered, here is an example

    if ( $prefixed ) {
        $value = apply_filters("pre_$field", $value);
        $value = apply_filters("${field_no_prefix}_save_pre", $value);
    } else {
        $value = apply_filters("pre_post_$field", $value);
        $value = apply_filters("${field}_pre", $value);
    }

Or for actions, in an actual template view like so:

<p class="submit"><input type="submit" class="button" name="submit" value="<?php esc_attr_e('Add Category'); ?>" /></p>
<?php do_action('edit_link_category_form', $category); ?>
</form>
J. Martin
  • 1,683
  • 2
  • 17
  • 33
  • You would probably want to break that up into a plugin class, that way you could use it so that plugins could hook into anything. and not just filter, you could do it for methods as well... – J. Martin Jan 21 '10 at 14:44
  • jolierouge, thanks for you effort. First, I think your eventDispatcher solution makes more sense as dispatching events/filters seems outside the responsibility of the view.
    The problem I see with both is that it doesn't seem to allow having a filter on something in a loop. Eg to go back to wordpress, a category displays multiple posts in a loop. In the loop it has filtering events. I don't see how I can have these too inside a loop: $view->posts = $latestTenPostsInCategoryX; foreach of those posts: echo $post->title(); // and title is filtered
    – koen Jan 21 '10 at 17:44
  • Here's some more background to my overall goal: http://stackoverflow.com/questions/2074317/viewhelper-newable-injectable-dilemma – koen Jan 21 '10 at 17:45