PHP is handling incorrectly my static call

2019-01-24 19:37发布

I'm havinh a problem on PHP 5.3. I need to call a method by using __callStatic, but if I use it in a instancied object, PHP call __call instead.

Above a real life example:

<?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();

?>

Prints: Ops! Don't works. Real Ops!

Note that I call useCallStatic from a new B(). If I call directly works fine like B::foo().

What I can do to it works fine?

5条回答
Explosion°爆炸
2楼-- · 2019-01-24 19:56

Good news

I make a BIG work arround, but it works pretty fine. This is the code:

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

You need to call NoContext::call (statically) like you call call_user_func or similar. It will to remove the $this context. I still thinks that it is a PHP bug, but...

To solve my problem, I do:

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

And it will prints: Fine!.

Thanks everybody that help me. I really learn too much with your replies and I wait that my tips be userful sometime to you.

查看更多
在下西门庆
3楼-- · 2019-01-24 19:57

After thinking about it, it's not really a bug. But it's definitely unintuitive.

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

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

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

This is valid, and calls bar(). It does not mean call bar statically. It means to look up the current class name and call function bar if it exists, whether or not it's a static function.

To clarify a bit more: do you think parent::bar() means to call a static function called bar()? No, it means call whatever function is named bar().

Consider:

<?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();

The parent::nonExistant() method invokes A::__call(), as does static::nonExistant(). Invoking A::__callStatic() for either call would be equally as valid! There is no way for PHP to know which one you want to be called. However, by design, PHP gives __call the priority when invoked from this sort of context.

parent::regularMethod() invokes the non static function regularMethod(). (Again, the :: operator does not mean "call this static function.") Likewise parent::staticMethod() invokes A::staticMethod() as one might expect.

To solve your problem:

You could manually call self::__callStatic('foo') in the inherited class.

Or within the __call method of class A filter against a known list and call self::__callStatic.

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

It's ugly, but at least people who extend the class won't need to do anything special.

查看更多
Summer. ? 凉城
4楼-- · 2019-01-24 19:59

First, make sure you're using PHP 5.3.0 or newer, as that's when __callStatic was implemented. It all seems to work as expected, see this example:

<?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');
?>

The output should be (or at least, it is for me):

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',
  ),
)

When I run your example above, I just get "Fine!" as the only output, which is exactly what I would have expected.

Exactly what version of PHP are you running? You can check with this:

$ php -r 'echo phpversion(), PHP_EOL;'
5.3.3-7+squeeze1
查看更多
乱世女痞
5楼-- · 2019-01-24 20:02

This is probably due to the standard notation of calling parent methods, regardless of state. You could check if the $this keyword is set in the __call method and if not, make a call to __callStatic ?

EDIT: This isn't a bug per se, the static call is being made from an object context. Take a look at this bug thread on php.

查看更多
该账号已被封号
6楼-- · 2019-01-24 20:11

Inheritance of static methods is a bit weird in PHP. I suspect you need to redefine __callStatic in B

查看更多
登录 后发表回答