4

I want to write a sort of "plugin/module" system for my code, and it would make it much easier if I could "add" stuff into a class after it's been defined.

For example, something like this:

class foo {
  public function a() {
     return 'b';
  }
}

There's the class. Now I want to add another function/variable/const to it, after it's defined.

I realize that this is probably not possible, but I need confirmation.

Savetheinternet
  • 491
  • 4
  • 11

7 Answers7

6

No, you cannot add methods to an already defined class at runtime.

But you can create similar functionality using __call/__callStatic magic methods.

Class Extendable  {
    private $handlers = array();
    public function registerHandler($handler) {
        $this->handlers[] = $handler;
    }

    public function __call($method, $arguments) {
        foreach ($this->handlers as $handler) {
            if (method_exists($handler, $method)) {
                return call_user_func_array(
                    array($handler, $method),
                    $arguments
                );
            }
        }
    }
}

Class myclass extends Extendable {
    public function foo() {
        echo 'foo';
    }
}

CLass myclass2 {
    public function bar() {
        echo 'bar';
    }
}

$myclass = new myclass();
$myclass->registerHandler(new myclass2());

$myclass->foo(); // prints 'foo'
echo "\n";

$myclass->bar(); // prints 'bar'
echo "\n";

This solution is quite limited but maybe it will work for you

German Rumm
  • 5,782
  • 1
  • 25
  • 30
  • In what way is this limited? The only way I can see this being limited is possibly not being able to add protected methods. – B T Feb 13 '11 at 19:47
3

To add/change how classes behave at runtime, you should use Decorators and/or Strategies. This is the prefered OO approach over resorting to any magic approaches or monkey patching.

A Decorator wraps an instance of a class and provides the same API as that instance. Any calls are delegated to the wrapped instance and results are modified where needed.

class Decorator 
{
    // …

    public function __construct($decoratedInstance)
    {
        $this->_decoratedInstace = $decoratedInstance;    
    }
    public function someMethod()
    {
        // call original method
        $result = $this->_decoratedInstance->someMethod();
        // decorate and return
        return $result * 10;
    }
    // …
}

For Strategy, see my more complete example at Can I include code into a PHP class?

More details and example code can be found at

Community
  • 1
  • 1
Gordon
  • 312,688
  • 75
  • 539
  • 559
1

I have a few methods for you to try. :) Have fun coding.

Method for only one class:

class main_class {
    private $_MODS = array(),...;
    public ...;
    public function __construct(...) {
        ...
        global $MODS_ENABLED;
        $this -> $_MODS = $MODS_ENABLED;
    }
    ...
    public function __get( $var ) {
        foreach ( $this->_MODS as $mod ) 
            if ( property_exists( $mod, $var ) )
                return $mod -> $var;
    }
    public function __call( $method, $args ) {
        foreach ( $this->_MODS as $mod )
            if ( method_exists( $mod, $method ) )
                return call_user_method_array( $method, $mod, $args );
    }
}

Method for when you want to deal with more than one class:

class modMe {
    private $_MODS = array();
    public function __construct__() {
        global $MODS_ENABLED;
        $this -> $_MODS = $MODS_ENABLED;
    }
    public function __get( $var ) {
        foreach ( $this->_MODS as $mod ) 
            if ( property_exists( $mod, $var ) )
                return $mod -> $var;
    }
    public function __call( $method, $args ) {
        foreach ( $this->_MODS as $mod )
            if ( method_exists( $mod, $method ) )
                return call_user_method_array( $method, $mod, $args );
    }
}
class mainClass extends modMe {
    function __construct(...){
        $this -> __construct__();
    }
}

Now lets try to use them:

$MODS_ENABLED = array();
$MODS_ENABLED[] = new mod_mail();
$myObj = new main_class(...);
$myObj -> mail("me@me.me","you@you.you","subject","message/body","Extra:Headers;More:Headers");
# Hey look, my mail class was just added into my main_class for later use.

Note:

I am currently using the first method (I only have one class, the mods are exceptions) in my own CMS that I have made from scratch (http://sitegen.com.au), and it works great, my reason on needing this is because I have my main_class that is getting generated after I have required all mods in ./mods-enabled/* creating functions and changing how other functions work, I will also come back here another time with a solution for two mods to both change a function without one winning as it ran first. I have split my plugins in two, mods that run on every site, and plugins that have settings for a site and may not even be enabled.
Have fun programming.

JamesM-SiteGen
  • 802
  • 2
  • 11
  • 26
0

If it is not enough magic for you, you can use dynamic objects. The common idea is here: https://github.com/ptrofimov/jslikeobject

0

You can extend the class

class foo {
  public function a() {
     return 'b';
  }
}

class woo extends foo {
  public function newStuff() {
     $var = $this->a();
     echo $var;
  }
}

By extending foo from the woo class the functionality in foo is usable while you can also create new methods in woo. That's the easiest way to add new functionality to a class.

Sondre
  • 1,878
  • 18
  • 33
  • 1
    But then I'd have to use the class woo in my class and not foo, correct? – Savetheinternet Jan 12 '11 at 12:52
  • Ye you would have to go through woo instead of foo to use the functionality you've added. Basicly you keep the foo class unchanged, but add different functionality to it by extending it from different other classes. A little hard to give exact examples that might be useful for you without knowing more about what you want your code to do – Sondre Jan 12 '11 at 13:52
0

You can use magic functionality of PHP to provide actions on methods that are not defined at compile time.

Dennis Kreminsky
  • 2,117
  • 15
  • 23
0

It is actually possible. For instance:

<?php
class Test {

  function set($set, $val) {
    $this->$set = $val;
  }

  function get($get) {
    return $this->$get;
  } 
}

$t = new Test();
$t->set('hello', 'world');
echo $t->get('hello');
exit; 
?>
user268396
  • 11,576
  • 2
  • 31
  • 26