In PHP 8, which rules does the engine enforce when a class declares magic methods such as __get, __call or __toString?
answer
- declared types must match the documented signature
- exact parameter counts, no by-reference
- static only for __callStatic and __set_state
- non-public magic: E_WARNING only
- omitted types are not enforced
basics
~20 sMagic methods have fixed parameter counts, no by-reference parameters, and only __callStatic and __set_state may be static. Since PHP 8.0, declared types must also match the documented signatures. A non-public magic method just triggers a warning.
solid answer
~40 sPHP checks magic method declarations at compile time. Parameter counts are fixed (`__get` exactly 1, `__call` exactly 2, `__toString` none), parameters cannot be by reference, and `__callStatic` and `__set_state` must be `static` while the others must not be. Since **PHP 8.0**, any types you *do* declare must match the documented signature: `__get(int $name)` fails with `Parameter #1 ($name) must be of type string when declared`, and `__toString(): int` fails with `Return type must be string when declared`. Omitted types are allowed, for backwards compatibility, and `never` is always an accepted return type. Visibility is the soft spot: a `private` magic method only emits `E_WARNING: The magic method ... must have public visibility`. `__construct` and `__destruct` cannot declare a return type at all.
go deeper
Remember that magic methods should be public and that most must not be static.
Know the fixed parameter counts, the 8.0 rule that declared types must match, and which two magic methods must be static.
Plan PHP 8 upgrades around these compile errors, and write full typed signatures on new magic methods.
Use these rules when setting coding standards for magic methods, and decide whether static analysis should require full signatures.
## Why the engine checks magic methods Magic methods are called by the engine, not by your code, so the engine decides what arguments it passes and what it expects back. Parameter counts and the static rules were checked long before PHP 8, but declared **types** were not: a `__get(int $name)` compiled and then misbehaved on every call. PHP 8.0 added **type checks** for magic methods when the class is compiled. ## The rules 1. **Parameter count is fixed.** `__get`, `__isset`, `__unset` and `__set_state` take exactly one parameter; `__set`, `__call` and `__callStatic` exactly two; `__toString`, `__debugInfo`, `__clone`, `__sleep`, `__wakeup` and `__serialize` none. Violations produce compile errors such as `Method Settings::__get() must take exactly 1 argument` or `Method Money::__toString() cannot take arguments`. 2. **No by-reference parameters.** `__set(string $name, &$value)` fails with `cannot take arguments by reference`. 3. **Static or not.** `__callStatic` and `__set_state` **must** be static; every other magic method **must not** be (`Method ... must be static` / `cannot be static`). Note that the manual's overloading page still says a static `__get` gives only a warning; the PHP 8.5 engine makes it a compile error. 4. **Declared types must match.** If you write a type, it must be the documented one. Omitting it is fine. 5. **Public visibility.** Every magic method except `__construct`, `__destruct` and `__clone` should be `public`. Violating this is only an `E_WARNING`, not a compile error. ## The documented signatures | Method | Parameters | Return type if declared | |---|---|---| | `__get` | `string $name` | any (`mixed`) | | `__set` | `string $name, mixed $value` | `void` | | `__isset` | `string $name` | `bool` | | `__unset` | `string $name` | `void` | | `__call`, `__callStatic` | `string $name, array $arguments` | any (`mixed`) | | `__toString` | none | `string` | | `__debugInfo` | none | `?array` | | `__set_state` | `array $properties` | `object` | | `__invoke` | free | free | Two details: - A return type of `never` is always accepted, because a method that never returns cannot violate the expected type. - `__construct` and `__destruct` must not declare any return type (`Method ... cannot declare a return type`). The error messages name the rule directly, for example `Settings::__get(): Parameter #1 ($name) must be of type string when declared` and `Money::__toString(): Return type must be string when declared`. ## A PHP 8.5 addition PHP 8.5 introduced the `#[\NoDiscard]` attribute, which warns when a caller ignores a return value. Magic methods whose return type is `void` by contract, such as `__set` and `__unset`, and `__construct`/`__destruct`, cannot carry it; the engine rejects `#[\NoDiscard]` on them at compile time. ## A fully typed, valid set This declaration passes every check and documents the contract for readers and analysers: ```php final class Settings { public function __get(string $name): mixed { /* ... */ } public function __set(string $name, mixed $value): void { /* ... */ } public function __isset(string $name): bool { /* ... */ } public function __unset(string $name): void { /* ... */ } public function __call(string $name, array $arguments): mixed { /* ... */ } public static function __callStatic(string $name, array $arguments): mixed { /* ... */ } public function __toString(): string { /* ... */ } } ``` Changing any declared type to something else, adding a parameter, or dropping `static` from `__callStatic` turns it into a compile error. ## What the checks do not cover - **Semantics.** Nothing checks that `__isset` agrees with `__get`, or that `__toString` is cheap. - **Omitted types.** A `__get($name)` without types passes; the engine checks only what you declare. - **Names that merely look magic.** All names starting with `__` are reserved by PHP, but `__TOSTRING` or `__tostring` still work because method names are case-insensitive, while `__to_string` is just an ordinary method that nothing calls. ## Why it matters in practice When upgrading old code to PHP 8, classes that declared magic methods with non-standard types (`__get(int $key)`, `__toString(): ?string`) stop compiling. The fix is mechanical: use the documented types or drop the declarations. Writing the full signatures in new code documents intent and lets analysers reason about the methods.
- In PHP 8, is a class with __get($name) and no types at all still valid?Yes. The engine enforces only the types you declare; an untyped `__get($name)` compiles for backwards compatibility. The parameter count, by-reference ban and static rules still apply.
- In PHP 8.5, what happens if a magic method such as __toString is declared private?The class still compiles, but the engine emits `E_WARNING: The magic method Money::__toString() must have public visibility`. Unlike a wrong type or parameter count, visibility is not a compile error.
saying these in an interview costs you the question
- Says a private magic method is a fatal compile error
- Believes PHP 8 requires every magic method to declare types
- Declares __get with an int parameter and expects it to compile
- Thinks __callStatic can be an instance method
- Says declared magic method types were already checked in PHP 7