PHP 8.0: Calling non-static class methods statically result in a fatal error

Version8.0
TypeChange

PHP 8.0 no longer allows to call non-static class methods with the static call operator (::).

Calling non-static methods statically raised a PHP deprecation notice in all PHP 7 versions, and raised a Strict Standards notice in PHP 5 versions.

class Foo {
    public function bar() {}
}
Foo::bar();

// Deprecated: Non-static method Foo::bar() should not be called statically in ... on line ...

In PHP 8.0 and later, this results in a fatal error:

class Foo {
    public function bar() {}
}
Foo::bar();

// Fatal error: Uncaught Error: Call to undefined method Foo::bar() in ...:...

Note that this only affects calling non-static methods statically. Although discouraged, calling a static method non-statically ($this->staticMethod()) is allowed.

This change is implemented throughout the engine.

Variable Functions

class Foo {
    public function bar() {}
}

['Foo', 'bar']();
// Fatal error: Uncaught Error: Non-static method Foo::bar() cannot be called statically in ...:...

Callables

PHP no longer considers an array with class name and a method (['Foo', 'bar']) as a valid callable, and results in a fatal error. This includes PHP core functions that expect a callable. If such callable is passed to a function that expects a valid callable, a \TypeError will be thrown instead of a fatal error at call-time.

class Foo {
    public function bar() {}
}

call_user_func(['Foo', 'bar']);
call_user_func_array(['Foo', 'bar'], []);
// Fatal error: Uncaught TypeError: call_user_func(): Argument #1 ($function) must be a valid callback, non-static method Foo::bar() cannot be called statically in ...:...

This affects all functions ranging from call_user_func and call_user_func_array to register_shutdown_function, set_error_handler, set_error_handler.

register_shutdown_function function in PHP 8.0 versions until beta3 raised a PHP warning at the time register_shutdown_function function is called instead of the current behavior of throwing a \TypeError exception. This was corrected in PHP beta4.

is_callable

is_callable function returns false on callable that calls non-static methods statically. It returned true prior to PHP 8.0.

class Foo {
    public function bar() {}
}

is_callable(['Foo', 'bar']); // false

static, self, and parent

static, self, and and parent pointers can continue to use the static call syntax inside a class.

class Test extends UpperTest{
    public function foo(): {}
    public function bar() {
        static::foo();
        self::foo();
        parent::foo();
    }
}

The call above is still allowed because static, self, and parent are used inside the class scope.

static:: and self:: calls are identical to $this-> calls on non-static methods, and improves readability. In the example above, static::foo() and self::foo() calls can be safely replaced with $this->foo() to improve readability because foo is not a static method.

Backwards Compatibility Impact

For existing code that get fatal errors in PHP 8.0, the fix can as simple as using the correct syntax if there is a class instance in the same scope.

class Foo {
    public function bar() {}
}

$foo = new Foo();
- Foo::bar();
+ $foo->bar(); 

If there is no instantiated class object, and if the class can be instantiated without any parameters or side effects, it will be simple replacement as well.

class Foo {
    public function bar() {}
}

- Foo::bar();
+ (new Foo())->bar(); 

If the class constructor requires parameters, or tends to make any state changes, the fix can be more complicated. The instance needs to be injected to the scope the static call is made.

Note that functions that expect a callable parameter no longer accept callables with a non-static method as static method. A \TypeError exception will be thrown when the callable is passed, as opposed to when the callable is invoked.

class Foo {
    public function bar() {}
}
function takeCallable(callable $func) {}

- takeCallable(['Foo', 'bar']);
+ takeCallable([new Foo(), 'bar']);

is_callable function no longer returns true for such callables either.


Implementation