1

I am not sure how to name this, but here it goes. Lets suppose i have the following

class A {
    public function aa() {
       $this->bb();
    }
    public function bb() {

    }
}

class B extends a {

}

class C {
   __construct(B $service) {
       $this->service = $service;
   }
   public function aa() {

       $this->service->aa();
   }
}

My call in code will be

$C = new C(new B());
$C->aa();

So this will basically execute A:aa() which is what i want. As you can see, in A::aa() AA::bb() is called.

What I need. When AA::bb() is called i want to execute some code defined in class C, but I am not allowed to change the A class. I can only change the B class or the C class.

My idea was to add a listener in the B class and overwrite the bb() function like this

class B extends a {
    public $listener;
    bb() {
       parent::bb();
       $this->listener();
    }
}

class C {
   __construct(B $service) {
       $this->service = $service;
   }
   public function aa() {

       $this->service->listener = function() { }
       $this->service->aa();
   }
}

But I don't like this idea a lot, doesn't look like a good one. What are my options here?

Again, I CANNOT change the A class and i can only call the C class.

PHP version is 5.3

1
  • Since you cannot keep listener logic directly in class B, there are not too much options. I would move $this->service->listener = function() { } to the constructor tho. Why you don't like it? Commented Sep 20, 2017 at 13:41

2 Answers 2

1

You have two options. Extend or decorate.

First one would be kinda what you have already written, though, I would not use public visibility for the listener:

class Foo extends A {
    private $listener;
    public function setListener(callable $func) {
        $this->listener = $func;
    }
    public function bb() {
        call_user_func($this->listener);
        return parent:bb();
    }
}

In the example I passed the listener via setter injection, but you can also use constructor injection and pass the $listened in the overloaded __construct() method. When you extend a class, the "interface restriction" does not aply to the constructor's signature.


The other approach is to use a decorator:

class Foo {
    private $target;
    public function __construct(A $target) {
        $this->target = $target;
    }

    public function bb($callback) {
        $callback();
        return $this->target->bb();
    }

    public function __call($method, $arguments) {
        return call_user_func_array( 
                array( $this->target, $method ),
                $arguments
            );
    }
}

The second approach would let you alter the interface.

Which option you pick depend on the exact functionality you actually need to implement. The decorator is a solution for, when you need drastic change in the objects behavior - for example, it is really good for adding access control.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your answer. What i omitted from the question is that the code that has to be executed needs to be inside the C class. In your example, Foo is the B class or the C class?
it's the B class .. you vague class/method don't really help
1

I understand that you want to execute code in C after code in A completes. You cannot change A.

As written, C::aa calls A::aa, which calls A::bb and the stack unwinds. Why not just do the work in C::aa after the service call finishes?

class C {
   public function aa() {
       $this->service->aa();
       // whatever you want to do
   }
}

If, on the other hand, you need to call code after A::aa is called but before A::bb is called then the example you posted would suffice with clarity:

class B extends a {
    public $listener;
    public function bb() {
       call_user_func($this->listener);
       parent::bb();
    }
}

Note the use of call_user_func, which is necessary for PHP 5.3 to call an anonymous function stored in a member variable.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.