PHP неправильно обрабатывает мой статический вызов - PullRequest
3 голосов
/ 31 мая 2011

У меня проблема с PHP 5.3. Мне нужно вызвать метод с помощью __callStatic, но если я использую его в инстанцированном объекте, PHP вместо этого вызовет __call.

Пример из реальной жизни:

<?php

    class A {
        function __call($method, $args){
            // Note: $this is defined!
            echo "Ops! Don't works. {$this->c}";
        }

        static function __callStatic($method, $args){
            echo 'Fine!';
        }
    }

    class B extends A {
        public $c = 'Real Ops!';

        function useCallStatic(){
            static::foo();
            // === A::foo();
            // === B::foo();
        }
    }

    $foo = new B();
    $foo->useCallStatic();

    // This works:
    // B::foo();

?>

Печать : Опс! Не работает. Real Ops!

Обратите внимание, что я звоню useCallStatic из new B(). Если я позвоню напрямую, работает нормально, как B::foo().

Что я могу сделать, чтобы он работал нормально?

Ответы [ 5 ]

7 голосов
/ 31 мая 2011

Подумав об этом, на самом деле это не ошибка.Но это определенно не интуитивно понятно.

<?php
class A
{
  public function foo()
  {
    static::bar();
  }

  public function bar()
  {
    echo "bar()\n";
  }
}

$a = new A();
$a->foo();

Это действительно, и вызывает bar ().Это не означает колл-бар статически.Это означает поиск текущего имени класса и вызов функции bar, если она существует, независимо от того, является ли она статической или нет.

Чтобы пояснить немного подробнее: как вы думаете, parent::bar() означает вызов статическогофункция называется bar()?Нет, это означает вызов любой функции с именем bar().

Рассмотрим:

<?php
class A
{
  function __call($name, $args)
  {
    echo "__call()\n";
  }

  static function __callStatic($name, $ags)
  {
    echo "__callStatic()\n";
  }

  function regularMethod()
  {
    echo "regularMethod()\n";
  }

  static function staticMethod()
  {
    echo "staticMethod()\n";
  }

}

class B extends A
{
  function foo()
  {
    parent::nonExistant();   
    static::nonExistant();
    parent::regularMethod();
    parent::staticMethod(); 
  }
}

$b = new B();
$b->foo();

Метод parent::nonExistant() вызывает A::__call(), как и static::nonExistant().Вызов A::__callStatic() для либо вызова будет одинаково действительным!У PHP нет возможности узнать, какой из них вы хотите назвать.Тем не менее, при разработке PHP дает __call приоритет при вызове из этого вида контекста.

parent::regularMethod() вызывает нестатическую функцию regularMethod().(Опять же, оператор :: не означает «вызов этой статической функции».) Аналогично parent::staticMethod() вызывает A::staticMethod(), как и следовало ожидать.

Для решения вашей проблемы:

Youможно вручную вызвать self::__callStatic('foo') в унаследованном классе.

Или в методе __call класса A отфильтровать известный список и вызвать self::__callStatic.

function __call($f, $a)
{
  if ($f == 'foo')
    return self::__callStatic($f, $a); 
  else
  {
  }
}

Это ужасно,но, по крайней мере, людям, которые расширяют класс, не нужно будет делать ничего особенного.

2 голосов
/ 31 мая 2011

Вероятно, это связано со стандартной нотацией вызова родительских методов независимо от состояния. Вы можете проверить, установлено ли ключевое слово $this в методе __call, а если нет, позвонить на __callStatic?

EDIT: Это не ошибка как таковая, статический вызов выполняется из контекста объекта. Посмотрите на эту ветку ошибок на php .

1 голос
/ 31 мая 2011

Во-первых, убедитесь, что вы используете PHP 5.3.0 или новее, как тогда, когда был реализован __callStatic. Кажется, все работает как положено, см. Этот пример:

<?php
class A {
    public function __call($method, $args) {
        echo 'A::__call, ' .
           var_export(array($method, $args), true), PHP_EOL;
    }

    /** As of PHP 5.3.0 */
    public static function __callStatic($method, $args) {
        echo 'A::__callStatic, ' .
           var_export(array($method, $args), true), PHP_EOL;
    }
}

$a = new A;
$a->foo('abc');
A::bar('123');

class B extends A {
    function invokeStatic($args) {
        echo 'B::invokeStatic', PHP_EOL;
        self::someStatic($args);
    }
}

$b = new B;
$b->invokeStatic('456');
?>

Вывод должен быть (или, по крайней мере, для меня):

A::__call, array (
  0 => 'foo',
  1 => 
  array (
    0 => 'abc',
  ),
)
A::__callStatic, array (
  0 => 'bar',
  1 => 
  array (
    0 => '123',
  ),
)
B::invokeStatic
A::__callStatic, array (
  0 => 'someStatic',
  1 => 
  array (
    0 => '456',
  ),
)

Когда я запускаю ваш пример выше, я просто получаю "Отлично!" как единственный выход, который именно то, что я ожидал.

Какую версию PHP вы используете? Вы можете проверить это:

$ php -r 'echo phpversion(), PHP_EOL;'
5.3.3-7+squeeze1
0 голосов
/ 01 июня 2011

Хорошие новости

Я делаю БОЛЬШУЮ работу, но она работает довольно хорошо.Это код:

class NoContext {
    public static function call($callback, $args = null){
        return call_user_func_array($callback, $args ?: array());
    }
}

Вам нужно позвонить NoContext::call (статически) так же, как вы звоните call_user_func или подобному.Будет удален контекст $this.Я все еще думаю, что это ошибка PHP, но ...

Чтобы решить мою проблему, я делаю:

class B extends A {
    function useCallStatic(){
        NoContext::call('A::foo');
    }
}

И он напечатает: Fine!.

Спасибо всем, кто мне помогает.Я очень многому учусь с вашими ответами и жду, когда мои советы будут вам когда-нибудь полезны.

0 голосов
/ 31 мая 2011

Наследование статических методов немного странно в PHP. Я подозреваю, что вам нужно переопределить __callStatic в B

...