PHP equivalent for a python decorator?

13,652

Solution 1

Apparently runkit might help you.

Also, you can always do this the OO way. Put the original fun in a class, and the decorator into an extended class. Instantiate and go.

Solution 2

Here is my method of mimicking decorators from python in php.

function call_decorator ($decorator, $function, $args, $kwargs) {

    // Call the decorator and pass the function to it
    $decorator($function, $args, $kwargs);
}

function testing ($args, $kwargs) {
    echo PHP_EOL . 'test 1234' . PHP_EOL;
}

function wrap_testing ($func, $args, $kwargs) {

    // Before call on passed function
    echo 'Before testing';

    // Call the passed function
    $func($args, $kwargs);

    // After call on passed function
    echo 'After testing';
}

// Run test
call_decorator('wrap_testing', 'testing');

Output:

Before testing
testing 1234
After testing

With this implementation you can also do something like this with an anonymous function:

// Run new test
call_decorator('wrap_testing', function($args, $kwargs) {
    echo PHP_EOL . 'Hello!' . PHP_EOL;
});

Output:

Before testing
Hello!
After testing

And lastly you can even do something like this, if you are so inclined.

// Run test
call_decorator(function ($func, $args, $kwargs) {
    echo 'Hello ';
    $func($args, $kwargs);
}, function($args, $kwargs) {
    echo 'World!';
});

Output:

Hello World!

With this construction above, you can pass variables to the inner function or wrapper, if need be. Here is that implementation with an anonymous inner function:

$test_val = 'I am accessible!';

call_decorator('wrap_testing', function($args, $kwargs){
    echo $args[0];
}, array($test_val));

It will work exactly the same without an anonymous function:

function test ($args, $kwargs) {
    echo $kwargs['test'];
}

$test_var = 'Hello again!';

call_decorator('wrap_testing', 'test', array(), array('test' => $test_var));

Lastly, if you need to modify the variable inside either the wrapper or the wrappie, you just need to pass the variable by reference.

Without reference:

$test_var = 'testing this';
call_decorator(function($func, $args, $kwargs) {
    $func($args, $kwargs);
}, function($args, $kwargs) {
    $args[0] = 'I changed!';
}, array($test_var));

Output:

testing this

With reference:

$test_var = 'testing this';
call_decorator(function($func, $args, $kwargs) {
    $func($args, $kwargs);
}, function($args, $kwargs) {
    $args[0] = 'I changed!';

// Reference the variable here
}, array(&$test_var));

Output:

I changed!

That is all I have for now, it is a pretty useful in a lot of cases, and you can even wrap them multiple times if you want to.

Solution 3

maybe you’re looking for call_user_func_array:

function wrapA() {
  $args = func_get_args();
  return call_user_func_array('A', $args);
}

since PHP 5.3 you could even say:

return call_user_func_array('A', func_get_args());

after you’ve edited your question i would say, no, this is not possible, but there are some ways, see this question: how to implement a decorator in PHP?

Solution 4

You can use Aspect Oriented Programming. But you need some framework that support AOP.

For example Symfony. This is one of implementations of AOP for php https://github.com/goaop/goaop-symfony-bundle

There is also the Nette Framework (I use this one)

In principle it works like this: let's say you want to throw an exception if user is not logged in and tries to access some method.

This is just "fictive" code to show how it works.

You have some aspect method like this:

/** @AroundMethod(annotataion="isUserLogged()") */
public function checkUser(Method $method) {
    if ($this->user->isLogged()) {
        return $method->call();
    } else {
        throw new \Exception('User must be logged');
    }
}

And then you can use the annotation @isUserLogged in your services which will be probably registered in some DI container.

/**
 * @isUserLogged()
 */
public function changeUserInfo() {

}

Solution 5

You can't do this with functions in PHP. In other dynamic languages, such as Perl and Ruby, you can redefine previously defined functions, but PHP throws a fatal error when you attempt to do so.

In 5.3, you can create an anonymous function and store it in a variable:

<?php
    $my_function = function($args, ...) { ... };
    $copy_of_my_function = $my_function;
    $my_function = function($arg, ...) { /* Do something with the copy */ };
?>

Alternatively, you can use the traditional decorator pattern and/or a factory and work with classes instead.

Share:
13,652
Fragsworth
Author by

Fragsworth

Developer of Clicker Heroes, Cloudstone, and other games http://www.clickerheroes.com/ http://www.kongregate.com/games/nexoncls/cloudstone http://armorgames.com/cloudstone-game/15364

Updated on June 03, 2022

Comments

  • Fragsworth
    Fragsworth about 2 years

    I want to be able to wrap a PHP function by another function, but leaving its original name/parameter list intact.

    For instance:

    function A() {
        print "inside A()\n";
    }
    
    function Wrap_A() {
        print "Calling A()\n";
        A();
        print "Finished calling A()\n";
    }
    
    // <--- Do some magic here (effectively "A = Wrap_A")
    
    A();
    

    Output:

    Calling A()
    inside A()
    Finished calling A()