Space Cat, Prince Among Thieves

Static::? More like Lies::

In my professional work, we have a system that utilizes __call and __callStatic for caching of certain method calls. We have been running into a problem where calling an undefined method via :: within the class itself will trigger __call rather than the expected __callStatic.

The Problem

The following code example illustrates the issue.

<?php

class foo {
    function __call($name, $args){
        echo "__call " . $name . PHP_EOL;
    }

    public static function __callStatic($name, $args) {
        echo "__callStatic " . $name . PHP_EOL;
    }

    public function make_call_self() {
        self::ted();
    }

    public function make_call_static() {
        static::ted();
    }

    function make_call_classname() {
        foo::bob();
    }
}

$x = new foo();
$x->make_call_self();
$x->make_call_static();
$x->make_call_classname();
foo::sam();

The above returns the following when executed in PHP 5.3+ (excluding PHP 5.3.3, we'll get to this later)

__call ted
__call ted
__call bob
__callStatic sam

See Example

As you can see, all of those ideally should have triggered __callStatic but all save the last foo:::sam() triggered call. If you click the link to the example execution above, it shows that this did in fact work in PHP 5.3.3 but has since been broken.

I was just about to post this as a bug on PHP.net, but my friend Joel Clermont brought to my attention several cases of this being submitted in the past. One example being fairly infamous. Remember how I mentioned it working in 5.3.3? It was actually fixed in that release in response to a bug report. The problem apparently is that it broke a lot of things to do with the at the time new late static binding, and was rolled back in the next release.

From the horses mouth, via colder@php.net

You're misunderstanding the meaning of static::foo();

it doesn't mean "call foo statically" but rather "use runtime information to get the class currently called", and do a static(or not) call to the method of that class.

So static doesn't mean static apparently.

Understanding

After quite a bit of reading and a fair deal of explination by my friend Joel, I came to understand that ::, unlike C++, doesn't mean static at all. Rather it is a "Scope Resolution Operator" which determines scope from "context" meaning that when called from within an instance of an object, it makes instance calls if possible, rather than static calls. Infact, in 5.3+ you can even do $this::. The consequence of this is that self:: nor static:: are necessarily static calls.

This all rubs me wrong; I don't see the benefit to this behavior on the users end. Perhaps there is some backend optimization this allows. Even if that were the case I still feel like you could short circuit undefined self:: calls to __callStatic without breaking it.

Solutions

There were several "solutions" given, none of which are ideal.

  • Call __callStatic directly.
  • Use forward_static_call.
  • Call the class from within a lambda. (After some testing I see that this does not work in PHP 5.4+)

None of these are particularly clean nor ideal. Instead I would like PHP to either add a new call like stat:: or simply fix self:: and static:: to trigger the proper call. The latter of course would be ideal, especially as static has the word static in its name.

See


Comment by: Lewis Cowles on

Lewis Cowles GravatarAre you sure this is not a bug in your understanding of your code? it seems like you are in one place first you are instantiating an object with static methods, then you are calling instantiated functions, at best you could hope for
__call $name
callStatic $name
as you are calling the function on the object, then within the function calling a static.

From the PHP manual
" Overloading in PHP provides means to dynamically "create" properties and methods. These dynamic entities are processed via magic methods one can establish in a class for various action types.

The overloading methods are invoked when interacting with properties or methods that have not been declared or are not visible in the current scope. The rest of this section will use the terms "inaccessible properties" and "inaccessible methods" to refer to this combination of declaration and visibility.

All overloading methods must be defined as public."

Also it was my understanding that callstatic referred to a non static function that was called as static (understandable), whereas you are using it to call a static from an object using object notation to a function that does not seem to exist, therefore call is used...

Is any of this helping? I hope so

Email address will never be publicly visible.