In PHP, when a subclass does not redeclare a parent's static property, do the parent and subclass share one value, and why does that matter?
answer
- one slot unless redeclared
- static:: reaches the same shared slot
- per-subclass counter or registry bug
- redeclare, or key by static::class
- lives per request, per process in CLI
basics
~20 sYes. An inherited static property that the subclass does not redeclare is the same storage as the parent's, so writes through Child::$x change Parent::$x too. Redeclare it in the subclass, or key an array by static::class, to get per-class values.
solid answer
~50 sA static property belongs to the class that declares it. A subclass that merely inherits it does not get a copy: `Child::$count`, `static::$count` and `Parent::$count` all point at one slot. So a base `Model` that does `static::$instances++` in its constructor counts **all** models together, and a per-class cache in the base leaks entries between subclasses. Two fixes: redeclare the property in each subclass (then `static::` picks the subclass's own slot), or keep one array in the base keyed by `static::class`. The value lasts for the rest of the request under FPM, but for the whole process in a CLI worker or a test run, so shared static state also leaks between jobs and tests. PHP 8.5 lets you write `public private(set) static` to make it read-only from outside; `readonly` is not allowed on static properties.
code
php · 24 lines<?php
declare(strict_types=1);
abstract class Model
{
/** @var array<class-string, int> */
private static array $instances = [];
public function __construct()
{
self::$instances[static::class] = (self::$instances[static::class] ?? 0) + 1;
}
public static function count(): int
{
return self::$instances[static::class] ?? 0;
}
}
final class User extends Model {}
final class Post extends Model {}
new User(); new User(); new Post();
echo User::count(), ' ', Post::count(), PHP_EOL; // 2 1go deeper
Remember that a static property belongs to the class that declares it, and a subclass shares it unless it declares its own.
Explain why static::$x still hits the parent's slot when the subclass did not redeclare it, and show the keyed-array fix.
Spot static caches that leak between subclasses, worker jobs or tests, and give each one a scope and a reset path.
Set a codebase rule for mutable static state: where it is allowed, how it is reset, and when it must become an injected service instead.
## One declaration, one slot A **static property** is storage attached to a class rather than to an object. The question is what a subclass gets when it inherits one. PHP's rule: - If the subclass **does not redeclare** the property, the subclass's entry is an alias of the parent's slot. `Parent::$x`, `Child::$x`, `self::$x` in either class and `static::$x` all read and write the **same value**. - If the subclass **redeclares** the property (`protected static int $x = 0;` again in the subclass), it gets its **own slot**, and the parent's value is untouched by writes through the subclass. Late static binding does not change this. `static::$x` resolves to the called class's property, and if that class did not redeclare it, that property *is* the parent's. ## The bug it causes A typical case is a base class that counts or caches per model: ```php class Model { protected static int $instances = 0; public function __construct() { static::$instances++; } public static function count(): int { return static::$instances; } } class User extends Model {} class Post extends Model {} new User(); new User(); new Post(); echo User::count(), Post::count(), Model::count(); // 333 ``` The author expected `2`, `1` and `0`. Because neither subclass redeclared `$instances`, there is only one counter. The same happens with an identity map or a metadata cache kept in a base-class static array: the first subclass to fill it wins, and the next subclass reads the wrong entry. ## Two fixes | Fix | How | Trade-off | |---|---|---| | Redeclare per subclass | Each subclass repeats `protected static int $instances = 0;` | Easy to forget in a new subclass; the bug returns silently | | Key by class name | Base keeps `private static array $instances = [];` and uses `self::$instances[static::class]` | One declaration, works for every subclass automatically | The keyed array is usually the safer choice, because it does not rely on every future subclass author remembering a redeclaration. Note that it uses `self::` for the storage (one array owned by the base) and `static::class` for the key (the called class). Whichever fix you choose, give the state a **reset path**. A small `public static function resetCounts(): void { self::$instances = []; }` (or a clear method on the cache) lets a test's setup start from a known state and lets a long-running worker drop per-job data between jobs. Without it, the only way to clear static state is to end the process. ## How long the value lives Static properties live as long as the script's user state: 1. **Under PHP-FPM or another web SAPI**, one request. The next request on the same worker starts with the declared default. 2. **In a CLI process**, the whole process. A long-running worker that handles many jobs in a loop keeps every static cache and counter from job to job, which can grow memory and serve stale data. 3. **In a test run**, all tests that execute in the same PHP process share static state, so a test that fills a static cache changes what the next test sees. Treat any mutable static property as global state with a documented reset path. ## Controlling writes PHP offers limited protection for static properties: - **Visibility** (`private`, `protected`) limits who can reach the property at all. - **Asymmetric visibility on static properties** arrived in PHP 8.5: `public private(set) static int $instances = 0;` lets anyone read `Model::$instances` but only the class write it. On PHP 8.4, asymmetric visibility worked only on instance properties. - **`readonly` is not available**: declaring a static property `readonly` is a compile error (`Static property Model::$x cannot be readonly`). For a value that never changes, use a class constant. ## Summary - An inherited static property that is not redeclared is one shared slot across the hierarchy. - `static::` finds the called class's property, which is still the parent's slot unless redeclared. - Prefer an array keyed by `static::class` for per-class state in a base class. - Static state resets per request under FPM but persists across jobs in CLI workers and across tests in one process.
- In PHP, if Child redeclares protected static $count = 0, what does Parent::$count show after Child::$count++?It keeps its own value. Redeclaring the static property gives the subclass a separate slot, so increments through `Child::$count` or `static::$count` in a `Child` call leave `Parent::$count` unchanged.
- In PHP 8.5, how do you let outside code read a static property but only the class write it?Declare it with asymmetric visibility: `public private(set) static int $total = 0;`. Reads from anywhere work, while writes from outside the class throw `Error`. `readonly` cannot be used, because static properties cannot be readonly.
saying these in an interview costs you the question
- Says every subclass automatically gets its own copy of an inherited static property
- Believes static:: gives a separate value per subclass without any redeclaration
- Assumes static caches are cleared between jobs in a long-running CLI worker
- Proposes marking a static property readonly to freeze it
- Keys a per-class cache by self::class, collapsing every subclass into one entry