Don't call instance methods statically

Don't call instance methods statically

There are quite a few things in PHP 4 that were a bit strange. One example is that PHP 4 allowed static calling of instance methods:

class   Something
{
     function   doWork ( )
     {
         var_dump ( 'doing work' ) ;
     }
}
 
$something   =   new   Something ;
 
Something :: doWork ( ) ;

To keep backwards compatibility with PHP 4, this code works up to PHP 5, even though doWork() is not declared static, and called statically using :: rather than the object operator ->. PHP 5 at least complains with the E_DEPRECATED error:

Deprecated: Non-static method Something::doWork() should not be called statically in ...

Now things will get really weird. When calling an instance method of another class statically, the $this context would carry over from the caller to the called class. In other words, $this suddenly refers to another object instance:

class   Something
{
     public   function   doWork ( )
     {
         var_dump ( $this ) ;
     }
}
 
class   Another
{
     public   function   run ( )
     {
         return   Something :: doWork ( ) ;
     }
}
 
$something   =   new   Something ;
 
$something -> doWork ( ) ;
 
$another   =   new   Another ;
 
$another -> run ( ) ;

While in PHP 5, this used to be an E_STRICT error, PHP 7 will emit an E_DEPRECATED error.

This article is an excerpt from our eBook PHP 7 Explained.

There are two problems with E_STRICT errors. First, E_ALL does not include E_STRICT before PHP 5.4, so in older versions you have to set error reporting to E_ALL & ~E_DEPRECATED (both error levels are bit fields, so you need a bitwise OR (&) to combine both). Second, you have to enable this error reporting level in php.ini, setting it at runtime by calling the error_reporting() function will not work, because E_STRICT error messages are already generated at compile-time.

When the above example gets executed with PHP 5, $this inside Something referred to the instance of Another, which is a really unexpected behavior. PHP 7, on the other hand, will complain about an undefined variable:

Notice: Undefined variable: this in ...

Since $this is undefined, trying to call a method will lead to another error, which in our example turns into a fatal error, since it is not caught:

Fatal error: Uncaught Error: Using $this when not in object context in ...

One might wonder why such a weird behavior still has not been removed from PHP after more than 10 years. The reasoning is that before something can be removed from PHP, it has to be marked as deprecated first, only then it can be removed in the next major version. PHP upholds backwards compatibility, that is for sure. Even if it feels ludicrous, we will have to wait until PHP 8 to finally get rid of this PHP 4 relic.

Should your application call non-static methods statically, you can fix this by injecting an instance of the class. You can then modify the call to use the object operator -> instead of the :: operator:

class   Something
{
     public   function   doWork ( )
     {
         var_dump ( $this ) ;
     }
}
 
class   Another
{
     private   $something ;
 
     public   function   __construct ( Something   $something )
     {
         $this -> something   =   $something ;
     }
 
     public   function   run ( )
     {
         return   $this -> something -> doWork ( ) ;
     }
}
 
$something   =   new   Something ;
 
$something -> doWork ( ) ;
 
$another   =   new   Another ( $something ) ;
 
$another -> run ( ) ;

First, we call the doWork() method directly on the instance of Something, then we call it from the instance of Another. The result is

object(Something)#1 (0) { } object(Something)#1 (0) { }

In both cases, $this refers to the Something instance.

For the sake of completeness, we should mention that PHP also allows calling of static methods dynamically:

class   Something
{
     public   static   function   doWork ( )
     {
         var_dump ( 'do work' ) ;
     }
 
     public   function   __construct ( )
     {
         $this -> doWork ( ) ;
     }
}
 
$something   =   new   Something ;

Fortunately, in this case, there is no problem with $this context propagation in the first place. This, however, does not argue away the fact that one should not write code like this.