How to use class methods as callbacks

96,616

Solution 1

Check the callable manual to see all the different ways to pass a function as a callback. I copied that manual here and added some examples of each approach based on your scenario.

Callable


  • A PHP function is passed by its name as a string. Any built-in or user-defined function can be used, except language constructs such as: array(), echo, empty(), eval(), exit(), isset(), list(), print or unset().
  // Not applicable in your scenario
  $this->processSomething('some_global_php_function');

  • A method of an instantiated object is passed as an array containing an object at index 0 and the method name at index 1.
  // Only from inside the same class
  $this->processSomething([$this, 'myCallback']);
  $this->processSomething([$this, 'myStaticCallback']);
  // From either inside or outside the same class
  $myObject->processSomething([new MyClass(), 'myCallback']);
  $myObject->processSomething([new MyClass(), 'myStaticCallback']);

  • Static class methods can also be passed without instantiating an object of that class by passing the class name instead of an object at index 0.
  // Only from inside the same class
  $this->processSomething([__CLASS__, 'myStaticCallback']);
  // From either inside or outside the same class
  $myObject->processSomething(['\Namespace\MyClass', 'myStaticCallback']);
  $myObject->processSomething(['\Namespace\MyClass::myStaticCallback']); // PHP 5.2.3+
  $myObject->processSomething([MyClass::class, 'myStaticCallback']); // PHP 5.5.0+

  • Apart from common user-defined function, anonymous functions can also be passed to a callback parameter.
  // Not applicable in your scenario unless you modify the structure
  $this->processSomething(function() {
      // process something directly here...
  });

Solution 2

Since 5.3 there is a more elegant way you can write it, I'm still trying to find out if it can be reduced more

$this->processSomething(function() {
    $this->myCallback();
});

Solution 3

You can also to use call_user_func() to specify a callback:

public function myMethod() {

    call_user_func(array($this, 'myCallback'));
}

private function myCallback() {
    // do something...
}

Solution 4

As of PHP 8.1, we now have first-class callables. They use the syntax $callable = functionName(...).

You can use the new syntax to create callable class methods.

Class MyClass {
    
    public function myMethod() {
        // first-class callables
        $this->processSomething($this->myCallback(...));
        $this->processSomething(self::myStaticCallback(...));
    }

    private function processSomething(callable $callback) {
        // Process something...
        $callback();
    }

    private function myCallback() {
        // Do something...
    }

    private static function myStaticCallback() {
        // Do something...
    }   
    
}
Share:
96,616

Related videos on Youtube

SmxCde
Author by

SmxCde

Updated on March 05, 2022

Comments

  • SmxCde
    SmxCde over 2 years

    I have a class with methods that I want to use as callbacks.
    How can I pass them as arguments?

    Class MyClass {
        
        public function myMethod() {
            // How should these be called?
            $this->processSomething(this->myCallback);
            $this->processSomething(self::myStaticCallback);
        }
    
        private function processSomething(callable $callback) {
            // Process something...
            $callback();
        }
    
        private function myCallback() {
            // Do something...
        }
    
        private static function myStaticCallback() {
            // Do something...
        }   
        
    }
    
  • MikO
    MikO over 9 years
    This may be more elegant, but you're adding overhead by adding an unnecesary call to the anonymous function, which just calls the actual callback...And what if myCallback needs 5 arguments? You have to write those arguments 4 times in that class! I agree anonymous functions are nice as callbacks, but in the case you write the code inside the anonymous function, not just including a call to another function...
  • Bankzilla
    Bankzilla over 9 years
    You're correct, I didn't think about passing the arguments across. But unless I'm reading it wrong, the way you've specified you also can't pass arguements.
  • SmxCde
    SmxCde over 9 years
    MikO, thanks for your response! I know that this wasn't in original question, but how to call for static method with static method as argument from static method (where there is no $this)?
  • SmxCde
    SmxCde over 9 years
    So far, the best solution I could come up with is self::processSomething([__CLASS__, 'callback']);
  • MikO
    MikO over 9 years
    @SmxCde I updated the answer with all the options you have - If you are passing you myStaticCallback from a static method, you need to pass __CLASS__. If you are passing your myCallBack from a static method, you need to create a new instance of the class with new MyClass(), as within the static method there's no instance of $this you can pass... Hope it's all clear :)
  • Christian
    Christian about 8 years
    You forgot to mention __invoke magic method.
  • Kolyunya
    Kolyunya over 7 years
    Passing around method names as strings is a refactoring nightmare.
  • Kolyunya
    Kolyunya over 7 years
    Upvoting this. And yes, it can be even more elegant: $this->processSomething($this->myCallback()); while myCallback() should return an anonymous function. This is WAY more maintainable than passing around method names as strings.
  • Radek Pech
    Radek Pech about 6 years
    Since PHP 5.6 you can use variadic function and argument unpacking: $this->processSomething(function(...$args) { $this->myCallback(...$args); });
  • HartleySan
    HartleySan about 6 years
    __CLASS__ for the static class methods section was what I was looking for. Thanks a million!
  • MannikJ
    MannikJ almost 6 years
    Is there not a better way of passing an existing function? Just another example why I don't like to do it like this: Assume processSomething validates the given parameter(s) and throws an Exception if the first paramater is not a valid callback. If you wrap the call in a new Closure like above, it will first go through and then blow up later when the callback function is actually executed. That makes it very hard to debug, because you're skipping the validation which was implemented for a reason.
  • naneri
    naneri over 4 years
    How do I pass params to that callback?
  • luukvhoudt
    luukvhoudt about 4 years
    Well, I guess I'm the only one who gets a Fatal error: Uncaught TypeError: Argument 1 passed to ::processSomething must be callable, array given
  • Electric Coffee
    Electric Coffee almost 4 years
    since self is an alias for the class itself, can [self::class, 'myCallback'] be used?
  • Ricardo
    Ricardo almost 4 years
    @Fleuv, that’s because you are passing an out of scope method (a protected or private method to an external function or class). Not an obvious error message at all.