PHP 8.1: Major Features
Upgrade path from PHP 8.0.x: language additions, runtime tightening, and migration checklists for staging.
Table of Contents
- Enums
- Readonly properties
- First-class callable syntax
- Fibers
- Intersection types
- never return type
- new in initializers
- Syntax & language tweaks (0o, array unpacking, named args after unpack)
- Backward incompatible changes (migration notes)
- Deprecations (what to fix early)
- Extensions: notable changes
Where 8.0 reset the platform, 8.1 fills in the language: Enums and readonly land in userland code, Fibers give libraries a concurrency primitive, and intersection/never round out the type story. Expect friction where the runtime gets stricter—$GLOBALS reassignment, MySQLi defaulting to exceptions, and a longer tail of deprecations that become hard errors in later 8.x releases.
Enums
Enums bring a first-class, type-safe way to represent a closed set of values.
- Backed enums store a scalar value (
string|int) that can be serialized/transported easily. - Pure enums are identity-only, great for domain state machines and exhaustive handling.
enum Status: string
{
case Draft = 'draft';
case Published = 'published';
case Archived = 'archived';
}
function canEdit(Status $status): bool
{
return $status !== Status::Archived;
}
$status = Status::from('draft'); // Status::Draft
$value = $status->value; // 'draft'
Practical guidance
- Use enums instead of “stringly-typed” flags (
'draft'|'published') in method signatures and DTOs. - Prefer backed enums for persistence and APIs; prefer pure enums for internal workflow states.
- Combine with
matchfor exhaustive handling:
function label(Status $status): string
{
return match ($status) {
Status::Draft => 'Draft',
Status::Published => 'Published',
Status::Archived => 'Archived',
};
}
Readonly properties
Readonly properties can be assigned only once, typically in the constructor. This is a major win for immutable DTOs, value objects, and safer domain models.
final class UserProfile
{
public function __construct(
public readonly int $id,
public readonly string $email,
) {}
}
What this changes architecturally
- Encourages “construct fully, then don’t mutate” objects.
- Reduces the need for private properties + getters for many DTO-like structures.
- Plays nicely with constructor property promotion for concise immutable types.
First-class callable syntax
PHP 8.1 adds a convenient way to create closures from callables:
function normalize(string $s): string
{
return strtolower(trim($s));
}
$fn = normalize(...); // Closure
$out = array_map($fn, [' A ', 'B ']); // ['a', 'b']
This is equivalent to Closure::fromCallable('normalize'), but is shorter and reads better in pipelines.
Fibers
Fibers are a low-level primitive for cooperative concurrency. They do not magically make your code async, but they enable frameworks/libraries to build async runtimes, schedulers, and structured concurrency.
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('paused');
echo "resumed with: {$value}\n";
});
echo $fiber->start() . "\n"; // paused
$fiber->resume('payload'); // resumed with: payload
When to care
- If you use an async framework/event loop: Fibers may significantly simplify integration.
- For most classic request/response apps: Fibers are mostly “infrastructure for libraries”.
Intersection types
Intersection types express “must satisfy all of these interfaces/classes”.
function export(iterable&Countable $items): array
{
if (count($items) === 0) {
return [];
}
return iterator_to_array($items, preserve_keys: true);
}
Note: Intersection types cannot be combined with union types.
never return type
The never return type indicates that a function does not return: it always throws or exits.
function fail(string $message): never
{
throw new RuntimeException($message);
}
This improves static analysis and makes control flow explicit.
new in initializers
PHP 8.1 allows new expressions in more places: parameter defaults, static variables, constant initializers, and attribute arguments.
final class Clock
{
public function now(): DateTimeImmutable
{
return new DateTimeImmutable('now');
}
}
function handler(Clock $clock = new Clock()): DateTimeImmutable
{
return $clock->now();
}
This removes a lot of “factory boilerplate” in simple cases. (Be mindful of service lifetimes if you’re in a DI container.)
Syntax & language tweaks (0o, array unpacking, named args after unpack)
Octal integer literal prefix: 0o / 0O
$mask = 0o755;
Array unpacking with string keys
$a = [1, 'a' => 'b'];
$b = [...$a, 'c' => 'd']; // [1, 'a' => 'b', 'c' => 'd']
Named argument after argument unpacking
foo(...$args, named: $arg);
full_path for file uploads
$_FILES entries now include a full_path key (intended for webkitdirectory uploads).
Practical recipes
Backed enum from untrusted input (tryFrom)
$status = Status::tryFrom($row['status']) ?? Status::Draft;
Pipelines with first-class callables
$normalize = static fn(string $s): string => strtolower(trim($s));
$mapper = $normalize(...);
$lines = array_map($mapper, $raw);
Backward incompatible changes (migration notes)
This section is intentionally pragmatic: it focuses on issues that commonly break applications during the upgrade.
PHP core / language
- $GLOBALS write restrictions: writing to the whole
$GLOBALSarray is no longer supported (e.g.array_pop($GLOBALS)errors). Element access like$GLOBALS['x']still works. - Static variables in inherited methods: inherited (non-overridden) methods now share static variables with the parent method.
- Optional before required parameters: optional parameters declared before required ones are treated as required; in PHP 8.1 this can now raise
ArgumentCountErrorat call time (even with named arguments). - Return type compatibility with internal classes: overriding internal methods without compatible return types triggers deprecations; use
#[ReturnTypeWillChange]where needed for cross-version support. - New keywords:
readonlyis now a keyword (still allowed as a function name).neveris now a reserved word (cannot be used for class/interface/trait names or in namespaces).
Resource to object migration (more of it)
Several extensions migrated resources to objects (update is_resource() checks to false checks or to object types):
- FileInfo: uses
finfoobjects instead offileinforesources. - FTP: uses
FTP\Connectionobjects. - IMAP: uses
IMAP\Connectionobjects. - LDAP: uses
LDAP\Connection,LDAP\Result,LDAP\ResultEntryobjects. - PgSQL: uses
PgSql\Connection,PgSql\Result,PgSql\Lobobjects. - PSpell: uses
PSpell\DictionaryandPSpell\Configobjects.
PDO / MySQLi behavior changes
- MySQLi: default error reporting mode changed from “silent” to “exceptions”. To restore old behavior:
mysqli_report(MYSQLI_REPORT_OFF); - PDO:
PDO::ATTR_STRINGIFY_FETCHESbehavior changed for booleans; SQLite/MySQL drivers now return ints/floats as native PHP types more consistently.
Standard library behavior changes
version_compare()no longer accepts undocumented operator abbreviations.- HTML escaping functions now default to
ENT_QUOTES | ENT_SUBSTITUTE(quotes are escaped; malformed UTF-8 replaced).
Deprecations (what to fix early)
Deprecations in 8.1 are “future errors”. Fixing them early pays off when you later move to PHP 8.2+.
Core deprecations
- Passing
nullto non-nullable parameters of built-in functions is deprecated. - Implicit lossy float-to-int conversions are deprecated (array keys, int type declarations in coercive mode, int operators).
- Calling a static element on a trait is deprecated.
- Autovivification from
falseis deprecated ($x = false; $x[] = 1;).
Notable extension deprecations
- Date:
strftime()/gmstrftime()andstrptime()deprecated (preferdate()/date_parse_from_format()/IntlDateFormatter). - Filter:
FILTER_SANITIZE_STRINGandFILTER_SANITIZE_STRIPPEDdeprecated. - Hash:
mhash*()functions deprecated (usehash_*()). - PDO:
PDO::FETCH_SERIALIZEdeprecated.
Extensions: notable changes
This section collects “good to know” changes that are not necessarily breaking but impact behavior and operations.
- Reflection:
ReflectionProperty::setAccessible()/ReflectionMethod::setAccessible()no longer have an effect (always accessible via Reflection). - Phar: SHA256 is used by default for signatures; OpenSSL_SHA256/OpenSSL_SHA512 signatures supported.
- OpenSSL: OpenSSL 3.0 is supported; cipher availability and parameter validation are stricter.
- CLI:
php -awithout readline now errors (instead of a misleading non-interactive mode). - PHPDBG: remote functionality removed.