PHP 8.3: Major Features

A refinement release for teams on PHP 8.2.x: stronger invariants (#[Override], typed constants), better JSON and string ergonomics (json_validate, str_increment/str_decrement), and a long tail of small runtime changes that show up under load or in edge-case code paths.

Table of Contents


PHP 8.3 is less about one headline feature and more about tightening contracts: you can express typed constants, verify overrides with #[\Override], and validate JSON without decoding—while the engine and standard library get stricter in places (range(), proc_get_status(), Date/ DOM exceptions). Plan for extra QA around string increment/decrement (deprecated operators, new helpers), reflection (ReflectionProperty::setValue), and assert configuration (INI settings and assert_options() deprecated).

#[\Override] — catch missing overrides at compile time

Mark methods that are intended to override a parent or interface. If the name is wrong or the parent removes the method, PHP fails early instead of silently introducing a new method.

interface Logger
{
    public function log(string $message): void;
}

final class FileLogger implements Logger
{
    #[\Override]
    public function log(string $message): void
    {
        // ...
    }
}

Use this especially in large codebases where refactors rename interface methods.

Typed class constants

Constants on classes, interfaces, traits, and enums can declare types, aligning constants with the rest of the type system.

interface Config
{
    public const string APP_NAME = 'MyApp';
}

This reduces “wrong constant type” bugs that previously surfaced only at use sites.

Readonly: anonymous classes & cloning

  • Anonymous classes may be declared readonly.
  • Readonly properties can be reinitialized during clone (when the object graph allows), which makes immutable clones less painful than in 8.2-only patterns.

Language quality-of-life

  • Dynamic class constant access: SomeClass::{$name} for runtime constant names.
  • Static variable initializers may use arbitrary expressions (not only constants).
  • final on trait methods when importing a trait method.
  • Closures from magic methods can receive named arguments when invoked.
  • php.ini: fallback/default value syntax for cleaner configuration.

New functions worth adopting (json_validate, str_*, DOM, Random)

json_validate()

Validate JSON without decoding into PHP values—ideal for APIs, queues, and quick guards before expensive json_decode().

if (!json_validate($payload)) {
    throw new InvalidArgumentException('Invalid JSON');
}
$data = json_decode($payload, true, flags: JSON_THROW_ON_ERROR);

str_increment() / str_decrement()

Preferred replacements for legacy ++/-- on strings (see deprecations). Use for alphanumeric counters where you previously relied on increment operators.

$next = str_increment('a9'); // predictable stepping without string ++
$prev = str_decrement($next);

DOM, Intl, Random, POSIX, etc.

PHP 8.3 adds many DOM methods (e.g. insertAdjacentElement, getRootNode, replaceChildren), Intl calendar helpers, Random helpers on Randomizer (getFloat, nextFloat, …), and POSIX sysconf/pathconf/eaccess—useful for system-level scripts and tooling.

Backward incompatible changes (migration notes)

Stack / timers / fibers

  • Deep recursion near stack limits may now throw Error when exceeding zend.max_allowed_stack_size (minus reserved); fibers use fiber.stack_size similarly.
  • Zend Max Execution Timers default on for ZTS builds on Linux—watch long-running CLI workers.

Processes

  • proc_get_status() on POSIX returns correct values on repeated calls; results may be cached (check the "cached" key). proc_close() after proc_get_status() now yields correct exit codes (not -1).

Arrays & traits

  • Class constant visibility: variance is now enforced when constants are inherited from interfaces—code that relied on looser visibility may need fixes.
  • Empty array + negative first index: next implicit key follows n+1 (not 0).
  • Traits with static properties: inherited statics from the parent are redeclared per trait user (separate storage)—can affect subtle static state.

Standard library (high impact)

  • range(): stricter validation (TypeError/ValueError for bad inputs), more warnings for odd boundaries, different behavior for character ranges when boundaries look numeric—re-test any code generating ranges from user input.
  • number_format(): negative $decimals now rounds before the decimal point (previously ignored).
  • file(): invalid flag combinations are rejected (e.g. FILE_APPEND was silently accepted before).

Date / DOM / FFI / Opcache

  • Date: richer DateError/DateException hierarchies instead of generic warnings/exceptions—update catch blocks.
  • DOM: spec-aligned behavior for nodes without parents; createAttributeNS() fixes; new members may conflict with userland subclasses—ensure compatible signatures.
  • FFI: void C functions return null, not a dummy FFI\CData:void object.
  • Opcache: opcache.consistency_checks removed (was broken with tracing JIT / inheritance cache).

WeakMap

  • Self-referential keys in WeakMap may be collected in cycles where reachability is only through iteration—audit exotic caching patterns.

Deprecations (increment/decrement strings, get_class(), assert INI)

String ++ / --

Increment/decrement on empty or non-numeric strings is deprecated; non-numeric increment is “soft deprecated”. Prefer str_increment() / str_decrement() for new code.

get_class() / get_parent_class() without arguments

Calling them with no args is deprecated—pass an object or class name explicitly.

Assert

  • assert_options() and related constants deprecated.
  • assert.* INI settings deprecated—migrate to ini settings documented in PHP 8.3 migration “INI handling” notes.

Misc

  • Reflection: ReflectionProperty::setValue($value) (one arg) deprecated for static props—pass null as object for static.
  • SQLite3: prefer exceptions; enableExceptions(false) emits deprecation.
  • Random: MT_RAND_PHP variant deprecated.

Other changes & operations (gc_status, streams, highlights)

  • gc_status() reports richer timing fields (collector/destructor/free time)—useful when tuning memory-heavy workloads.
  • Streams: fread() on sockets may return sooner when buffered data exists; memory streams behave more like files for seeks past end.
  • open_basedir: runtime ini_set rejects paths with .. even when prefixed with ./.
  • highlight_string/file HTML output structure changed—if you snapshot tests of highlighted PHP, update baselines.

Closing thoughts

PHP 8.3 rewards teams that treat constants and overrides as part of the type surface: typed constants and #[\Override] reduce “silent drift” during refactors. Pair that with json_validate for cheap validation and a deliberate plan for string increment and assert deprecations—those are the changes most likely to surprise legacy codebases.