PHP equivalent for a python decorator?

Question:

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()
Asked By: Fragsworth

||

Answers:

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?

Answered By: knittl

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.

Answered By: Charles

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.

Answered By: Zed

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.

Answered By: Devin McBeth

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() {

}
Answered By: Ali Amjid

Here is my implementation of python decorators by using php attributes

use SagittaraccPhpPythonDecoratorDecorator;

class Calc
{
    use Decorator;

    #[Timer]
    function sum($a, $b)
    {
        sleep(1);
        return $a + $b;
    }
}
#[Attribute]
class Timer
{
    public function main($func, ...$args)
    {
        $time_start = microtime(true);

        $result = $func($args);

        $time_end = microtime(true);

        return sprintf(
            "Total execution: %f; Result: %d",
            $time_end - $time_start,
            $result
        );
    }
}

You can check it out here – https://github.com/sagittaracc/php-python-decorator

Answered By: sagittaracc