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
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.
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.