__callStatic (), call_user_func_array (), ссылки и PHP 5.3.1 - PullRequest
8 голосов
/ 09 апреля 2011

Я читал об SO и в других местах, однако, кажется, я не могу найти ничего убедительного.

Есть ли способ эффективно переносить ссылки через этот стек вызовов, что приводит к желаемой функциональностикак описано в примере ниже?Хотя пример не пытается решить его, он, безусловно, иллюстрирует проблему:

class TestClass{
    // surely __call would result similarly
    public static function __callStatic($function, $arguments){
        return call_user_func_array($function, $arguments);
    }
}

// note argument by reference
function testFunction(&$arg){
    $arg .= 'bar';
}

$test = 'foo';
TestClass::testFunction($test);

// expecting: 'foobar'
// getting:   'foo' and a warning about the reference
echo $test;

Для того, чтобы вдохновить потенциальное разрешение, я собираюсь добавить краткие сведения здесь:

Ориентируясь только на call_user_func_array(), мы можем определить, что ( по крайней мере с PHP 5.3.1 ) вы не можете неявно передавать аргументы по ссылке:

function testFunction(&$arg){
    $arg .= 'bar';
}

$test = 'foo';
call_user_func_array('testFunction', array($test));
var_dump($test);
// string(3) "foo" and a warning about the non-reference parameter

Путем явной передачиэлемент массива $test в качестве ссылки, мы можем облегчить это:

call_user_func_array('testFunction', array(&$test));
var_dump($test);
// string(6) "foobar"

Когда мы вводим класс с __callStatic(), явный параметр времени вызова по ссылке, кажется, проходит, как я ожидал,но выдаются предупреждения об устаревании (в моей IDE):

class TestClass{
    public static function __callStatic($function, $arguments){
        return call_user_func_array($function, $arguments);
    }
}

function testFunction(&$arg){
    $arg .= 'bar';
}

$test = 'foo';
TestClass::testFunction(&$test);
var_dump($test);
// string(6) "foobar"

Пропуск ссылочного оператора в TestClass::testFunction() приводит к тому, что $test передается по значению в __callStatic() и, конечно, передается по значениюв качестве элемента массива к testFunction() через call_user_func_array().Это приводит к предупреждению, поскольку testFunction() ожидает ссылку.

Взлом, некоторые дополнительные детали всплыли.Определение __callStatic(), если оно написано для возврата по ссылке (public static function &__callStatic()), не имеет видимого эффекта.Кроме того, если преобразовать элементы массива $arguments в __callStatic() в качестве ссылок, мы увидим, что call_user_func_array() работает примерно так, как ожидалось:

class TestClass{
    public static function __callStatic($function, $arguments){
        foreach($arguments as &$arg){}
        call_user_func_array($function, $arguments);
        var_dump($arguments);
        // array(1) {
        //   [0]=>
        //   &string(6) "foobar"
        // }
    }
}

function testFunction(&$arg){
    $arg .= 'bar';
}

$test = 'foo';
TestClass::testFunction($test);
var_dump($test);
// string(3) "foo"

Эти результаты ожидаемы, так как $test больше не являетсяпередано по ссылке, изменение не передается обратно в его область.Тем не менее, это подтверждает, что call_user_func_array() на самом деле работает, как и ожидалось, и что проблема, безусловно, ограничивается вызывающей магией.

При дальнейшем чтении, похоже, это может бытьPHP-обработка пользовательских функций и магия __call() / __callStatic().Я просмотрел базу данных ошибок на предмет существующих или связанных проблем и нашел ее, но не смог найти ее снова.Я рассматриваю возможность выпуска другого отчета или запроса на повторное открытие существующего отчета.

Ответы [ 4 ]

3 голосов
/ 09 апреля 2011

Это весело.

TestClass::testFunction(&$string);

Это работает, но также выдает предупреждение о времени вызова по ссылке.

Основная проблема в том, что второй аргумент __callStatic входит по значению. Он создает копию данных, если только эти данные не уже ссылка.

Мы можем продублировать ошибку вызова таким образом:

call_user_func_array('testFunction', array( $string ));
// PHP Warning:  Parameter 1 to testFunction() expected to be a reference, value given

call_user_func_array('testFunction', array( &$string ));
echo $string;
// 'helloworld'

Я пытался изменить метод __callStatic для глубокого копирования массива по ссылке, но это тоже не сработало.

Я почти уверен, что немедленная копия, вызванная __callStatic, станет здесь убийцей, и что вы не сможете сделать это без достаточного количества прыжков, чтобы сделать ее немного стикерной, с синтаксической точки зрения .

2 голосов
/ 10 мая 2014

Я отвечаю на это сам с пошаговым воспроизведением проблемы, которая демонстрирует, что, вероятно, невозможно достичь желаемой функциональности.

Наша тестовая функция довольно проста иследующим образом:

function testFunction(&$argument)
{
    $argument++;
}

Если мы вызываем функцию напрямую, она, конечно, ведет себя как ожидалось:

$value = 1;
testFunction($value);
var_dump($value); // int(2)

Если мы вызываем функцию, используя call_user_func_array:

call_user_func_array('testFunction', [$value]);
var_dump($value); // int(1)

Значение остается на 1, поскольку $value не было передано по ссылке.Мы можем заставить это:

call_user_func_array('testFunction', [&$value]);
var_dump($value); // int(2)

, который снова работает как непосредственный вызов функции.

Теперь мы представляем класс, используя __callStatic:

class TestClass
{
    public static function __callStatic($name, $argumentArray)
    {
        return call_user_func_array($name, $argumentArray);
    }
}

Если мы вызываем функцию, перенаправляя ее через __callStatic:

TestClass::testFunction($value);
var_dump($value); // int(1)

Значение остается в 1, и , мы также получаем предупреждение:

Warning: Parameter 1 to testFunction() expected to be a reference, value given

Проверяет, что $argumentArray, переданный в __callStatic, не содержит ссылок.Это имеет смысл, так как PHP не знает, что аргументы предназначены для принятия по ссылке в __callStatic, и что мы пересылаем эти аргументы.

Если мы изменим __callStatic для принятия массива по ссылке, в попытке вызвать желаемое поведение:

public static function __callStatic($name, &$argumentArray)
{
    return call_user_func_array($name, $argumentArray);
}

Ой, о!Спагетти-О!

Fatal error: Method TestClass::__callstatic() cannot take arguments by reference

Не могу этого сделать;кроме того, это поддерживается документацией в любом случае :

Примечание:
Ни один из аргументов этих магических методов не может быть передан по ссылке.

Несмотря на то, что мы не можем объявить __callStatic принять по ссылке, если мы используем call_user_func_array (, что на практике было бы довольно неловко и самоубийственно ) для пересылки ссылокчерез __callStatic:

call_user_func_array(['TestClass', 'testFunction'], [&$value]);
var_dump($value); // int(2)

Это снова работает.

0 голосов
/ 05 июня 2018

Через несколько лет у меня был момент счастливой случайности:

class TestClass{
    public static function __callStatic($functionName, $arguments) {
        $arguments[0]->{'ioParameter'} = 'two';
    }
}
$objectWrapper = new StdClass();
$objectWrapper->{'ioParameter'} = 'one';
TestClass::testFunction($objectWrapper);
var_dump($objectWrapper); 
// outputs: object(stdClass)#3 (1) { ["ioParameter"]=> string(3) "two" }

(проверено на php 5.5)

Этот обходной путь является чистым и жизнеспособным, если вы контролируете интерфейсы.

Он работает, оборачивая аргументы в объекте, который по определению языка всегда передается "по ссылке".

Он позволяет передавать доступные для записи (ссылочные) параметры даже с использованием __callStatic (и, возможно, __call).

0 голосов
/ 09 апреля 2011

Ух ты, после более чем часа моих мыслей, я все еще не уверен, но ...

mixed call_user_func_array ( callback $function , array $param_arr )

Если мы используем простую логику (в этой ситуации), когда класс вызывает функцию call_user_func_array(), она использует статические строки в качестве параметров (передаваемых магическим методом __callStatic()), поэтому параметр 2 является строкой в ​​глазах функции call_user_func_array() и этот параметр также является параметром 1 для функции testFunction(). Это просто строка, а не указатель на переменную, и это, вероятно, причина для сообщения вроде:

Warning: Parameter 1 to testFunction() expected to be a reference, value given in ... on line ...

Другими словами, он просто не может присвоить значение обратно переменной, которая не существует, но все же, testFunction () ожидает, что параметр является ссылкой, и выдает предупреждение, потому что это не так.

Вы можете проверить это вне класса, как:

function testFunction(&$arg){
    $arg .= 'world';
  }

$string = 'hello';
call_user_func_array('testFunction', array($string));
echo $string;

Выдает то же самое предупреждение! Так что проблема не в классе, проблема в этой функции.

Эта функция, очевидно, передает параметры функции в виде статических данных без указания на переменные, а вызываемые ею функции просто не могут обрабатывать ссылочные аргументы.

...