Method filtering is an alternative to event-driven architectures. It provide a way to inject/override some logic in the program flow without polluting too much the original source code.
There's a couple of different existing approches which try to bring the AOP concepts in PHP.
However I still think that the lihtium implementation is simpler to implements and the overhead still minimal when AOP is required for a couple of methods in a projet.
Like the lithium's version this implementation also require some boilerplate code to make filterable methods. Event if there's some slighly differences in the API the logic is roughly the same.
So let's take a simple example:
class Home {
public static function version() {
return '1.0.0';
}
public function enter($name) {
return "Welcome {$name}!";
}
}
With such class you should be able to run the following:
echo "You are using the Home " . Home::version();
$home = new Home();
echo $home->enter('Bob');
And it will produce:
You are using the Home 1.0.0
Welcome Bob
Ok, now let's make methods filterable first:
namespace city;
use filter\Filter;
use filter\behavior\Filterable;
class Home {
use Filterable; // Only required for `enter` (i.e. instance methods)
public static function version() {
Filter::on(get_called_class(), __FUNCTION__, func_get_args(), function($chain) {
return '1.0.0'; // Your inchanged code here
});
}
public function enter($name) {
Filter::on($this, __FUNCTION__, func_get_args(), function($chain, $name) {
return "Welcome {$name}!"; // Your inchanged code here
});
}
}
So we end up doing a simple wrapping of the core implementation using a closure. Notice the use of get_called_class()
or $this
depends if you are in a static class or not. Don't forget the add all method parameters just after the mandatory $chain
parameter in the closure definition. $chain
will always be the first parameter and represents the chain of filters to apply.
Once the code rewrited, we are now ready to attach some filters to change the default behavior of methods.
Filter::register('city.version', function($chain) { // Registering an aspect.
$version = $chain->next();
return "Version: {$version}";
});
Filter::register('city.civility', function($chain, $name) { // Registering another aspect.
$name = "Mister {$name}";
return $chain->next($name);
});
Filter::apply('city\Home', 'version' 'city.version'); // Applying `'city.version'` to the static method.
$home = new Home();
Filter::apply($home, 'enter', 'city.civility'); // Applying `'city.civility'` the the intance method.
echo "You are using the Home " . Home::version();
echo $home->enter('Bob');
And it will produce:
You are using the Home Version 1.0.0
Welcome Mister Bob
-
Is it possible to apply a filter for all instances ? Yes, for such behavior, you need to set your filter using the class name string as context.
-
If sub class inherit from filters setted at a parent class level ? Yes.
On a static method:
class StaticClass {
public static function($param1, $param2) {
Filter::on(get_called_class(), __FUNCTION__, func_get_args(), function($chain, $param1, $param2) {
// Method's logic here
});
}
}
On a dynamic method:
use filter\behavior\Filterable;
class DynamicClass {
use Filterable;
public function($param1, $param2) {
Filter::on($this, __FUNCTION__, func_get_args(), function($chain, $param1, $param2) {
// Method's logic here
});
}
}
Filter::register('an_aspect_name', function($chain, $param1, $param2, ...) {
// Method's logic here
return $chain->next($param1, $param2, ...); // Process the chain if needed
});
Filter::apply($context, 'method_name', 'an_aspect_name');
Detach all filters associated to a method:
Filter::detach($context, 'method_name');
Detach a named filter only:
Filter::detach($context, 'method_name', 'an_aspect_name');
Detach a named filters only but for all class's method:
Filter::detach($context, null, 'an_aspect_name');
Filter::registred('an_aspect_name');
Filter::unregister('an_aspect_name');
Getter:
$registered = Filter::registered();
$filters = Filter::filters();
Setter:
Filter::register($registered);
Filter::filters($filters);
Filter::reset();
Note: It also detaches all filters attached statically (i.e it doesn't affect filters on intance's methods).
Filter::enable(); // Enable
Filter::enable(false); // Disable
Note: It doesn't detach any filters