Class method pre-invocation hooking - php

Is it possible in PHP 5.X to have a method in a class that is executed whenever a classes method is called, prior to the called function? I need this because i want to do some dynamic validation for the parameters used in the called function.
class MyClass {
protected function preHook(){ echo "You are about to call a method."; }
public function methodA() { echo "You called methodA."; }
}
$obj = new MyClass();
$obj->methodA();
// Output: "You called methodA."
// Desired output: "You are about to call a method.You called methodA"
Also keep in mind the following:
The "methodA" needs to be public because reflection is used in the code in order to detect the method.

As mentioned in comment this is impossible as __call magic method gets called only if method with given name is not defined:
http://php.net/manual/en/language.oop5.magic.php
However, maybe one of the following hackish solutions will solve your issue.
SOLUTION 1
will require changing all your methods names:
class MyClass {
public function __call($name, $arguments){
echo "You are about to call $name method.";
return call_user_func_array(array($this, '_real_' . $name), $arguments);
}
private function _real_methodA() { echo "You called methodA."; }
}
$obj = new MyClass();
$obj->methodA();
SOLUTION 2
this will require a 'wrapper' classes:
class MyClass {
public function methodA() { echo "You called methodA."; }
}
class MyClassWrapper {
public function __construct(){
$this->myClass = new MyClass();
}
public function __call($name, $arguments){
echo "You are about to call $name method.";
return call_user_func_array(array($this->myClass, $name), $arguments);
}
}
$obj = new MyClassWrapper();
$obj->methodA();
SOLUTION 3
Third approach would be to apply decorator pattern and create one wrapper class.
class Decorator
{
protected $_instance;
public function __construct($instance)
{
$this->_instance = $instance;
}
public function __call($method, $args)
{
print 'do your stuff here';
return call_user_func_array(array($this->_instance, $method), $args);
}
}
$obj = new Decorator(new MyClass);
$obj->methodA();
SOLUTION 4
mix of solution 1 and use reflection and "runkit_method_rename" to rename all methods
http://docs.php.net/manual/en/function.runkit-method-rename.php
runkit is experimental so this is rather hardcore.
class MyClass {
public function __call($name, $arguments){
echo "You are about to call $name method.";
return call_user_func_array(array($this, '_real_' . $name), $arguments);
}
private function methodA() { echo "You called methodA."; }
}
$reflection = new ReflectionClass('MyClass');
$methods = $reflection->getMethods();
foreach ($methods as $method) {
runkit_method_rename('MyClass', $method->name , '_real_' . $method->name);
}
$obj = new MyClass();
$obj->methodA();

In the end i modified the dispatcher to call the prehook (using __call in order to keep it hidden from reflection but still be public) before the actual method is called.

Related

How to handle __invoke() method does not exist in PHP?

The magical __call() and __callStatic can pretty much handle any non existing method on the class, but is there a way to handle a non existing magical method on a class?!
Here's an example on why I need this:
I have a class called DoSomething:
class DoSomething{
public function ok(){
echo 'Something!';
}
}
I want to call this class as a function for a reason! which should call the __invoke function of that class:
$doSomething = new DoSomething();
$doSomething();
Normally by doing that, the class should look for the __invoke function, however in my case I don't to have that function declared on my class (DoSomething), instead I want to be able to call another function (such as the ok()) if the __invoke doesn't exist.
I was expecting something like this to work, but of course it didn't :)
public function __call($class, $arguments)
{
$object = IoC::resolve($class);
$object->ok(...$arguments);
}
The main goal is to use the class as a function, without having to declare the __invoke method. Handle the function does not exist error and call another function instead.
I think that would be really cool :D I appreciate suggestions or other solutions to achieve this.
Internal solution
Extract an abstract class
You could extract an abstract class and have your classes extend it:
<?php
abstract class Invokable
{
public function __invoke()
{
return $this->ok();
}
abstract public function ok();
}
class DoSomething extends Invokable
{
public function ok()
{
echo 'Something';
}
}
$doSomething = new DoSomething();
echo $doSomething();
For an example, see:
https://3v4l.org/m0ih8
Extract a trait
You could extract a trait and have your classes use it:
<?php
trait InvokableTrait
{
public function __invoke()
{
return $this->ok();
}
}
class DoSomething
{
use InvokableTrait;
public function ok()
{
echo 'Something';
}
}
$doSomething = new DoSomething();
echo $doSomething();
For an example, see:
https://3v4l.org/ftUfI
External Solution
Create a proxy
You could create a proxy (a decorator) that composes the object that is not invokable:
<?php
class InvokableDecorator
{
private $decorated;
public function __construct($decorated)
{
$this->decorated = $decorated;
}
public function __call($name, $arguments)
{
/**
* delegate to decorated object if the method exists
*/
if (method_exists($this->decorated, $name)) {
return $this->decorated->{$name}($arguments);
}
}
public function __invoke()
{
return $this->decorated->ok();
}
}
class DoSomething
{
public function ok()
{
echo 'Something';
}
}
$doSomething = new InvokableDecorator(new DoSomething());
echo $doSomething();
For an example, see:
https://3v4l.org/C3XEX
Create a handler
You could create a handler that takes care of determining this externally:
<?php
class Handler
{
public function handle($subject)
{
if (is_callable($subject)) {
return $subject();
}
if (method_exists($subject, 'ok')) {
return $subject->ok();
}
throw new \BadMethodCallException(sprintf(
'Unable to handle instance of "%s"',
get_class($subject)
));
}
}
class DoSomething
{
public function ok()
{
echo 'Something';
}
}
$handler = new Handler();
echo $handler->handle(new DoSomething());
For an example, see:
https://3v4l.org/E0NVs

call a function before method call

Design question / PHP:
I have a class with methods.
I would like to call to an external function anytime when any of the methods within the class is called.
I would like to make it generic so anytime I add another method, the flow works with this method too.
Simplified example:
<?php
function foo()
{
return true;
}
class ABC {
public function a()
{
echo 'a';
}
public function b()
{
echo 'b';
}
}
?>
I need to call to foo() before a() or b() anytime are called.
How can I achieve this?
Protect your methods so they're not directly accessible from outside the class, then use the magic __call() method to control access to them, and execute them after calling your foo()
function foo()
{
echo 'In pre-execute hook', PHP_EOL;
return true;
}
class ABC {
private function a()
{
echo 'a', PHP_EOL;
}
private function b($myarg)
{
echo $myarg, ' b', PHP_EOL;
}
public function __call($method, $args) {
if(!method_exists($this, $method)) {
throw new Exception("Method doesn't exist");
}
call_user_func('foo');
call_user_func_array([$this, $method], $args);
}
}
$test = new ABC();
$test->a();
$test->b('Hello');
$test->c();

Automatically decorate every method in a class

I would like to add some logic before and after every time I'm using a method (doesn't really matter if is private, protected or public) in a class.
For example:
class Service
{
function test1() {
Log:start(__METHOD__);
someLogicInThere(); ....
Log:end(__METHOD__);
}
function test2() {
Log:start(__METHOD__);
someLogicInThere(); ....
Log:end(__METHOD__);
}
...
}
My idea is to finally have something like this:
/**
* #LogDecorate
*/
class Service
{
function test1() {
someLogicInThere();
}
function test2() {
someLogicInThere();
}
...
}
Use annotations is not important. There is any way to do that?
As your question-title already suggests, you could use the Decorator Pattern for this. I'm not quite sure though if a full-stack Decorator pattern is necessary here. If it's a really simple use-case, something like this could suffice.
What you could do, is extend the class and 'route' all calls to the extended class. Then add some logic before and after, and call the parent method in between. Something like this:
class Service {
function method1() {
doSomeFunkyStuff();
}
function method2() {
doSomeOtherFunkyStuff();
}
}
class DecoratedService extends Service {
function method1() {
Log::start(__METHOD__);
parent::method1();
Log::end(__METHOD__);
}
function method2() {
Log::start(__METHOD__);
parent::method2();
Log::end(__METHOD__);
}
}
$service = new DecoratedService();
$service->method1();
$service->method2();
Now you can either choose to use the original Service or use the DecoratedService. The funcionality is the same, and the DecoratedService will not have to be altered if the Service changes, assuming the method names won't change (which would actually be a bad thing).
But also look to the wiki page (or any other resource) to understand fully what's the intent of the Decorator Pattern. This (above) might not be the ideal solution for your problem.
EDIT a bit more automatic as requested, sir.
As you cannot change the visibility of methods, using the magic __call() doesn't work (as public or protected parent methods will be accessible from the child as well). But, what you can do, is create your own call method!
class DecoratedService extends Service {
function call($method) {
if(!method_exists(parent, $method)) {
return false; // OR:
throw Exception;
// OR handle this case some other way
}
Log::start(array(parent, $method));
call_user_func(array(parent, $method));
Log::end(array(parent, $method));
}
}
$service = new DecoratedService;
$service->call('method1');
I guess, it is a typicall case of Smart Reference Pattern (some mix of Proxy and Decorator Patterns).
class A {
function test1() {
echo 'TEST 1', PHP_EOL;
}
function test2() {
echo 'TEST 1', PHP_EOL;
}
}
class ProxyA {
protected $wrapped;
public function __construct($wrapped) {
$this->wrapped = $wrapped;
}
public function __call($name, $args) {
echo 'Log:start';
$this->wrapped->$name($args);
echo 'Log:end';
}
}
$proxy = new ProxyA(new A());
$proxy->test1();
But it works only for public methods.
Mixing Smart Reference with DecoratedService::call() method from #giorgio and #yceruto answer can cover all method, or just implement __call() twice:
class A {
public function test1() {
echo 'TEST 1', PHP_EOL;
}
private function test2() {
echo 'TEST 2', PHP_EOL;
}
public function __call($name, $args) {
if (method_exists($this, $name)) {
$this->$name($args);
}
}
}
class ProxyA {
protected $wrapped;
public function __construct($wrapped) {
$this->wrapped = $wrapped;
}
public function __call($name, $args) {
if (method_exists($this->wrapped, $name)) {
echo 'Log:start';
$this->wrapped->$name($args);
echo 'Log:end';
}
}
}
$proxy = new ProxyA(new A());
$proxy->test0(); // Nothing to do
$proxy->test1(); // Done
$proxy->test2(); // Done
Use of the magic __call method might make this easy for you:
class Service
{
public function test1() {
echo 'TEST 1', PHP_EOL;
}
protected function test2() {
echo 'TEST 2', PHP_EOL;
}
public function __call($method, $args) {
echo 'Some stuff before', PHP_EOL;
$returnValue = $this->$method($args);
echo 'Some stuff after', PHP_EOL;
return $returnValue;
}
}
$x = new Service();
$x->test2();
$x->test1();
Note that if the method is accessible from outside the class (like test1) then __call() won't be invoked; it only executes if the methods involved are protected or private; and doesn't trigger if they are invoked internally within the object either

Mixins, call_to_user_func not finding the function

So I have a class which is designed to "mix" other classes in, via what i call a "bridge" class. So you have your sample classes for example:
class A{
public function __construct(){}
public function hello_a(){ echo "hello A"; }
}
class B{
public function __construct(){}
public function hello_b(){ echo "hello B"; }
}
You might also have a single class called C - which needs to inherit from both A and B, but since PHP doesn't have multiple inheritance we have the following:
class C extends Bridge{
public function __construct(){
parent::__construct();
}
public function hello_C(){
$this->hello_a(); // Freaks out*
}
}
class Bridge extends AisisCore_Loader_Mixins{
public function construct(){
parent::construct();
$this->setup(array(
'A' => array(),
'B' => array()
));
}
}
And now finally we have our mix-in class which allows all of this to work. Note: this code assumes you have a auto loader using the pear naming standards to load classes for you.
class AisisCore_Loader_Mixins {
private $_classes;
private $_class_objects = array();
private $_methods = array();
public function __construct(){
$this->init();
}
public function init(){}
public function setup($class){
if(!is_array($class)){
throw new AisisCore_Loader_LoaderException('Object passed in must be of type $class_name=>$params.');
}
$this->_classes = $class;
$this->get_class_objects();
$this->get_methods();
}
public function get_class_objects(){
foreach($this->_classes as $class_name=>$params){
$object = new ReflectionClass($class_name);
$this->_class_objects[] = $object->newInstanceArgs($params);
}
}
public function get_methods(){
foreach($this->_class_objects as $class_object){
$this->_methods[] = get_class_methods($class_object);
}
return $this->_methods;
}
public function __call($name, $param = null){
foreach($this->_methods as $key=>$methods){
foreach($methods as $method){
if($name === $method){
return $this->isParam($method, $param);
}
}
}
throw new AisisCore_Loader_LoaderException("Method: " .$name.
" does not exist or it's access is not public");
}
private function isParam($method, $param){
if($param != null){
call_user_func($method, $param);
}else{
call_user_func($method);
}
}
}
You can see in class C how the class above is used, we simply just call hello_a. All is well up to this point until it tries call_user_func() and freaks out saying:
Warning: call_user_func() expects parameter 1 to be a valid callback, function 'hello_a' not found or invalid function name
Is there a particular reason it cannot find this? the classes are loaded, the methods are stored in the array, it obviously found the method in the array of methods, the method is public. whats going on?
Your call to call_user_func is passing just the method name, so it's looking for a global function. You must pass your class name or instance:
$a = new A(); // Or however you plan to get your instance of A
call_user_func(array($a, $method));

how can magic methods overide inheritance

Given class A that extends class B, how can I have calls to class A's __call function override the matching function inherited from the parent?
Consider this simplified example:
class A
{
public function method_one()
{
echo "Method one!\n";
}
}
class B extends A
{
public function __call($name, $args)
{
echo "You called $name!\n";
}
}
$b = new B();
$b->method_one();
When I run it, I get the output Method one!. I WANT to get the output You called method_one!.
So, how do I have the subclass's magic method override the parent classes defined method?
I need to extend the object, because I need access to a protected method in A, but I want to channel all public methods into my own __call handler. Is there any way to do this?
So, I found a way to do it, which involves making an intermediate class to expose the protected method I need, while still using __call for the public ones. This works, but I really don't like the idea of extending a class just to expose a protected method... Still, someone might find it useful, so thought I'd share:
class A
{
public function method_one()
{
echo "Method one!\n";
}
protected function protected_method()
{
echo "Accessible!\n";
}
}
class A_accessor extends A
{
public function publicise()
{
$args = func_get_args();
return call_user_func_array(array($this, array_shift($args)), $args);
}
}
class B
{
private $A;
public function __construct()
{
$this->A = new A_accessor();
}
public function __call($name, $args)
{
echo "You called $name!\n";
}
public function protected_method()
{
return $this->A->publicise('protected_method');
}
}
$b = new B();
$b->method_one();
$b->protected_method();
This is the answer I actually used, based on Mark Baker's comment.
By having an object of the class whose methods I want access to as a variable, I can use ReflectionMethod to access any of its methods just as if I was extending it, but with __call still catching everything else. So any methods I want to pass through, I can pass through with something like this:
public function __call($name, $args)
{
$method = new ReflectionMethod($this->A, $name);
$method->setAccessible(true);
return $method->invokeArgs($this->A, $args);
}
Or in my case, with the full class like this:
class B
{
private $A;
public function __construct()
{
$this->A = new A();
}
public function __call($name, $args)
{
echo "You called $name!\n";
}
public function protected_method()
{
$method = new ReflectionMethod($this->A, 'protected_method');
$method->setAccessible(true);
return $method->invoke($this->A, $args);
}
}
try this
class A1
{
protected function method_one()
{
echo "Method one!\n";
}
}
class B1
{
private $A;
public function __construct()
{
$this->A = new A1;
}
public function __call($name, $args)
{
$class = new ReflectionClass($this->A);
$method = $class->getMethod($name);
$method->setAccessible(true);
//return $method;
echo "You called $name!\n";
$Output=$method->invokeArgs($this->A, $args);
$method->setAccessible(false);
return $Output;
}
}
$a = new B1;
$a->method_one("");

Categories