12

Working with a PHP library class, and I'd like to wrap all of its public functions in a subclass... Something along the lines of:

class BaseClass
{
   function do_something()
   {
        some;
        stuff;
   }

   function do_something_else()
   {
        other;
        stuff;
   }

   /*
    * 20-or-so other functions here!
    */
}

class SubClass extends BaseClass
{
   function magicalOverrideEveryone()
   {
        stuff-to-do-before;        // i.e. Display header
        call_original_function();  // i.e. Display otherwise-undecorated content
        stuff-to-do-after;         // i.e. Display footer
   }
}

Boiling it down, I'd prefer not to have to override every superclass method with the same wrapper code, if there's a [somewhat elegant / clean] way to do it all in one place.

Is this possible? I suspect I'm in metaprogramming land here, and don't even know if PHP offers such a beast, but figured I'd ask...

lisachenko
  • 5,952
  • 3
  • 31
  • 51
mtbkrdave
  • 2,900
  • 3
  • 23
  • 24
  • To clarify - There are only some methods from `BaseClass` that you want to override in `SubClass`, not all of them? – Sean Walsh Mar 01 '12 at 22:35
  • @s992, not exactly - I was looking for a way to apply the same wrapper code to every method in the base class. Looks like PHP's __call magic method will do the job with a proxy wrapper, thanks @meagar! – mtbkrdave Mar 01 '12 at 22:42
  • Gotcha, the question wasn't exactly clear. If meagar's answer helped you out, you should accept it. :) – Sean Walsh Mar 01 '12 at 22:44
  • What you want is called AOP. There are some realizations of AOP in PHP: PHP-AOP extension, Go! AOP library, TYPO3 Flow AOP. – lisachenko Nov 24 '12 at 19:40

4 Answers4

25

You could do this easily with the __call magic method and a generic "proxy" class which doesn't inherit directly from the base class.

Here is a (near) complete implementation of a proxying class which wraps whatever object you pass it. It will invoke some "before" and "after" code around each method call.

class MyProxy {
  function __construct($object) {
    $this->object = $object;
  }

  function __call($method, $args) {
    // Run before code here

    // Invoke original method on our proxied object
    call_user_func_array(array($this->object, $method), $args);

    // Run after code here
  }
}


$base = new BaseClass();
$proxy = new MyProxy($base);

$proxy->doSomething(); // invoke $base->doSomething();

You would of course want to add a bit of error handling, like asking the proxied object if it responds to the given method in __call and raising an error if it doesn't. You could even design the Proxy class to be a base-class for other proxies. The child proxy classes could implement before and after methods.

The downside is that your "child class" no longer implements BaseClass, meaning if you're using type-hinting and want to demand that only objects of type BaseClass are passed into a function, this approach will fail.

user229044
  • 232,980
  • 40
  • 330
  • 338
  • 1
    One way to circumvent the type issue is to look at how PHP version of Mockito, Phockito, generates Mock classes. https://github.com/hafriedlander/phockito/blob/master/Phockito.php#L170 – Fredrik Wendt Dec 18 '12 at 09:45
  • what about if a method has reference parameter? "public function (&$output) { }" – John Smith Jun 21 '18 at 09:57
2

If the method names of SubClass may differ slightly from the original method names of BaseClass, you could write a generic wrapper with __call(). If the method names must match, I don't see how you could achieve your goal without manually overwriting each method. Maybe you could use the funcall PECL to do this - but you'd have to be able to load that PECL in the first place.

If you can make the methods of BaseClass protected, the __call() approach in SubClass will work.

If you do not need to extend the class, @meager's approach is perfectly fine. Please note that __call() and call_user_func_array() do impose a certain overhead.

rodneyrehm
  • 13,442
  • 1
  • 40
  • 56
  • This is an important thing to point out - thanks. I do not (think I) need to extend the class, so I anticipate the proxy approach will work. (Will depend on how CodeIgniter routing is done under the hood) – mtbkrdave Mar 03 '12 at 12:45
0

you might be looking for the decorator pattern:

class A
{
   function do1(){

   }
}
class DecoratorForA extends A
{
   private A original;
   public DecoratorForA( A original )
   {
      this.original = original;
   }
   function do1(){  //<-- override keyword in php?
      stuffBefore(); 
      original->do1();
      stuffAfter();      
   }
}

since this is not what you want, maybe this link is of help? http://code.google.com/p/php-aop/

mindandmedia
  • 6,800
  • 1
  • 24
  • 33
  • oh, now i read it correctly, guess you are NOT looking for that. – mindandmedia Mar 01 '12 at 22:35
  • He's trying to find a simple way to generally apply the decorator pattern *without* writing a magnitude of boilerplate code. You just gave his needs a name, not a solution… – rodneyrehm Mar 01 '12 at 22:37
  • Correct, I was looking for a metaprogramming application of the decorator pattern... Thank you for your input, though! – mtbkrdave Mar 01 '12 at 22:40
0

If I understand you right, you want to extend a class, but not allow any of methods from the parent to be called. Instead, you want to call all of the methods yourself in one method in the new class.

So why do you even want to inherit in the first place? It sounds a lot like you should just create an adapter/decorator for your BaseClass.

class SubClass
{
   function magicalOverrideEveryone()
   {
      BaseClass bc = new BaseClass();
      bc.do_something();
      bc.do_something_else();
   }
}
kba
  • 19,333
  • 5
  • 62
  • 89