skip to content

Versions & Deprecations

PHP ships a new minor version each year, and the 8.0 to 8.5 line changed comparisons, errors and types. Interviewers ask which version added a feature and how you upgrade without breakage.

part ofPHPoverview, primer and where to startread it →
on this pageshow

explore

questions

6

Which headline language features did each PHP release from 8.0 to 8.5 introduce?

level: middleimportance: must knowfreq 62%

answer

  1. 8.0 the big line: JIT, match, attributes
  2. 8.1 enums, readonly, fibers
  3. 8.3 typed class constants, #[\Override]
  4. 8.4 property hooks, asymmetric visibility
  5. 8.5 pipe operator, clone-with

basics

~20 s

8.0: named arguments, attributes, union types, match, nullsafe, promotion, JIT. 8.1: enums, readonly, fibers, first-class callables. 8.2: readonly classes, DNF types. 8.3: typed class constants, #[\Override]. 8.4: property hooks, asymmetric visibility. 8.5: pipe operator, clone-with.

solid answer

~30 s

**8.0** was the big release: named arguments, attributes, constructor property promotion, union types, `match`, the nullsafe operator `?->`, `mixed`, `str_contains()` and a JIT in OPcache. **8.1** added enums, readonly properties, fibers, first-class callable syntax `f(...)`, intersection types and `never`. **8.2** added readonly classes, DNF types, standalone `null`/`false`/`true` types and the Random extension. **8.3** added typed class constants, `#[\Override]`, dynamic class-constant fetch and `json_validate()`. **8.4** added property hooks, asymmetric visibility, lazy objects, `new Foo()->bar()` without parentheses, `array_find()` and friends, and the `#[\Deprecated]` attribute. **8.5** added the pipe operator `|>`, clone-with, `#[\NoDiscard]`, `array_first()`/`array_last()` and the URI extension.

code

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

enum Status: string { case Draft = 'draft'; case Sent = 'sent'; } // 8.1

final class Invoice
{
    public function __construct(
        public readonly string $number,          // promotion 8.0, readonly 8.1
        public private(set) Status $status,      // asymmetric visibility 8.4
    ) {}
}

$invoice = new Invoice(number: 'INV-1', status: Status::Draft); // named args 8.0

$slug = '  Invoice Draft  '
    |> trim(...)                               // pipe 8.5, first-class callable 8.1
    |> strtolower(...);

$first = array_first(['a', 'b']);             // 8.5

go deeper

for a junior

Recall a handful of anchors: match and named arguments in 8.0, enums and readonly in 8.1, property hooks in 8.4, the pipe operator in 8.5.

for a middle

Place most headline features on the right release and pair each minor with the deprecation it brought, such as dynamic properties in 8.2.

for a senior

Use the map to plan: know which features you can adopt at your minimum supported version and which deprecations block the next upgrade.

for a principal

Decide when a codebase may adopt a new release's features, balancing the gain against every deployment target and shared library that must move to that version first.

## Why this question comes up Interviewers ask "which version added X" to see whether a candidate has written current PHP or only read about it. You are not expected to recite every changelog line; you are expected to place the headline features on the right release and to know that **8.0 is the dividing line** between legacy and modern PHP. Each feature's mechanics are a separate topic; this is the map. ## The release-by-release map | Release | Headline language features | Notable functions and APIs | |---|---|---| | **8.0** | named arguments, attributes `#[...]`, constructor property promotion, union types, `match`, nullsafe `?->`, `mixed`, `static` return type, `throw` as an expression | `str_contains()`, `str_starts_with()`, `str_ends_with()`, `WeakMap`, `ValueError`, the `Stringable` interface, a JIT in OPcache | | **8.1** | enums, readonly properties, fibers, first-class callable syntax `strlen(...)`, pure intersection types, `never`, `new` in initializers, `0o` octal prefix | `array_is_list()` | | **8.2** | readonly classes, DNF types, standalone `null`, `false` and `true` types, constants in traits | the Random extension, `#[\SensitiveParameter]` | | **8.3** | typed class constants, `#[\Override]`, dynamic class-constant fetch `C::{$name}`, readonly properties re-initialisable during cloning | `json_validate()`, `mb_str_pad()`, `str_increment()` | | **8.4** | property hooks, asymmetric visibility such as `public private(set)`, lazy objects, `new Foo()->method()` without wrapping parentheses | `array_find()`, `array_any()`, `array_all()`, `BcMath\Number`, `Dom\HTMLDocument`, PDO driver subclasses such as `Pdo\Mysql`, `#[\Deprecated]` | | **8.5** | the pipe operator `\|>`, clone-with (`clone($obj, [...])`), `#[\NoDiscard]` with a `(void)` cast, closures in constant expressions | `array_first()`, `array_last()`, the URI extension (`Uri\Rfc3986\Uri`, `Uri\WhatWg\Url`) | ## 8.0: the dividing line PHP 8.0 was a **major** release, so it combined new features with the largest set of behaviour changes in the 8.x line: - Comparing a number with a **non-numeric** string now compares them as strings, so `0 == "foo"` became `false`. - Many engine warnings became `Error` exceptions, and internal functions throw `TypeError` or `ValueError` on bad arguments. - PDO's default error mode became exceptions. - `create_function()`, `each()` and `__autoload()` were removed. That is why teams speak of "before 8" and "after 8" even though every later minor also changed things. ## Minor releases still change behaviour The features arrive with deprecations that matter for upgrades: - **8.1** deprecated passing `null` to non-nullable parameters of built-in functions, and made mysqli throw by default. - **8.2** deprecated **dynamic properties** (unless the class has `#[\AllowDynamicProperties]`) and `"${var}"` string interpolation. - **8.4** deprecated **implicitly nullable** parameter types (`Foo $x = null`) and removed the `E_STRICT` level. - **8.5** deprecated the backtick operator, the non-canonical casts such as `(integer)`, and ending a `case` with `;`. ## Adopting a feature: syntax versus functions Knowing the release of a feature matters most when code must run on more than one version: - **Syntax features** such as enums, `match`, property hooks or `|>` are **parse errors** on older versions. The whole file fails to compile, so they can be used only once every runtime that loads the file is on the new release. - **New functions** such as `str_contains()` or `array_first()` can be **polyfilled**: define the function only when `function_exists()` says it is missing. A library can therefore use them before its minimum PHP version reaches the release that added them. - **New classes and attributes** sit in between: an attribute naming a class that does not exist is not an error until something reads it through reflection, while a missing class used with `new` fails at runtime. ## How to answer well A strong answer does three things: 1. Anchors the big items correctly: enums and readonly in 8.1, property hooks in 8.4, the pipe operator in 8.5. 2. Mentions that each minor also brought deprecations, so "which version added X" and "which version broke Y" go together. 3. Knows where to check: the manual's **migration guides** ("Migrating from PHP 8.4.x to PHP 8.5.x" and so on) list new features, new functions, backward-incompatible changes and deprecations per release. Common slips are putting enums in 8.0, putting readonly classes in 8.1 (readonly *properties* are 8.1, readonly *classes* 8.2), and describing an 8.6 feature as available: 8.6 is still in pre-release, and **8.5** is the current stable line.

  • Readonly properties and readonly classes: which version added which?
    Readonly **properties** arrived in PHP 8.1: a property declared `readonly` can be initialised once from inside the class scope. Readonly **classes** arrived in 8.2: marking the class `readonly` makes every declared property readonly. 8.3 then allowed readonly properties to be re-initialised during cloning, and 8.5's clone-with lets you pass new values to `clone()`.
  • Where do you look up exactly what changed between two PHP versions?
    In the PHP manual's migration appendices, one per minor, such as "Migrating from PHP 8.4.x to PHP 8.5.x". Each has sections for new features, new classes, new functions, backward-incompatible changes, deprecated features and other changes. The php-src `UPGRADING` file in the release tag carries the same information for the latest version.

saying these in an interview costs you the question

  • Enums have been part of PHP since 8.0.
  • Property hooks and the pipe operator both arrived in PHP 8.4.
  • Minor releases only add features and never deprecate anything.
  • Readonly classes came in 8.1, together with readonly properties.
  • PHP 8.6 is the current stable release.
open as a page

When upgrading a ten-year-old PHP 7.4 invoicing application to PHP 8.5, which changes are most likely to break it, and how do you sequence the work?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Most breakage comes from 8.0: 0 == "" is false, many warnings became TypeError, count(null) throws, PDO throws by default. Then come later deprecations. Add tests, fix on 7.4, run 8.x in CI, walk the migration guides, then switch.

open as a page

How does the PHP release cycle work: how often does a new minor version ship, and what support does each branch get?

level: juniorimportance: should knowfreq 34%

basics

~20 s

PHP ships one new major or minor version a year, in late November, after a 20-week alpha, beta and RC cycle. Each branch then gets patch releases, first bug fixes, later security fixes only, until end of life.

open as a page

In PHP, what does an E_DEPRECATED notice tell you, and how should a team act on deprecations before an upgrade?

level: middleimportance: should knowfreq 42%

basics

~10 s

E_DEPRECATED means the code still works but uses something PHP plans to remove or change, usually at the next major version. Surface the notices in development and CI, fix them first, then upgrade.

open as a page

In PHP, how do you check which version is running, and when should you prefer PHP_VERSION_ID over version_compare()?

level: middleimportance: should knowfreq 36%

basics

~10 s

PHP_VERSION is the version string, such as "8.5.11"; PHP_VERSION_ID is an integer, 80511, that compares with plain < and >=. version_compare() compares arbitrary version strings, including RC and dev suffixes.

open as a page

How would you plan PHP version upgrades across a fleet of dozens of PHP applications that currently run different versions?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Inventory every app's PHP version and dependencies, rank by support dates and risk, and make upgrading a yearly routine: next version in CI, deprecations fixed continuously, shared libraries first, and a standard base image per version.

open as a page