skip to content

In PHP 8.3 and later, what do typed class constants add, and when does static::NAME instead of self::NAME matter?

level: middleimportance: should knowfreq 38%

answer

  1. one value per class, overridable
  2. self:: reads the defining class
  3. const string NAME since 8.3
  4. override must keep a compatible type
  5. C::{$name} fetch since 8.3

basics

~20 s

Since PHP 8.3 a class constant can declare a type, and a subclass override must keep a compatible type. self::NAME reads the constant of the class the code is in; static::NAME reads the called class's value, so a subclass override takes effect.

solid answer

~40 s

Class constants are per-class values, public by default, and a subclass can override them unless they are `final`. A parent method that reads `self::TABLE` always gets the parent's value, even when called on a subclass; `static::TABLE` uses late static binding and gets the override. PHP 8.3 added **typed class constants** (`const string TABLE = 'users';`): the value must match the type, and an overriding constant must declare a compatible type, otherwise compilation fails with `Type of Post::TABLE must be compatible with Model::TABLE of type string`. `callable`, `void` and `never` are rejected as constant types. PHP 8.3 also added `Foo::{$name}` for dynamic constant fetches, replacing `constant('Foo::' . $name)`. `Foo::class` is different: it is the class name resolved at compile time, with no autoloading.

code

php · 22 lines
php
<?php
declare(strict_types=1);

abstract class Model
{
    public const string TABLE = 'models';

    public static function describe(): string
    {
        return self::TABLE . ' / ' . static::TABLE;
    }
}

final class Post extends Model
{
    public const string TABLE = 'posts';
    // public const TABLE = 'posts'; // compile error: type must be compatible with string
}

echo Post::describe(), PHP_EOL; // models / posts
$name = 'TABLE';
echo Post::{$name}, PHP_EOL;    // posts (PHP 8.3+)

go deeper

for a junior

Recall that class constants belong to the class, are read with ::, and that Foo::class gives the full class name.

for a middle

Explain self::NAME versus static::NAME with an override, and what typed constants in 8.3 enforce on subclasses.

for a senior

Use typed constants as contracts in base classes and interfaces, and choose self:: for invariants and static:: for extension points.

for a principal

Decide when a constant is the right extension point versus an abstract method or injected configuration, given how rigid constants are.

## Class constants in brief A **class constant** is a named value attached to a class with `const`: ```php class Model { public const string TABLE = 'models'; } ``` Key properties: - A class constant is allocated **once per class**, not per object. - The default visibility is `public`; `protected` and `private` have been allowed since PHP 7.1. - The value must be a **constant expression**: literals, other constants, operators and a few permitted constructs, evaluated without runtime state. - A subclass can **override** an inherited constant by declaring it again, unless the parent marked it `final`. - An undefined constant fetch throws `Error: Undefined constant Model::NAME`. ## `self::NAME` versus `static::NAME` Constants follow the same `self`/`static` split as methods: | Expression in `Model::describe()` | Called as `Post::describe()` where Post overrides `TABLE` | |---|---| | `self::TABLE` | `'models'`, the value in the class where the code is written | | `static::TABLE` | `'posts'`, the called class's override | | `Model::TABLE` | `'models'`, the explicitly named class | So a base class that wants subclasses to configure it through a constant must read it with `static::`. Reading with `self::` is right when the value is part of the base class's own invariant and must not change underneath it. One restriction: `static::` is not allowed inside a constant expression, so `const FULL = static::PREFIX . 'x';` is a compile error. Constant expressions are evaluated with no called class. ## Typed class constants (PHP 8.3) Before 8.3 a constant had no declared type, and a subclass could override `const LIMIT = 10;` with `const LIMIT = 'ten';`, breaking every parent method that did arithmetic with `static::LIMIT`. PHP 8.3 lets class, interface, trait and enum constants declare a type: ```php interface HasTable { const string TABLE = 'unknown'; } class Post implements HasTable { const string TABLE = 'posts'; } ``` The engine enforces three things at compile time: 1. **The value matches the type.** `const int LIMIT = 'ten';` fails with `Cannot use string as value for class constant ... of type int`. 2. **An override declares a compatible type.** The child's type must be the same or narrower (covariant). Omitting the type in the child is itself an error, because an untyped constant is not compatible with a typed one. 3. **Some types are forbidden.** `callable`, `void` and `never` cannot be constant types. This turns a base class's constant into a small typed contract that subclasses and interface implementers must honour. ## Dynamic constant fetch: `C::{$name}` (PHP 8.3) To read a constant whose name is in a variable, PHP 8.3 added the `Foo::{$name}` syntax. Before, the only way was `constant('Foo::' . $name)`, which still works. The expression in braces must evaluate to a string; anything else throws `TypeError: Cannot use value of type int as class constant name`. Visibility still applies, so a private constant cannot be fetched this way from outside. ## `::class` `Foo::class` looks like a constant but is special: - For a class name, it is resolved at **compile time** to the fully qualified name, using the file's namespace and `use` imports. It does not autoload the class and does not check that it exists, so a typo still produces a string. - `static::class` and `self::class` resolve to the called and defining class respectively. - Since PHP 8.0, `$object::class` works on an object and returns the same as `get_class($object)`. On a non-object it throws `TypeError` (for example `Cannot use "::class" on null`). ## Constant or static method? Constants are rigid by design. Their value is a compile-time expression, so a constant cannot depend on configuration, the environment or another object. PHP also has no abstract constants: a base class cannot force a subclass to define one; it can only supply a default the subclass may override. When a base class needs every subclass to provide a value, or the value must be computed, an `abstract public static function table(): string` is the better extension point, because the engine refuses to compile a concrete subclass that has not implemented it. ## Summary - Read overridable configuration with `static::NAME`, fixed invariants with `self::NAME`. - Type class constants on 8.3+ so overrides stay type-safe. - Use `Foo::{$name}` for dynamic fetches and `::class` for names, remembering that `::class` does not autoload.

  • In PHP, does Foo::class trigger autoloading or fail when class Foo does not exist?
    No to both. For a name, `::class` is a compile-time transformation that expands the name against the current namespace and imports. It never loads the class, so a misspelt name still yields a string and fails only when something actually uses it.
  • In PHP 8.3, what error do you get when a subclass overrides a typed constant without a type?
    A compile error: `Type of Child::NAME must be compatible with Parent::NAME of type string`. An untyped override is not considered compatible with a typed parent, so the child must repeat the type or narrow it.
  • In PHP, how did code read a class constant by a variable name before 8.3?
    With `constant('Foo::' . $name)`, which returns the value and throws `Error` if the constant is undefined. PHP 8.3's `Foo::{$name}` does the same with ordinary syntax and still respects constant visibility.

saying these in an interview costs you the question

  • Says self::CONST in a parent method picks up a subclass's override
  • Believes class constants cannot be overridden by subclasses at all
  • Claims typed class constants existed before PHP 8.3
  • Thinks Foo::class autoloads Foo or verifies it exists
  • Writes const X = static::Y and expects it to compile