Вызываемые выражения

Вызываемое выражение — ссылка на функцию или метод, которая передаётся в другую функцию как аргумент. Параметрам, которые принимают вызываемые выражения, объявляют тип callable.

<?php

function foo(callable $callback) {
$callback();
}

Отдельные функции наподобие array_map(), usort() или preg_replace_callback() принимают callback-функции как аргументы.

Объявление вызываемых выражений

Тип callable представляет значения или выражения, доступные для вызова как функция. Вызываемые выражения вызывают как функции или передают как аргументы в функции или методы, которые ожидают в параметре callback-функцию. Свойствам классов нельзя объявлять тип callable; вызываемым свойствам объявляют тип Closure.

Вызываемыми значениями становятся:

  • Объекты класса Closure
  • Строки (string) с названием функции или квалифицированным названием метода
  • Массивы (array) с названием класса или объектом (object) в элементе с индексом 0 и названием метода в элементе с индексом 1
  • Объекты (object), в классе которых объявили магический метод __invoke()

Объекты Closure создают синтаксисом анонимных функций, стрелочных функций, синтаксисом первоклассных вызываемых значений или методом Closure::fromCallable().

Замечание: Синтаксис первоклассных callable-значений доступен только с PHP 8.1.0.

Пример #1 Пример объявления функций обратного вызова, которые становятся объектами класса Closure

<?php

// Синтаксис анонимных функций
$double1 = function ($a) {
return
$a * 2;
};

// Синтаксис первоклассных вызываемых значений
function double_function($a) {
return
$a * 2;
}
$double2 = double_function(...);

// Синтаксис стрелочных функций
$double3 = fn($a) => $a * 2;

// Метод Closure::fromCallable
$double4 = Closure::fromCallable('double_function');

// Вызов замыкания как callback-функции
// удваивает значение каждого элемента в диапазоне
$new_numbers = array_map($double1, range(1, 5));
print
implode(' ', $new_numbers) . PHP_EOL;

$new_numbers = array_map($double2, range(1, 5));
print
implode(' ', $new_numbers) . PHP_EOL;

$new_numbers = array_map($double3, range(1, 5));
print
implode(' ', $new_numbers) . PHP_EOL;

$new_numbers = array_map($double4, range(1, 5));
print
implode(' ', $new_numbers);

Результат выполнения приведённого примера в PHP 8.1:

2 4 6 8 10
2 4 6 8 10
2 4 6 8 10
2 4 6 8 10

Вызываемые выражения объявляют как строку с названием функции или статического метода. PHP поддерживает передачу встроенных и пользовательских функций, но не языковых конструкций: array(), echo, empty(), eval(), isset(), list(), print или unset().

Для ссылки на статический метод класса не потребуется создавать объект (object) этого класса, достаточно создать массив с названием класса в элементе с индексом 0 и названием метода в элементе с индексом 1 или сослаться на метод через синтаксис с оператором ::, который разрешает область действия метода: 'ClassName::methodName'.

На методы объектов (object) в вызываемых выражениях ссылаются через массив: объект (object) указывают в элементе с индексом 0, а название метода — в элементе с индексом 1.

Видимость метода в объектах Closure проверяется относительно места объявления, а не вызова замыкания Closure, тогда как видимость методов в выражениях с псевдотипом callable проверяется относительно места вызова выражения, поэтому попытка вызова callable-выражения, которое ссылается на недоступный в точке вызова метод, выдаст ошибку. На методы классов лучше ссылаться через замыкания Closure, поскольку видимость метода в точке вызова замыкания не влияет на доступность вызова метода.

Замечание: Объекты Closure связываются с областью видимости объявления, тогда как вызываемые выражения со ссылкой на метод класса через строку или массив разрешаются в области видимости вызова. Вызываемое значение со ссылкой на закрытый или защищённый метод класса, который потребуется вызывать извне области видимости класса, создают методом Closure::fromCallable() или синтаксисом первоклассных вызываемых значений.

PHP поддерживает вызываемые выражения, которые возможно передать как аргумент, но невозможно вызывать. К таким выражениям относятся вызываемые значения, которые зависят от контекста и ссылаются на метод класса в иерархии наследования через ключевые слова: 'parent::method' или ["static", "method"].

Замечание: С PHP 8.2.0 контекстно-зависимые вызываемые выражения устарели. Независимые от контекста ссылки на методы получают путём замены выражений наподобие 'parent::method' выражениями parent::class . '::method' или синтаксисом первоклассных вызываемых значений.

Пример #2 Примеры вызова callable-выражений функцией call_user_function()

<?php

// Пример callback-функции
function my_callback_function()
{
echo
"Привет, мир!", PHP_EOL;
}

// Пример callback-метода
class MyClass
{
static function
myCallbackMethod()
{
echo
"Привет, мир!", PHP_EOL;
}
}

// Тип 1: Простой вызов callback-функции
call_user_func('my_callback_function');

// Тип 2: Вызов статического метода класса
call_user_func(['MyClass', 'myCallbackMethod']);

// Тип 3: Вызов метода объекта класса
$obj = new MyClass();
call_user_func([$obj, 'myCallbackMethod']);

// Тип 4: Вызов статического метода класса
call_user_func('MyClass::myCallbackMethod');

// Тип 5: Вызов статического метода класса с разрешением названия класса через языковую конструкцию ::class
call_user_func([MyClass::class, 'myCallbackMethod']);

// Тип 6: Вызов статического метода класса по контекстно-зависимой относительной ссылке
class A
{
public static function
who()
{
echo
'A', PHP_EOL;
}
}

class
B extends A
{
public static function
who()
{
echo
'B', PHP_EOL;
}
}

call_user_func(['B', 'parent::who']); // Выводит: A.
// Начиная с PHP 8.2.0 callable-выражения
// с относительными названиями методов устарели

// Тип 7: Объекты, классы которых реализуют магический метод __invoke(),
// доступны для вызова как функции
class C
{
public function
__invoke($name)
{
echo
'Привет, ', $name;
}
}

$c = new C();
call_user_func($c, 'PHP!');

Результат выполнения приведённого примера:

Привет, мир!
Привет, мир!
Привет, мир!
Привет, мир!
Привет, мир!

Deprecated: Callables of the form ["B", "parent::who"] are deprecated in script on line 51
A
Привет, PHP!

Замечание:

Callback-функции, которые зарегистрировали функцией call_user_func() или call_user_func_array(), не вызовутся при непойманном исключении, которое выбросила предыдущая callback-функция.

Добавить

Примечания пользователей 12 notes

up
280
andrewbessa at gmail dot com
13 years ago
You can also use the $this variable to specify a callback:

<?php
class MyClass {

    public $property = 'Hello World!';

    public function MyMethod()
    {
        call_user_func(array($this, 'myCallbackMethod'));
    }

    public function MyCallbackMethod()
    {
        echo $this->property;
    }

}
?>
up
207
steve at mrclay dot org
13 years ago
Performance note: The callable type hint, like is_callable(), will trigger an autoload of the class if the value looks like a static method callback.
up
193
computrius at gmail dot com
12 years ago
When specifying a call back in array notation (ie. array($this, "myfunc") ) the method can be private if called from inside the class, but if you call it from outside you'll get a warning:

<?php

class mc {
   public function go(array $arr) {
       array_walk($arr, array($this, "walkIt"));
   }

   private function walkIt($val) {
       echo $val . "<br />";
   }

    public function export() {
        return array($this, 'walkIt');
    }
}

$data = array(1,2,3,4);

$m = new mc;
$m->go($data); // valid

array_walk($data, $m->export()); // will generate warning

?>

Output:
1<br />2<br />3<br />4<br />
Warning: array_walk() expects parameter 2 to be a valid callback, cannot access private method mc::walkIt() in /in/tfh7f on line 22
up
184
Riikka K
10 years ago
A note on differences when calling callbacks as "variable functions" without the use of call_user_func() (e.g. "<?php $callback = 'printf'; $callback('Hello World!') ?>"):

- Using the name of a function as string has worked since at least 4.3.0
- Calling anonymous functions and invokable objects has worked since 5.3.0
- Using the array structure [$object, 'method'] has worked since 5.4.0

Note, however, that the following are not supported when calling callbacks as variable functions, even though they are supported by call_user_func():

- Calling static class methods via strings such as 'foo::doStuff'
- Calling parent method using the [$object, 'parent::method'] array structure

All of these cases are correctly recognized as callbacks by the 'callable' type hint, however. Thus, the following code will produce an error "Fatal error: Call to undefined function foo::doStuff() in /tmp/code.php on line 4":

<?php
class foo {
    static function callIt(callable $callback) {
        $callback();
    }
    
    static function doStuff() {
        echo "Hello World!";
    }
}

foo::callIt('foo::doStuff');
?>

The code would work fine, if we replaced the '$callback()' with 'call_user_func($callback)' or if we used the array ['foo', 'doStuff'] as the callback instead.
up
185
edanschwartz at gmail dot com
11 years ago
You can use 'self::methodName' as a callable, but this is dangerous. Consider this example:

<?php
class Foo {
    public static function doAwesomeThings() {
        FunctionCaller::callIt('self::someAwesomeMethod');
    }

    public static function someAwesomeMethod() {
        // fantastic code goes here.
    }
}

class FunctionCaller {
    public static function callIt(callable $func) {
        call_user_func($func);
    }
}

Foo::doAwesomeThings();
?>

This results in an error:
Warning: class 'FunctionCaller' does not have a method 'someAwesomeMethod'.

For this reason you should always use the full class name:
<?php
FunctionCaller::callIt('Foo::someAwesomeMethod');
?>

I believe this is because there is no way for FunctionCaller to know that the string 'self' at one point referred to to `Foo`.
up
175
metamarkers at gmail dot com
12 years ago
you can pass an object as a callable if its class defines the __invoke() magic method..
up
118
mariano dot REMOVE dot perez dot rodriguez at gmail dot com
10 years ago
I needed a function that would determine the type of callable being passed, and, eventually,
normalized it to some extent. Here's what I came up with:

<?php

/**
 * The callable types and normalizations are given in the table below:
 *
 *  Callable                        | Normalization                   | Type
 * ---------------------------------+---------------------------------+--------------
 *  function (...) use (...) {...}  | function (...) use (...) {...}  | 'closure'
 *  $object                         | $object                         | 'invocable'
 *  "function"                      | "function"                      | 'function'
 *  "class::method"                 | ["class", "method"]             | 'static'
 *  ["class", "parent::method"]     | ["parent of class", "method"]   | 'static'
 *  ["class", "self::method"]       | ["class", "method"]             | 'static'
 *  ["class", "method"]             | ["class", "method"]             | 'static'
 *  [$object, "parent::method"]     | [$object, "parent::method"]     | 'object'
 *  [$object, "self::method"]       | [$object, "method"]             | 'object'
 *  [$object, "method"]             | [$object, "method"]             | 'object'
 * ---------------------------------+---------------------------------+--------------
 *  other callable                  | idem                            | 'unknown'
 * ---------------------------------+---------------------------------+--------------
 *  not a callable                  | null                            | false
 *
 * If the "strict" parameter is set to true, additional checks are
 * performed, in particular:
 *  - when a callable string of the form "class::method" or a callable array
 *    of the form ["class", "method"] is given, the method must be a static one,
 *  - when a callable array of the form [$object, "method"] is given, the
 *    method must be a non-static one.
 *
 */
function callableType($callable, $strict = true, callable& $norm = null) {
  if (!is_callable($callable)) {
    switch (true) {
      case is_object($callable):
        $norm = $callable;
        return 'Closure' === get_class($callable) ? 'closure' : 'invocable';
      case is_string($callable):
        $m    = null;
        if (preg_match('~^(?<class>[a-z_][a-z0-9_]*)::(?<method>[a-z_][a-z0-9_]*)$~i', $callable, $m)) {
          list($left, $right) = [$m['class'], $m['method']];
          if (!$strict || (new \ReflectionMethod($left, $right))->isStatic()) {
            $norm = [$left, $right];
            return 'static';
          }
        } else {
          $norm = $callable;
          return 'function';
        }
        break;
      case is_array($callable):
        $m = null;
        if (preg_match('~^(:?(?<reference>self|parent)::)?(?<method>[a-z_][a-z0-9_]*)$~i', $callable[1], $m)) {
          if (is_string($callable[0])) {
            if ('parent' === strtolower($m['reference'])) {
              list($left, $right) = [get_parent_class($callable[0]), $m['method']];
            } else {
              list($left, $right) = [$callable[0], $m['method']];
            }
            if (!$strict || (new \ReflectionMethod($left, $right))->isStatic()) {
              $norm = [$left, $right];
              return 'static';
            }
          } else {
            if ('self' === strtolower($m['reference'])) {
              list($left, $right) = [$callable[0], $m['method']];
            } else {
              list($left, $right) = $callable;
            }
            if (!$strict || !(new \ReflectionMethod($left, $right))->isStatic()) {
              $norm = [$left, $right];
              return 'object';
            }
          }
        }
        break;
    }
    $norm = $callable;
    return 'unknown';
  }
  $norm = null;
  return false;
}

?>

Hope someone else finds it useful.
up
24
bradyn at NOSPAM dot bradynpoulsen dot com
9 years ago
When trying to make a callable from a function name located in a namespace, you MUST give the fully qualified function name (regardless of the current namespace or use statements).

<?php

namespace MyNamespace;

function doSomethingFancy($arg1)
{
    // do something...
}

$values = [1, 2, 3];

array_map('doSomethingFancy', $values);
// array_map() expects parameter 1 to be a valid callback, function 'doSomethingFancy' not found or invalid function name

array_map('MyNamespace\doSomethingFancy', $values);
// => [..., ..., ...]
up
11
InvisibleSmiley
4 years ago
If you pass a callable method to a function with a callable type declaration, the error message is misleading:

<?php
class X {
    protected function foo(): void {}
}

function bar(callable $c) {}

$x = new X;
$c = [$x, 'foo'];
bar($c);
?>

Error message will be something like "Argument #1 ($c) must be of type callable, array given" while the actual problem here is only the visibility of method "foo". All you need to do is changing it to public (or use a different approach, e.g. with a Closure).
up
6
gulaschsuppe2 at gmail dot com
6 years ago
I tried many possible ways of calling functions by function name directly and assigned to a variable on 3v4l. Not mentioned yet, it is possible to use an array as a caller, at least since PHP 7.1.25. The following script contains all the information I gained:

<?php

// Call function via function name:
    // Basics:
        // A function can also be called by using its string name:
        function callbackFunc() {
            echo 'Hello World';
        }

        'callbackFunc'(); // Hello World
                            
        // A function can also be called if its name is assigned to a variable:
            function callbackFunc() {
                echo 'Hello World';
            }

            $funcName = 'callbackFunc';
            $funcName(); // Hello World

    // Static class method:
        // It is also possible to call a public static class method via 'ClassName::functioName' notation:
            class A {
                public static function callbackMethod() {
                    echo "Hello World\n";
                }
            }
            'A::callbackMethod'(); // Hello World

            $funcName = 'A::callbackMethod';
            $funcName(); // Hello World

    // Non static class method:
        // It is also possible to call non static class methods by creating an array which first element is the object the method should be called on and the second element is the non static method to be called. The array can directly be used as a caller:
            class A {
                private $prop = "Hello World\n";

                public function callbackMethod() {
                    echo $this->prop;
                }
            }

            $a = new A;
            [$a, 'callbackMethod']();
            $funcCallArr = [$a, 'callbackMethod'];
            $funcCallArr();

        // Of course this also works inside the class with '$this':
            class A {
                private function privCallback() {
                    echo 'Private';
                }

                public function privCallbackCaller($funcName) {
                    [$this, $funcName]();
                }
            }

            (new A)->privCallbackCaller('privCallback'); // Private

?>
up
2
pawel dot tadeusz dot niedzielski at gmail dot com
9 years ago
@edanschwartz at gmail dot com

You can use ::class property to always indicate the class you're in when using static methods:

<?php
class Foo {
    public static function doAwesomeThings() {
        FunctionCaller::callIt(self::class . '::someAwesomeMethod');
    }

    public static function someAwesomeMethod() {
        // fantastic code goes here.
    }
}

class FunctionCaller {
    public static function callIt(callable $func) {
        call_user_func($func);
    }
}

Foo::doAwesomeThings();
?>
up
0
chris dot rutledge at gmail dot com
7 years ago
Having read this line in the manual above, 

"A method of an instantiated object is passed as an array containing an object at index 0 and the method name at index 1. Accessing protected and private methods from within a class is allowed."

I decided to do some testing to see if I could access private methods using the call_user_func methods. Thankfully not, but for completeness here is my test which also covers using static and object contexts

<?php
class foo {
    
    public static $isInstance = false;
    
    public function __construct() {
        self::$isInstance = true;
    }

    public function bar() {
        var_dump(self::$isInstance);
        echo __METHOD__;
    }
    
    private function baz() {
        var_dump(self::$isInstance);
        echo __METHOD__;
    }
    
    public function qux() {
        $this->baz();
    }
    
    public function quux() {
        self::baz();
    }
}

call_user_func(['foo','bar']);    //fase, foo:bar

call_user_func(['foo','baz']);  //warning, cannot access private method

call_user_func(['foo','quux']); //false, foo::baz

call_user_func(['foo','qux']);  //fatal, Using $this when not in object context 

$foo = new foo;

call_user_func([$foo,'bar']);    //true, foo::bar
call_user_func([$foo,'baz']);    //warning, cannot access private method
call_user_func([$foo,'qux']);    //true, foo::baz

call_user_func(['foo','bar']);  //true, foo::bar (static call, yet $isInstance is true)

?>
To Top