2

How can one detect that a static function called non-statically?

For example, in this case:

class Foo
{
    public static function bar()
    {
        // How can I tell here that bar() is called on an instance?

        //var_dump(debug_backtrace()[0]['type'] == '::');
        // at all times the above prints bool(true)
        return 1;
    }
}

// later in the code
$foo = new Foo();
$foo::bar(); // that's fine

// even later
$foo->bar(); // this should not happen, yet it's here and there

I want to debug and eradicate cases such as on the last line above: someone somewhere wrongly calls a function on an instance, expecting that it would return something related to the instance in subject; instead one gets a somewhat related constant. Subsequently I need to know when this happens. If hell breaks loose or an exception gets thrown is fine with me too.

So far I've found that non-static calls for static functions are internally translated into static calls, hence debug_backtrace()[0]['type'] tells us nothing (it's :: in both cases).

Community
  • 1
  • 1
sanmai
  • 29,083
  • 12
  • 64
  • 76
  • Calling a non-static function statically is an error. Calling a static function non-statically is **not**. Your code is example of the latter, not the former. – Sherif Oct 31 '16 at 01:54
  • @Sherif I update the question to make it more obvious that that's really not error – sanmai Oct 31 '16 at 01:58
  • I guess what I'm left wondering then, is why you care to detect this at all then if it's not an error? What threw me off is the fact that you use the statement "*debug these cases*", which leads me to believe that you are under the impression this can cause buggy behavior. It can't. PHP already knows at compile-time that the method is declared statically. So it doesn't care that you tried invoking the method from an object instance at runtime. It still knows to use a static context. – Sherif Oct 31 '16 at 02:06
  • @Sherif it certainly causes a buggy behavior in a legacy code I'm trying to fix; how I'm supposed to fix it not knowing where's the trouble? – sanmai Oct 31 '16 at 02:10
  • And what is this buggy behavior you're seeing? Chances are it has nothing to do with calling static methods non-statically, which is why I'm left wondering why you'd ask such a strange question. Perhaps try presenting the actual code that produces the problem and explain the desired behavior vs. the actual behavior of the real-world problem? Because right now the code you present in this question does not demonstrate any problems. – Sherif Oct 31 '16 at 02:13
  • The bug is that someone somewhere calls this function on an instance, expecting that it would return something related to the instance in subject; instead one gets a somewhat related constant; I need to know when and where this happens; how I am supposed to know that? – sanmai Oct 31 '16 at 02:17
  • Why should someone call a **static** method non-statically and expect a return related to the instance? I think it's clearly mentioned in the php manual that even if you call a static method non-statically that the call gets translated into a static context. The method will never run in a non-statically context. What you mention as a bug, is not a bug. It's a fundamental misunderstanding of PHP's static methods. – Charlotte Dunois Oct 31 '16 at 03:37
  • @CharlotteDunois exactly, and that's why I want to know when this happens; because it shouldn't be that way! – sanmai Oct 31 '16 at 04:36

2 Answers2

2

The solution is twofold:

  1. First one needs to disable E_STRICT reporting:

    error_reporting(error_reporting() ^ E_STRICT);
    
  2. Next one should remove static keyword from the function's declaration.

    public /* static */ function bar()
    

Now it is possible to see if a function being called statically or dynamically with help of debug_backtrace function:

class Foo
{
    public function bar()
    {
        $calledStatically = debug_backtrace()[0]['type'] == '::';

        if (!$calledStatically) {
            throw new Exception("Should not happen");
        }

        // ...
    }
}

From the documentation:

type (string): The current call type. If a method call, "->" is returned. If a static method call, "::" is returned. If a function call, nothing is returned.

Quick demo.

Foo::bar(); // normal result returned

$foo = new Foo();
$foo->bar(); // throws exception
sanmai
  • 29,083
  • 12
  • 64
  • 76
Adam Plocher
  • 13,994
  • 6
  • 46
  • 79
0

If you don't want to turn off E_STRICT for eternity, there's a way.

If the class Foo is, by sheer luck or providence, instantiated only in a couple of places like so:

public function getFoo()
{
    return new Foo();
}

Then if we subclass it and redefine bar():

class Foo2 extends Foo
{
    public static function bar()
    {
         throw new Exception("You're calling Foo::bar() on an instance");
    }
}

Then if we replace Foo with Foo2 where it were instantiated...

public function getFoo()
{
    //return new Foo();
    return new Foo2();
}

Exception will be thrown upon calling the above new static method:

$q = Foo::bar(); // no error just as before

$foo = $other->getFoo();
$foo->bar(); // throws exception

If anyone ever calls $foo->bar(); on this kind-of-Foo, he would be quickly notified that he's making a mistake.

sanmai
  • 29,083
  • 12
  • 64
  • 76