PHP 8.0: Class magic method signatures are strictly enforced
Magic methods in PHP are special class method names that, if declared, brings special functionality to the class.
There are several magic methods in PHP. __constuct()
magic method is called when a new class object is instantiated with new Foo()
pattern, the __get()
magic method is called when non-existing class property is requested, __toString()
magic method is called when a class object is coerced to a string
, and so on.
Although all these magic methods carried a semantic meaning, they were not programmatically enforced until PHP 8.0. The __toString()
method, for example, semantically expects to return a string
, but it was possible to return any data type, including ones that cannot be coerced to strings.
class Foo {
public function __toString(): object {
}
}
In PHP 8.0 and later, if a magic method is declared, it must adhere to the function signature and return type if declared too.
A few signature checks are enforced for return types since their introduction in PHP 7.0. Namely, __construct
, __destruct
, and __clone
are were not allowed to declare a return type at all, not even void
(void
was added in PHP 7.1).
Types are optional
Unlike a PHP class interface
, that enforces the parameter and return types to be present in all implementations, magic method enforcement allows to not declare a type at all.
This allows existing classes that implement magic methods to continue to run without having to declare explicit types.
However, if a type is declared, it must follow the signature.
Fatal Errors on Signature Mismatches
PHP 8.0 throws fatal errors when interface implementations and class inheritance violates LSP. Magic methods follow the same pattern.
- Return types are allowed to narrow the return type.
- Method parameters are allowed to widen the parameter type.
Variance is allowed
Similar to standard class/interface inheritance, magic methods variance is allowed too.
For example, a __get()
method which should return a mixed
type can declare that its return type is bool
because bool
is already included in the mixed
type.
Further, the __set()
magic method can widen its method parameter from __set(string $name, $value)
to __set(string|CacheEntry $name, $value)
if the function can handle CacheEntry
objects too.
Magic Method Signatures
class GoodFoo {
public function __isset(string $name): bool {}
public function __get(string $name): mixed {}
public function __set(string $name, mixed $value): void {}
public function __unset(string $name): void {}
public function __set_state(array $properties): object {}
public function __call(string $name, array $arguments): mixed {}
public function __callStatic(string $name, array $arguments): mixed {}
public function __invoke(mixed $arguments): mixed {}
public function __clone(): void {}
public function __serialize(): array {}
public function __unserialize(array $data): void {}
public function __sleep(): array {}
public function __wakeup(): void {}
public function __debugInfo(): ?array {}
}
Any magic method implementation must take all parameters in their signature, and if declares a return type, it must be of the same type of a sub type too.
Any declarations otherwise triggers a fatal error:
class BadFoo {
public function __isset(string $name): array {}
// mismatch ^^^^^
}
// Fatal error: BadFoo::__isset(): Return type must be bool when declared in ... on line ...
class BadBar {
public function __call(array $name, array $arguments): mixed {}
// mismatch ^^^^^
}
// Fatal error: BadBar::__call(): Parameter #1 ($name) must be of type string when declared in /in/JpKPd on line 4
Magic methods with no return types allowed
Magic methods that did not allow declaring any return type prior to PHP 8.0 continue to enforce this.
class BadBar {
public function __construct(): void {}
public function __destruct(): void {}
}
Neither of the declarations are allowed in PHP 8.0 and prior versions. They result in a fatal error:
Fatal error: Method BadBar::__construct() cannot declare a return type in ... on line ...
Fatal error: Method BadBar::__destruct() cannot declare a return type in ... on line ...
Stringable
interface
PHP 8.0 introduces a new Stringable
that is added automatically to any class that implements __toString
magic methods. If the Stringable
interface is explicitly declared (e.g Foo implements Stringable
), the __toString()
method signature is enforced with interface rules.
private
Magic Methods
PHP 8.0 relaxes signature enforcement when a private
method is re-declared in a child class. Magic methods must follow the signature even if they are declared private
.
class Foo {
private function __get(): mixed {}
}
// Fatal error: Method Foo::__get() must take exactly 1 argument in ... on line ...
Backwards Compatibility Impact
This is backwards-compatibility breaking change. However, the enforced signatures are semantically correct, and allows variance, mitigates potential compatibility issues.
- If there are no types enforced, the their types will not be checked. However, the methods must accept same number of parameters.
mixed
type is includes all types in PHP exceptvoid
, which makes any return type fulfills return type signature.
Related Changes
- Fatal errors on incompatible method signatures
- Calling non-static class methods statically result in a fatal error
- Inheritance rules are not applied to
private
class methods