In a PHP model base class, how do you write a static factory such as User::create() that returns the subclass, and what can break it?
answer
- instantiate the called class
- new self returns the base
- : static return type since 8.0
- child constructor may need other arguments
- final or abstract constructor pins the signature
basics
~20 sWrite create() as return new static(...) with a : static return type, so User::create() builds a User. Guard the constructor signature (final, abstract or interface-declared), because a subclass constructor with different parameters makes new static throw.
solid answer
~40 s`new self()` always builds the class that contains the method, so `User::create()` would return a bare `Model`, and with a `: static` return type PHP throws `TypeError` (`Model::create(): Return value must be of type User, Model returned`). `new static()` uses late static binding to instantiate the **called** class, and the `static` return type (PHP 8.0+) tells callers and tools they get that subclass. The risk is the constructor: PHP does not check constructor signatures across inheritance unless the parent's constructor is abstract or comes from an interface, so a subclass can demand extra arguments and `new static()` then throws `ArgumentCountError`. Declare the base constructor `final`, abstract, or in an interface so every subclass accepts what the factory passes. Calling `Model::create()` on an abstract base throws `Error: Cannot instantiate abstract class Model`.
code
php · 22 lines<?php
declare(strict_types=1);
abstract class Model
{
final public function __construct(protected array $attributes = []) {}
public static function create(array $attributes): static
{
return new static($attributes);
}
abstract public static function table(): string;
}
final class User extends Model
{
public static function table(): string { return 'users'; }
}
$user = User::create(['name' => 'Ada']);
echo $user::class, ' ', $user::table(), PHP_EOL; // User usersgo deeper
Know that new static() builds the class a static method was called on, while new self() builds the class the code is in.
Explain the static return type, the TypeError it raises for new self, and why an abstract base cannot be instantiated.
Show that unchecked constructor signatures make new static fragile, and fix it with a final or abstract constructor or an interface.
Decide whether a model hierarchy should rely on inherited static factories at all, or on composition and explicit factories that are easier to replace.
## The goal A common base-class design gives every model a **static named constructor**: ```php $user = User::create(['name' => 'Ada']); $post = Post::create(['title' => 'Hello']); ``` `create()` is written once, in the base class `Model`, but each call should return an instance of the class it was called on. That requires the base class to refer to "whichever subclass was called", which is what **late static binding** (the `static` keyword) provides. ## `new self` versus `new static` | Expression inside `Model::create()` | `User::create()` returns | Notes | |---|---|---| | `new self(...)` | a `Model` | `self` is the class where the code is written | | `new Model(...)` | a `Model` | same as `self`, but hard-coded | | `new static(...)` | a `User` | `static` is the called class, resolved at runtime | With `new self`, the subclass-specific methods are missing from the returned object. If the method also declares `: static` as its return type, PHP catches the mismatch at the return: `Model::create(): Return value must be of type User, Model returned` is a `TypeError`. ## The `static` return type PHP 8.0 added `static` as a **return type** (it is not allowed for parameters or properties). It means "an instance of the called class". It documents the contract, is enforced at runtime, and lets static analysers and IDEs infer `User` from `User::create()` without a docblock. PHP 8.5 also allows a `final` subclass to narrow an inherited `: static` return type to `self` or to its own class name when it overrides the method. ## What breaks `new static` 1. **Constructor signatures are not inherited contracts.** PHP checks that an overriding method is compatible with its parent, but it skips that check for constructors unless the parent constructor is `abstract` or declared in an interface. A subclass can therefore write `__construct(private string $title)`. `new static($attributes)` then passes the wrong arguments, and PHP throws `ArgumentCountError` ("Too few arguments to function Post::__construct() …") or a `TypeError`. 2. **An abstract base.** If `Model` is abstract, `Model::create()` makes `Model` the called class and `new static` throws `Error: Cannot instantiate abstract class Model`. Usually that is the right outcome; the factory is meant to be called on concrete subclasses. 3. **Abstract static hooks.** A base class often reads subclass details through `static::`, for example `static::table()`. If `table()` is declared `abstract public static` in the base and someone calls it on the base, PHP throws `Error: Cannot call abstract method Model::table()`. ## Making the constructor safe There are three ways to guarantee every subclass accepts what the factory passes: - **Declare the base constructor `final`.** Subclasses cannot override it, so `new static($attributes)` always matches. Subclass-specific setup moves into a protected hook the constructor calls. - **Declare it `abstract`, or in an interface the base implements.** PHP then applies the normal signature-compatibility check to subclass constructors. - **Make the class `final`** when it needs no subclasses at all. Then `static` and `self` name the same class and `new static` cannot meet a foreign constructor. PHPStan reports `Unsafe usage of new static()` for exactly this risk and stays quiet when the class is final, the constructor is final or abstract, or the constructor comes from an interface. ## Why not override `create()` in every subclass The alternative to late static binding is to write `create()` again in each subclass with `return new User(...)`. It works, but it multiplies identical code, and every new model must remember to add it. Worse, a subclass that forgets inherits the parent's version and quietly returns the wrong type unless a `: static` return type catches it. One `new static` in the base removes the duplication, and the return type turns a forgotten detail into an immediate `TypeError` instead of a subtle bug. ## Putting it together A robust version of the pattern: - the base constructor is `final` (or abstract with a fixed signature); - `create()` returns `new static(...)` and declares `: static`; - per-class details come from `static::` members such as a table-name method or constant; - the base class is `abstract`, so it cannot be instantiated by accident. The same technique powers fluent "with-er" methods that return `static` on immutable value objects, so a subclass's `withX()` keeps returning the subclass. Whether a class should have a single shared instance (the singleton pattern) is a separate design question; this pattern creates a new object on every call.
- In PHP, why does PHP allow a subclass constructor to take different parameters from its parent's?Constructors are exempt from the method signature-compatibility check unless the parent constructor is abstract or declared in an interface. Construction is treated as class-specific, so a subclass may add required parameters. That freedom is what makes `new static()` in a parent unsafe unless the constructor is pinned.
- In PHP, what happens if a method declared : static returns new self() and is called on a subclass?The return type check fails: PHP throws `TypeError` with a message such as `Model::create(): Return value must be of type User, Model returned`. The `static` return type is enforced at runtime against the called class.
- In PHP 8.5, can a final subclass override a method declared : static with a return type of self?Yes. PHP 8.5 allows a final subclass to substitute `static` with `self` or its concrete class name, because no further subclass can exist for `static` to mean. Earlier versions rejected that narrowing as incompatible.
saying these in an interview costs you the question
- Uses new self() in a base factory and expects the subclass back
- Assumes PHP enforces the parent constructor's signature on every subclass
- Believes the static return type is only a docblock hint with no runtime check
- Thinks new static() creates a shared single instance per class
- Calls the factory on the abstract base and expects a usable object