SitePoint Sponsor

User Tag List

Results 1 to 25 of 26

Hybrid View

  1. #1
    Resident Code Monkey Chris Corbyn's Avatar
    Join Date
    Nov 2005
    Location
    Melbourne, Australia
    Posts
    713
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Creating dynamic plugins for PHP Classes

    I'm interested in hearing how you guys would go about creating classes that support plugins. I'm really keen to hear ways in which you can have plugins respond to events that are triggered inside the class they are plugged into and how you actually load and contain the plugin. But generally, I'm just looking for a healthy discussion on plugin development for PHP Classes

    I've just yesterday done something like this. It contains event handlers which trigger responses from the plugins. Obviously, in this little example it's not even worth writing the code for but on a large scale I can imagin this would give plugin authors incredible control over the class they plug in to.

    PHP Code:
    class baseClass
    {
        private 
    $plugins = array();

        public 
    $word '';
       
        public function 
    loadPlugin(&$object)
        {
            
    $plugin_name $object->identifier;
            
    $object->loadBaseObject(&$this);
            
    $this->plugins[$plugin_name] =& $object;
        }

        
    //This sets of logic in the plugin
        
    private function triggerEvents($event)
        {
            foreach (
    $this->plugins as $name => $object)
            {
                if (
    method_exists($object$event))
                {
                    
    $this->plugins[$name]->$event();
                }
            }
        }

        private function 
    speak($word)
        {
            
    $this->word $word;
            
    $this->triggerEvents('onSpeak');
            echo 
    $this->word;
        }

        public function &
    getPlugin($name)
        {
            return 
    $this->plugins[$name];
        }
    }

    class 
    dummyPlugin
    {
        private 
    $baseInstance;
        
        public 
    $identifier 'dummy'//The name of the plugin
        
        
    public function loadBaseObject(&$object)
        {
            
    $this->baseInstance =& $object;
        }
        
        public function 
    test()
        {
            echo 
    'Test called...';
        }

        
    //Only runs when baseClass allows it to
        
    public function onSpeak()
        {
            
    $this->baseInstance->word .= '... touched by a plugin';
        }
    }

    $base = new baseClass;
    $base->speak('foo'); //Foo

    $base->loadPlugin(new dummyPlugin);

    $base->speak('bar'); //bar ... touched by a plugin

    $base->getPlugin('dummy')->test(); // Test called 
    I can see a big problem with the above though, what if a user loads two plugins that each try to alter the same property in some fashion? How would you go about minimizing that risk without forcing users to only load one plugin?

    Has anyone got any cool and interesting ideas to do things like above?
    Last edited by stymiee; May 11, 2006 at 07:45.

  2. #2
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

  3. #3
    Resident Code Monkey Chris Corbyn's Avatar
    Join Date
    Nov 2005
    Location
    Melbourne, Australia
    Posts
    713
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    Thanks, that looks really awesome Yeah that's basically the sort of thing I'd like to acheive but purely in a vanilla PHP installation. It looks as though what I was doing above was almost like a sloppy, procedural way of including pointcuts in the code. I'll keep reading around this topic in PHP and how other people have attempted AOP-style programming with it.

    I guessed this thread wouldn't get many responses... I posted it on another forum I moderate/admin but it hasn't had much interest there neither I live for this sort of discussion on theory/design.

    Have you done similar things yourself?

  4. #4
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Here's something experimental I threw together a year ago. Oh, how this makes me wish PHP had closures...
    PHP Code:
    /**
     * Delegate interface. Describes a kind of refined callback.
     * 
     * @author        Ezku (dmnEe0@gmail.com)
     * @since        Sep 6, 2005
     */
    interface IDelegate
    {
        
    /**
         * Invoke Delegate
         * @param    array    arguments, optional
         */
        
    public function invoke($args = array());
    }

    /**
     * Calls a method on an object upon invocation.
     * 
     * @author        Ezku (dmnEe0@gmail.com)
     * @since        Sep 6, 2005
     */
    class Delegate implements IDelegate
    {
        private 
    $subordinate NULL;
        private 
    $method NULL;
        
        public function 
    __construct($subordinate$method)
        {
            
    $this->subordinate $subordinate;
            
    $this->method $method;
        }
        
        public function 
    invoke($args = array())
        {
            
    $this->subordinate Handle::resolve($this->subordinate);
            
    $callback = array(&$this->subordinate$this->method);
            return 
    call_user_func_array($callback$args);
        }

    PHP Code:
    /**
     * "A Handle represents an uninstantiated object that takes the place of a
     * given object and can be swapped out for the object.
     * Implements lazy loading for composing object heirarchies."
     * 
     * @author        Ezku (dmnEe0@gmail.com)
     * @since        Jul 12, 2005
     * @see            [url]http://wact.sourceforge.net/index.php/ResolveHandle[/url]
     */
    class Handle
    {
        
    /**
         * @var    string    class name
         */
        
    protected $class NULL;
        
    /**
         * @var    array    class constructor arguments
         */
        
    protected $args = array();
        
        
    /**
         * @param    string    class name
         * @param    array    class constructor arguments, optional
         */
        
    public function __construct($class$args = array())
        {
            
    $this->class    = (string)    $class;
            
    $this->args        = (array)    $args;
        }
        
        
    /**
         * Resolves a Handle; replaces a Handle instance with its identified class
         * @param    object    Handle
         * @return    object
         */
        
    static public function resolve($handle)
        {
            if (
    $handle instanceof self)
            {
                
    $handle call_user_constructor_array($handle->getClass(), $handle->getArgs());
            }
            return 
    $handle;
        }
        
        public function 
    getClass() { return $this->class; }
        public function 
    getArgs() { return $this->args; }

    PHP Code:
    /**
     * Simple Event: attach a Delegate and trigger.
     * @author        Ezku (dmnEe0@gmail.com)
     * @since        1.10.2005
     */
    class Event
    {
        private 
    $listener NULL;
        
        
    /**
         * @param    object    IDelegate
         * @return    boolean    overridden
         */
        
    public function attach(IDelegate $listener)
        {
            
    $overridden = isset($this->listener);
            
    $this->listener $listener;
            return 
    $overridden;
        }
        
        
    /**
         * @param    array    invocation arguments
         * @return    mixed    results
         */
        
    public function trigger($args = array())
        {
            return 
    $this->listener->invoke($args);
        }
        
        
    /**
         * Get attached listener
         * @return    object    IDelegate
         */
        
    public function getAttached()
        {
            return 
    $this->listener;
        }
    }
    /**
     * Multicast Event: attach multiple Delegates.
     * @author        Ezku (dmnEe0@gmail.com)
     * @since        1.10.2005
     */
    class MulticastEvent extends Event
    {
        private 
    $listeners = array();
        
        
    /**
         * @param    object    IDelegate
         */
        
    public function attach(IDelegate $delegate)
        {
            
    $this->listeners[] = $delegate;
        }
        
        
    /**
         * @param    array    invocation arguments
         * @return    array    results
         */
        
    public function trigger($args = array())
        {
            
    $return = array();
            foreach(
    $this->listeners as $listener)
            {
                
    $return[] = $listener->invoke($args);
            }
            return 
    $return;
        }
        
        
    /**
         * Get attached listeners
         * @return    array    IDelegate
         */
        
    public function getAttached()
        {
            return 
    $this->listeners;
        }

    PHP Code:
    /**
     * Handle groups of events
     * @author        Ezku (dmnEe0@gmail.com)
     * @since        26.9.2005
     */
    class EventHandler
    {
        private 
    $args = array();
        private 
    $events = array();
        
        
    /**
         * EventHandler consturctor
         * @param    mixed    default invocation argument
         * ...
         */
        
    public function __construct()
        {
            
    $this->args func_get_args();
        }
        
        
    /**
         * Attach a listener to an event
         * @param    string    event name
         * @param    object    IDelegate
         */
        
    public function attach($eventIDelegate $listener)
        {
            if(empty(
    $this->events[$event]))
            {
                
    $this->events[$event] = $this->getEvent();
            }
            
    $this->events[$event]->attach($listener);
        }
        
        
    /**
         * Trigger an event
         * @param    string    event name
         * @param    array    arguments, optional
         */
        
    public function trigger($event$args = array())
        {
            if(empty(
    $args))
            {
                
    $args $this->getArgs();
            }
            
    $return NULL;
            if(!empty(
    $this->events[$event]))
            {
                
    $return $this->events[$event]->trigger((array) $args);
            }
            return 
    $return;
        }
        
        
    /**
         * @return    array    default invocation arguments
         */
        
    protected function getArgs()
        {
            return 
    $this->args;
        }
        
        
    /**
         * @return    object    a fresh Event
         */
        
    protected function getEvent()
        {
            return new 
    Event;
        }
        
        
    /**
         * Shortuct for trigger()
         */
        
    public function __call($event$args)
        {
            return 
    call_user_func_array(array($this'trigger'), array($event$args));
        }
        
        
    /**
         * Shortcut for attach()
         */
        
    public function __set($event$args)
        {
            return 
    call_user_func_array(array($this'attach'), array($event$args));
        }
    }
    /**
     * Extend EventHandler to use MulticastEvents
     * 
     * @author        Ezku (dmnEe0@gmail.com)
     * @since        26.9.2005
     */
    class MulticastEventHandler extends EventHandler
    {
        protected function 
    getEvent()
        {
            return new 
    MulticastEvent;
        }

    It's supposed to be used like this (a total nonsense example, bear with me):

    PHP Code:
    $events = new EventHandler;
    $events->onload = new Delegate(new Handle('Controller'), 'execute');
    $events->onload(Context::getInstance); // lazy-loads and creates a Controller instance, calls execute() with the arguments given 
    Here's a slightly more elaborate example, albeit on an older version of the code.

    The whole idea is hacky to begin with, but aspect oriented programming in PHP seems doubly so to me. Just gives me a bad vibe.

    Edit:

    If you intend to use this, it would likely be a good idea to implement call_user_constructor_array() , as well as get rid of Handle in favour of a LazyDelegate. Delegate using a static method in Handle really creates an ugly dependency.
    Last edited by Ezku; May 22, 2006 at 16:37.

  5. #5
    Resident Code Monkey Chris Corbyn's Avatar
    Join Date
    Nov 2005
    Location
    Melbourne, Australia
    Posts
    713
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ezku
    Here's something experimental I threw together a year ago. Oh, how this makes me wish PHP had closures...
    Sorry to dig this up again but you got me thinking about closures and I've been bored today. I came up with a nast dirty evil little hack that *sort of* works like a basic closure.... I'd never use the concept I doubt though - I just like to experiment a lot with ideas

    PHP Code:
    function myClosure($arg1$arg2)
    {
        
    $localVar 8;

        
    $exampleReturned create_function('$innerArg''
        return (('
    .$arg1.' + '.$arg2.')/($innerArg + '.$localVar.'));
        '
    );

        return 
    $exampleReturned;
    }

    $globalFunc myClosure(24);

    echo 
    $globalFunc(4); //0.5 

  6. #6
    Resident Code Monkey Chris Corbyn's Avatar
    Join Date
    Nov 2005
    Location
    Melbourne, Australia
    Posts
    713
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    OK ignore me, that's not even worth looking at, you can't write back again so it pointless.

  7. #7
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by d11wtq
    Sorry to dig this up again but you got me thinking about closures and I've been bored today. I came up with a nast dirty evil little hack that *sort of* works like a basic closure.... I'd never use the concept I doubt though - I just like to experiment a lot with ideas
    It's possible to make closures with create_function. I have used the following implementation, if you're interested :
    PHP Code:
    function curry($fnc$arg /* [, $arg ... ] */) {
        if (
    is_string($fnc)) {
            
    $fnc addcslashes($fnc"\0");
        }
        
    $args func_get_args();
        
    array_shift($args);
        
    $callargs = Array();
        foreach (
    $args as $arg) {
            
    $callargs[] = var_export($argTRUE);
        }
        
    $lambda sprintf("\$args = func_get_args();\nreturn call_user_func_array(\"%s\", array_merge(Array(%s), \$args));"$fncimplode(",",$callargs));
        return 
    create_function(''$lambda);

    It's limited by only accepting primitives (eg. no objects or resources), but if you wanted, I suppose that could be archieved by storing in a global location. I don't use it a lot though, since functional programming is really a bit half-arsed in PHP

  8. #8
    SitePoint Wizard dreamscape's Avatar
    Join Date
    Aug 2005
    Posts
    1,080
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    You know almost every time I create a plugin API for a project, I do it differently. A text book observer/obervable works well for cases where you have an object that you want to make plugins for, but not so well for cases where you may have many objects you want accessible to plugins or even entire applications [at least not the textbook sort of case].

    The one time for a generic service plugin API that covered an entire application, I had the service plugin, which loaded and called the plugins, initialized it and placed it in a registry, and then just placed virtual calls to it wherever I felt they were needed or would be useful. I used __call() magic, so the calls were in the form $service_plugins->doSomeAction($param-1, ... $param-n);

    d11wtq, I was intrigued by your first example which gives the plugin a base object it can work with. So expanding upon that, and the idea of point cuts, I just wanted to see what I could come up with this time around, so just whipped this up. Mind you I haven't had much time to think about it, and this approach is bound to be flawed in several ways, but it was a fun exercise.

    Here is the real heart of it, first the Plugins base class, made abstract to allow more than one "plugin group" in the app:
    PHP Code:
    abstract class Plugins {
        protected 
    $plugins = array();

        public function 
    __construct($plugins) {
            foreach (
    $plugins as $plugin) {
                
    $plug = new $plugin();
                if (
    $plug instanceof Plugin) {
                    
    $this->plugins[] = $plug;
                }
            }
        }

        public function 
    __call($method$args) {
            
    $baseObject false;

            if ((
    sizeof($args) > 0) && is_object($args[0])) {
                
    $baseObject array_shift($args);
            }

            foreach (
    $this->plugins as $key => $plugin) {
                if (!
    method_exists($plugin$method)) {
                    continue;
                }

                if (
    $baseObject !== false) {
                    
    $this->plugins[$key]->loadBaseObject($baseObject);
                } else {
                    
    $this->plugins[$key]->loadBaseObject(NULL);
                }

                
    call_user_func_array(array(&$this->plugins[$key], $method), $args);
            }
        }

    The the individual Plugin core:
    PHP Code:
    abstract class Plugin {
        protected 
    $baseInstance NULL;

        public function 
    loadBaseObject($object NULL) { 
            
    $this->baseInstance $object;
        }

        public function 
    __call($method$args) {
            if (
    $this->baseInstance === NULL) {
                return 
    false;
            }
            if (
    preg_match('/^do(\w+)$/'$method$matches)) {
                
    $method strtolower($matches[1]{0}) . substr($matches[1], 1);
                
    $return NULL;

                
    $plugin_method ucfirst($method);
                
    $plugin_args $args;
                
    array_unshift($plugin_args$this->baseInstance);

                
    call_user_func_array(array(&$this->baseInstance->plugins'before' $plugin_method), $plugin_args);

                if (
    method_exists($this$method)) {
                    
    $return call_user_func_array(array(&$this$method), $args);
                }

                
    call_user_func_array(array(&$this->baseInstance->plugins'on' $plugin_method), $plugin_args);

                
    call_user_func_array(array(&$this->baseInstance->plugins'after' $plugin_method), $plugin_args);

                return 
    $return;
            }
        }

    And then the core for "Plugabble" objects:
    PHP Code:
    abstract class Pluggable {
        public 
    $plugins = array();

        public function 
    __construct($plugins) {
            
    $this->plugins $plugins;
        }

        public function 
    __call($method$args) {
            if (
    preg_match('/^do(\w+)$/'$method$matches)) {
                
    $method strtolower($matches[1]{0}) . substr($matches[1], 1);
                
    $return NULL;

                
    $plugin_method ucfirst($method);
                
    $plugin_args $args;
                
    array_unshift($plugin_args$this);

                
    call_user_func_array(array(&$this->plugins'before' $plugin_method), $plugin_args);

                if (
    method_exists($this$method)) {
                    
    $return call_user_func_array(array(&$this$method), $args);
                }

                
    call_user_func_array(array(&$this->plugins'on' $plugin_method), $plugin_args);

                
    call_user_func_array(array(&$this->plugins'after' $plugin_method), $plugin_args);

                return 
    $return;
            }
        }

    The way this works, is that if you call doMethodName() on a Pluggable object, it will set itself as the current base object for the plugin group the object is using, call beforeMethodName() across the plugin group, call & get result of methodName() in the object [if it exists], call onMethodName() across the plugin group, call afterMethodName() across the plugin group, and finally return the result. And then basically the same is true of Plugins as well, so in addition to being responsive to Pluggable objects, Plugins can also be responsive to each other as well. And the Pluggable's method doesn't have to exist, so you can make virtual event/action calls.

    And then here is a quite simple & silly example I did of how one could implement the above API.

    First a "plugin group". I'm only going to have one group in this example, but naturally one could have as many as they wished. I also don't have anything in this group such as specific methods for the group on whole, but you naturally can and a more complex example probably would:
    PHP Code:
    class SpeakerPlugins extends Plugins { } 
    Then a simple ClassRoom that is Plugabble:
    PHP Code:
    class ClassRoom extends Pluggable {

        public 
    $noise false;


    Then a Pluggable speaker:
    PHP Code:
    class Speaker extends Pluggable {

        public 
    $speaker;
        public 
    $ended false;

        public function 
    __construct($speaker$plugins) {
            
    parent::__construct($plugins);
            
    $this->speaker $speaker;
        }

        protected function 
    speak($word) {
            echo 
    "{$this->speaker}$word<br />";
        }

        public function 
    speakNormally($word) {
            
    $this->speak($word);
        }

        public function 
    speakSoftly($word) { 
            
    $this->speak('<small>'.strtolower($word).'</small>');
        }

        public function 
    speakLoudly($word) { 
            
    $this->speak(strtoupper($word));
        }

        public function 
    endSpeech($text) {
            if (
    $this->ended === true) {
                return;
            }
            if (
    $text !== false) {
                
    $this->doSpeakNormally($text);
            }
            
    $this->ended true;
        }


    Then a Speech, which I didn't make Pluggable:
    PHP Code:
    class Speech {

        public 
    $speaker;

        public function 
    __construct(Pluggable $speaker) {
            
    $this->speaker $speaker;
        }

        public function 
    giveSpeech($texts$type 'normal') {
            
    $this->speaker->doStartSpeech();

            if (!
    is_array($texts)) {
                
    $texts = array($texts);
            }

            foreach (
    $texts as $text) {
                switch (
    $type) {
                    case 
    'soft':
                        
    $this->speaker->doSpeakSoftly($text);
                        break;
                    case 
    'loud':
                        
    $this->speaker->doSpeakLoudly($text);
                        break;
                    case 
    'normal':
                    default:
                        
    $this->speaker->doSpeakNormally($text);
                        break;
                }
            }

            
    $this->speaker->doEndSpeech('Thank You.');  
        }


    Then an abstract SpeakerPlugin for the SpeakerPlugins group:
    PHP Code:
    abstract class SpeakerPlugin extends Plugin {
        public 
    $speaker;

        protected function 
    speak($text) {
            echo 
    "{$this->speaker}$text<br />";
        }

    Then a Bell SpeakerPlugin:
    PHP Code:
    class Bell extends SpeakerPlugin {

        public 
    $speaker 'Bell';

        public function 
    onBeginClass() {
            
    $this->speak('&lt;&lt;&lt;bell rings&gt;&gt;&gt;');
            
    $this->baseInstance->noise false;
        }

        public function 
    onEndClass() {
            
    $this->speak('&lt;&lt;&lt;bell rings&gt;&gt;&gt;');
        }

        protected function 
    speak($text) {
            echo 
    "<strong>$text</strong><br />";
        }


    Then a Teacher SpeakerPlugin:
    PHP Code:
    class Teacher extends SpeakerPlugin {
         
        public 
    $speaker 'Teacher';

        public function 
    afterBeginClass() {
            if (
    $this->baseInstance->noise === true) {
                
    $this->speak("Hush children! The bell has rung.");
                
    $this->baseInstance->noise false;
            }
        }

        public function 
    afterEndClass() {
            if (
    $this->baseInstance->noise === true) {
                
    $this->speak("May I have your attention for a moment please!");
                
    $this->baseInstance->noise false;
            }
            
    $this->speak("Your reports are due 1 week from today. Have a nice weekend");
        }

        public function 
    onStartSpeeches() {
            if (
    $this->baseInstance->noise === true) {
                
    $this->speak("Shhh!");
                
    $this->baseInstance->noise false;
            }
        }

        public function 
    beforeStartSpeech() {
            
    $this->speak("{$this->baseInstance->speaker} is now going to give a speech.");
        }

        public function 
    afterSpeakSoftly($word) {
            
    $this->doReprimandSpeaker("Speak Up {$this->baseInstance->speaker}!");
            
    $this->baseInstance->doSpeakNormally($word);
        }

        public function 
    afterSpeakLoudly($word) {
            
    $this->doReprimandSpeaker("Thank you {$this->baseInstance->speaker}, but next time don't yell.");
            
    $this->baseInstance->doEndSpeech(false);
        }

        protected function 
    reprimandSpeaker($text) {
            
    $this->speak($text);
        }


    And finally Students SpeakerPlugin:
    PHP Code:
    class Students extends SpeakerPlugin {

        public 
    $speaker 'ClassRoom';

        protected 
    $canApplaud true;

        public function 
    beforeBeginClass() {
            
    $this->speak("&lt;&lt;&lt;giggling & loud talking&gt;&gt;&gt;");
            
    $this->baseInstance->noise true;
        }

        public function 
    afterBeginClass() {
            
    $this->speak("&lt;&lt;&lt;giggling & loud talking&gt;&gt;&gt;");
            
    $this->baseInstance->noise true;
        }

        public function 
    afterEndClass() {
            
    $this->speak("&lt;&lt;&lt;giggling & loud talking&gt;&gt;&gt;");
            
    $this->baseInstance->noise true;  
        }

        public function 
    beforeStartSpeeches() {
            
    $this->speak("&lt;&lt;&lt;giggling&gt;&gt;&gt;");
            
    $this->baseInstance->noise true;
        }

        public function 
    onReprimandSpeaker() {
            
    $this->speak("&lt;&lt;&lt;giggles&gt;&gt;&gt;");
        }

        public function 
    beforeEndSpeech($text) {
            if (
    $this->baseInstance->ended === true) {
                
    $this->canApplaud false;
            } else {
                
    $this->canApplaud true;
            }
        }

        public function 
    afterEndSpeech($text) {
            if ((
    $text !== false) && $this->canApplaud) {
                
    $this->speak("&lt;&lt;&lt;applause&gt;&gt;&gt;");
            }
        }


    Whew.

    And now putting it all together something like:
    PHP Code:
    $load_plugins = array(
        
    'Students',
        
    'Teacher',
        
    'Bell',
    );

    $plugins = new SpeakerPlugins($load_plugins);


    $class = new ClassRoom($plugins);

    $class->doBeginClass();

    echo 
    '<hr />';

    $class->doStartSpeeches();

    echo 
    '<hr />';

    $speech = new Speech(new Speaker('Bob'$plugins));
    $speech->giveSpeech('Foo Bar Foo Foo.''soft');

    echo 
    '<hr />';

    $speech = new Speech(new Speaker('Linda'$plugins));
    $speech->giveSpeech(array(
        
    'Foo Bar Foo Foo.',
        
    'Foo Bar Foo Foo.',
        
    'Foo Bar Foo Foo.',
        
    'Foo Bar Foo Foo.',
    ));


    echo 
    '<hr />';

    $speech = new Speech(new Speaker('John'$plugins));
    $speech->giveSpeech('Foo Bar Foo Foo.''loud');

    echo 
    '<hr />';

    $class->doEndSpeeches();

    echo 
    '<hr />';

    $class->doEndClass(); 
    Which outputs something like:
    ClassRoom: <<<giggling & loud talking>>>
    <<<bell rings>>>
    ClassRoom: <<<giggling & loud talking>>>
    Teacher: Hush children! The bell has rung.
    ------------------------
    ClassRoom: <<<giggling>>>
    Teacher: Shhh!
    ------------------------
    Teacher: Bob is now going to give a speech.
    Bob: foo bar foo foo.
    Teacher: Speak Up Bob!
    ClassRoom: <<<giggles>>>
    Bob: Foo Bar Foo Foo.
    Bob: Thank You.
    ClassRoom: <<<applause>>>
    ------------------------
    Teacher: Linda is now going to give a speech.
    Linda: Foo Bar Foo Foo.
    Linda: Foo Bar Foo Foo.
    Linda: Foo Bar Foo Foo.
    Linda: Foo Bar Foo Foo.
    Linda: Thank You.
    ClassRoom: <<<applause>>>
    ------------------------
    Teacher: John is now going to give a speech.
    John: FOO BAR FOO FOO.
    Teacher: Thank you John, but next time don't yell.
    ClassRoom: <<<giggles>>>
    ------------------------
    ------------------------
    <<<bell rings>>>
    ClassRoom: <<<giggling & loud talking>>>
    Teacher: May I have your attention for a moment please!
    Teacher: Your reports are due 1 week from today. Have a nice weekend
    If you play around with the example a bit, you'll notice that in addition to being responsive to the Pluggable objects, particularly the Teacher & Students Plugins are quite responsive to each other too. For example, if the students aren't being noisy, the Teacher doesn't have to keep telling them to be quite.

    I think this may be one of my favorite implementations to date, though I haven't had a lot of time to think it over or test it in a real scenario, so I reserve the right to take that comment back

  9. #9
    Resident Code Monkey Chris Corbyn's Avatar
    Join Date
    Nov 2005
    Location
    Melbourne, Australia
    Posts
    713
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    ~dreamscape thanks for the appreciation of how it was implemented (I've used my first version in Swift and it seems to be working nicely for the plugins I've been mucking about with

    Long post! Started reading the code but I'm pretty exhausted from decorating all day so I'm gonna have to look over it in more detail after a soak and a coffee Love the example you used! It's actually quite a nice way to describe exactly how the everything communicates with each other. And yes, it's brilliant that everything only runs as and when it needs to - and in fact it doesn't have to run at all if it doesn't want to... I think it's really flexible and I'll no doubt keep using something like this, alveit a little more refined as I suss out any pitfalls etc.

    I'll probably post again after a second look at your code

  10. #10
    Resident Code Monkey Chris Corbyn's Avatar
    Join Date
    Nov 2005
    Location
    Melbourne, Australia
    Posts
    713
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    ~Ezku, I love that code. Yeah, you refer to it as "hacky" but it's damn good given the language it's implemented in. If we all sat around building forms and validating POST data all day we'd never move anywhere. Do you mind if I copy that code and play around with it a little bit myself?

  11. #11
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by d11wtq
    ~Ezku, I love that code. Yeah, you refer to it as "hacky" but it's damn good given the language it's implemented in. If we all sat around building forms and validating POST data all day we'd never move anywhere. Do you mind if I copy that code and play around with it a little bit myself?
    Don't mind at all. It's not like it wouldn't be trivial once you got the idea. The idea is basically from WACT, as far as I can remember, but I was thinking more of Javascript events when I implemented my version.

  12. #12
    Resident Code Monkey Chris Corbyn's Avatar
    Join Date
    Nov 2005
    Location
    Melbourne, Australia
    Posts
    713
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    JavaScript was exactly what was on my mind when I got thinking about plugins supporting event handlers yesterday Although I love PHP to bits, the more advanced theory I learn, the more I wish PHP could "do more".

    Thanks, I will play around with this. I followed the code in your example above, but I'd just like to play around with it in a more practical situation (a mailer class I'm working on).

  13. #13
    SitePoint Guru Galo's Avatar
    Join Date
    May 2005
    Location
    Holland!
    Posts
    852
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by d11wtq
    ~Ezku, I love that code. Yeah, you refer to it as "hacky" but it's damn good given the language it's implemented in.
    Must say i agree Ezku... nice code ...
    Business as usual is off the menu folks, ...

  14. #14
    SitePoint Addict timvw's Avatar
    Join Date
    Jan 2005
    Location
    Belgium
    Posts
    354
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I would end up going for an eventhandling / observer pattern..

    I'm aware of the fact that in .NET delegates are typesafe callbacks / eventhandlers but i'm still not sure how useful this concept is for PHP applications.

  15. #15
    Resident Code Monkey Chris Corbyn's Avatar
    Join Date
    Nov 2005
    Location
    Melbourne, Australia
    Posts
    713
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by timvw
    I would end up going for an eventhandling / observer pattern..

    I'm aware of the fact that in .NET delegates are typesafe callbacks / eventhandlers but i'm still not sure how useful this concept is for PHP applications.
    Hi tim Fancy meeting you here :P

    Yeah you're right, it's not really well suited for PHP Applications but there's still times when I've thought it would be useful to be able to trigger events. Still pretty cool to toss the ideas around all the same.

  16. #16
    SitePoint Addict
    Join Date
    May 2003
    Location
    The Netherlands
    Posts
    391
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by timvw
    I'm aware of the fact that in .NET delegates are typesafe callbacks / eventhandlers but i'm still not sure how useful this concept is for PHP applications.
    That's exactly how I feel also about event driven apps in PHP and my reason not to go further with PRADO, which I actually liked because of the component based architecture.
    There’s more than one way to skin a cat.

  17. #17
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by timvw
    I would end up going for an eventhandling / observer pattern..
    But isn't this exactly that, only isolated to a separate component instead of being an integral part of the observers and observees themselves? I'm asking merely out of curiosity.

  18. #18
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    So the Pluggable - which is the main feat of this thing - is a sort of automatic decorator. The only difference is that rather than manually writing a decorator for the purpose, you use __call to dispatch on the decorated object ?
    What's the reason for having both the class Pluggable and Plugin ? Couldn't the Pluggable have an array of listeners, and you could then register with that ? (And gt rid of Plugin)
    I realize that the actual "wiring" would be more verbose, but that could then be hidden away somewhere else (or somehow be automagically resolved).

  19. #19
    SitePoint Wizard dreamscape's Avatar
    Join Date
    Aug 2005
    Posts
    1,080
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    So the Pluggable - which is the main feat of this thing - is a sort of automatic decorator.
    Ah let's see.... hmm... Well I don't pretend to be all that familiar with the decorator pattern, but I guess you can kind of look at it like that. Though to me I think of it more as an automatic point cutter, making cut points before entering the method and after leaving it, and now that I've thought some, ideally one that enables using or manipulating the return value.

    Quote Originally Posted by kyberfabrikken
    The only difference is that rather than manually writing a decorator for the purpose, you use __call to dispatch on the decorated object ?
    Uh.. ok.... the automatic wiring gets a little hairy, so let's see if I can map this out here. On a Pluggable, __call dispatches a before point cut (I'm just going to use the term "point cut" here, even though this really isn't AOP, I think we can still call them point cuts). Then it dispatches to the called method of the object, the Pluggable. Then it dispatches to the after point cuts.

    These point cut dispatches are made to the Plugins which the Pluggable has loaded. On a Plugins collection, __call checks if an object was passed in as the 1st parameter. If so, it sets this object as the current "base object" for each Plugin and removes it from the parameter array. Then it loops through each plugin, and if the requested point cut exists in the Plugin, it sets the current base object for that Plugin, and then dispatches the call to the Plugin.

    Quote Originally Posted by kyberfabrikken
    What's the reason for having both the class Pluggable and Plugin ?
    Let's see if I can explain this in any meaningful way ... I'll do all 3 class types, to help me along. First we have Pluggable, which is an object that you want to be able to plug into. Next we have Plugins, which is a collection of Plugins, or group if you prefer. For a small application you may just have one type of Plugins, as I did in the example, but let's say something like eCommerce, you may have PaymentPlugins, ShippingPlugins, ProductPlugins, ServicePlugins, etc... And finally a Plugin is and individual plugin. Then, the reason for the "Plugin" base class that plugins must extend is that they have some common behavior, such as loading the base object they are currently plugging into (which changes on the fly as the program executes), as well as some __call() magic that basically allows plugins to also be pluggable, to allow not just Plugin to Pluggable interaction, but also Plugin to Plugin interaction. I've worked on a few Plugin APIs before as I said, and letting Plugins be able to plug into each other as well as to the application is really a cool thing, and at times quite necessary. And just generally makes the plugins more responsive since they can be aware of actions of other plugins (maybe this plugin over here has already done something, so that plugin over there doesn't need to).

    As for getting rid of Plugin, my thought is actually to minimize Pluggable. Pluggables are objects you want the ability to hook into, so these are likely the main objects of your application, or at least some, and naturally you don't want to be forced to have them all extend Pluggable. So I'm thinking now, to have a DynamicPluggable which would allow any object to become pluggable itself, by passing the object to it.

    So from the example instead of:
    PHP Code:
    $plugins = new SpeakerPlugins($load_plugins);

    $class = new ClassRoom($plugins);

    $class->doBeginClass(); 
    I'm thinking something like:
    PHP Code:
    $school = new DynamicPluggable(new SpeakerPlugins($load_plugins));

    $class = new ClassRoom();

    $school->doBeginClass($class); 
    Here, ClassRoom is no longer a Pluggable. The pluggable calls are a little more awkward, being the form $pluggable->doSomeMethod($object-to-plug-into, $params...); rather than $object-to-plug-into->doSomeMethod($params...); but ultimately I think that the former is more flexible.

    If done right, that could also actually get rid of the need for Plugin too, and really any object could then become either a Pluggable or a Plugin or both at any point.

  20. #20
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Ah ... silly me. I somehow got confused about Plugin and Plugins - the little s makes the difference. It makes sense to me now. So the Plugins (with a suffix s) is essentially just a collection of Plugin. I suggest renaming it to something like PluginCollection to avoid confusion.
    I would have suggested that Plugin could be scrapped and replaced with a callback. I can see your point in making them pluggable per default, rather than having to create new Pluggable's around each Plugin. I'm not entirely convinced of this being the best solution, since it adds a semantic overhead over just using a callback in the trivial cases. I haven't used it though, and I suspect the crux is how often you'll need to re-wire the Plugins (without s) as Pluggables. How's your take on that ?

  21. #21
    SitePoint Wizard dreamscape's Avatar
    Join Date
    Aug 2005
    Posts
    1,080
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    I'm not entirely convinced of this being the best solution, since it adds a semantic overhead over just using a callback in the trivial cases. I haven't used it though, and I suspect the crux is how often you'll need to re-wire the Plugins (without s) as Pluggables. How's your take on that ?
    Ah yes, for very small applications it might be too much overhead. Though for larger ones I believe it would be acceptable, especially if the application has numerous plugin collections and numerous plugins per collection. Though none of the overhead of plugins also being Pluggable comes into play unless the Plugin makes a pluggable call.

    The other thing is that for the trivial cases, one could easily bypass the __call magic and overhead by calling the normal methods and manually calling the necessary point cuts or hooks inside them, somewhat similar to d11wtq's first example, you still have this option.

    The solution still needs work, no doubt. I've already got a number of things that I know could be done better. I've started working on some of them. Working on removing the necessity for objects to be "Pluggable" in order to have plugins interact with them right now.

    PS. Thanks for the tip about renaming Plugins. I see how that could be confusing.

  22. #22
    SitePoint Wizard dreamscape's Avatar
    Join Date
    Aug 2005
    Posts
    1,080
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I've been working on this some more recently. I've made it now that any Object can be made pluggable; so you'd have an instance of Pluggable with a given PluginCollection; then you simply make the pluggable calls like $pluggable->doSomeMethod($object, $param-1, ..., $param-n).

    It's a bit awkward at first maybe, but I think that is quickly overcome by the fact that anything can be dynamically made pluggable. This is where the real flexibility lies.

    I've made a bit of a generic flowchart of the magic wiring that goes on when you make the call:
    http://www.sitepoint.com/forums/atta...chmentid=31876

    The only major issue I can see is the question of how much overhead is introduced, and though I've only done small tests, I've been very pleased with the results. For me, the flexibility outweighs the little bit of overhead.

    In any case, I think I'll probably brush it up some & release the API to the community... you know, share & share alike & all that jazz

  23. #23
    SitePoint Member
    Join Date
    Mar 2007
    Posts
    1
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Hi Dreamscape. This was a great read. I was wondering if you have an update to your plugin system?

  24. #24
    SitePoint Wizard dreamscape's Avatar
    Join Date
    Aug 2005
    Posts
    1,080
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by cgarvis View Post
    Hi Dreamscape. This was a great read. I was wondering if you have an update to your plugin system?
    Sure do: http://code.google.com/p/phpplexus/ (link is in my sig too)
    <.smarter.web.development.>
    PHP Stuff: Plexus | Chocolate (BDD Framework... coming soon)
    Graphite

  25. #25
    SitePoint Member
    Join Date
    Jul 2005
    Posts
    10
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    check this post out

    http://www.flyingwithfire.com/2006/1...-hooks-system/

    You may have a problem with the ' things as wordpress messed them up

    http://rafb.net/p/0nkh8l85.html
    http://rafb.net/p/6zo1Tq74.html

    Those are the correct files with the ' changed so it works


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •