PHP 7.4: Major Features
PHP 7.4 is the last 7.x feature release before the 8.0 jump: it adds typed properties, arrow functions, limited variance, and everyday syntax sugar (??=, array spread, numeric separators). It also ships FFI, OPcache preloading, and a modern serialization pair (__serialize / __unserialize). Plan the upgrade around three risks: typed property initialization, password algorithm constants (now strings), and extension/build changes (SQLite/Zip no longer bundled, pkg-config migration).
Table of Contents
- Typed properties
- Arrow functions
- Covariance & contravariance
- Null coalescing assignment (
??=) - Spread operator in arrays
- Numeric literal separator
- Weak references
__toString()may throw- Serialization:
__serialize/__unserialize - Notable extensions & stdlib
- Practical recipes
- Backward incompatible changes (migration notes)
- Deprecations (fix early)
- Other changes & operations (build, INI, perf)
Typed properties
Class properties can declare types. Uninitialized typed properties must not be read before assignment—you get Typed property ... must not be accessed before initialization.
final class User
{
public int $id;
public string $name;
public ?string $nickname = null;
}
Notes from real code:
- Use
?Typeor a default value if “unset” is valid. callableis not allowed as a property type (context-dependent).- You cannot default a property to
new SomeClass()at declaration time in the same way as scalars; object typed properties are constrained (see manual).
Arrow functions
Arrow functions (fn) capture variables by value from the enclosing scope and return a single expression—ideal for short callbacks.
$factor = 10;
$nums = array_map(fn($n) => $n * $factor, [1, 2, 3]);
fn is reserved: you cannot name a function or class fn (method/constant names are still allowed).
Covariance & contravariance
Return types can be covariant and parameter types contravariant in inheritance, with limits. Full variance works best with autoloading; in a single file only non-cyclic references are possible because classes must be known before use.
Null coalescing assignment (??=)
Assign only if the left-hand side is unset or null:
$config['debug'] ??= false;
Spread operator in arrays
Unpack iterables inside array literals:
$base = [1, 2];
$all = [0, ...$base, 3]; // [0, 1, 2, 3]
Pairs well with array_merge(...$arrays) now that array_merge() may be called with no arguments (returns []).
Numeric literal separator
Underscores improve readability and do not change the value:
$million = 1_000_000;
$hex = 0xFF_FF_FF;
Weak references
WeakReference lets you hold a reference that does not keep an object alive—useful for caches and graphs without leaking memory.
__toString() may throw
Throwing from __toString() is allowed (previously fatal). Some previous recoverable fatals became Error exceptions—audit string casting paths.
Serialization: __serialize / __unserialize
New hooks replace ad hoc patterns and supersede Serializable long-term:
final class Point
{
public function __serialize(): array
{
return ['x' => $this->x, 'y' => $this->y];
}
public function __unserialize(array $data): void
{
$this->x = $data['x'];
$this->y = $data['y'];
}
}
SPL types like ArrayObject can emit new payloads readable on 7.4+ but not older PHP.
Notable extensions & stdlib
- FFI: call C libraries from PHP (powerful; sandbox and security review required).
- OPcache preloading: load a set of files once at startup for lower autoload cost—needs
opcache.preloadand oftenopcache.preload_user. mb_str_split(): likestr_split()but on code points.proc_open(): command as array (no shell), plusredirect/nulldescriptors.strip_tags(): allowed tags as array of names.- PDO: user/password in DSN for more drivers;
??in SQL escapes a literal?(e.g. PostgreSQL JSON?). - PCRE:
preg_replace_callback*acceptsflags(PREG_OFFSET_CAPTURE,PREG_UNMATCHED_AS_NULL). - Password: Argon2 via sodium when built without libargon.
Practical recipes
Safe defaults with ??=
$options['timeout'] ??= 30;
Arrow function in a sort
usort($rows, fn($a, $b) => $a['score'] <=> $b['score']);
Spread-merge of dynamic lists
$merged = array_merge(...$chunks);
Backward incompatible changes (migration notes)
Core
- Array access on non-arrays: using
null,bool,int,float, orresourcelike$x['k']emits a notice. get_declared_classes(): no longer lists anonymous classes until instantiated.fn: reserved keyword for class/function names.<?phpat EOF without newline: now parsed as an open tag (was ambiguous withshort_open_tag).- Stream wrappers:
include/requireon streams may callstream_set_option(STREAM_OPTION_READ_BUFFER)—implement or returnfalseto avoid warnings. - Serialization:
oformat removed (only mattered for crafted payloads). - Password constants:
PASSWORD_*identifiers are strings ('2y','argon2id', …), not ints—code comparing to1/2/3breaks. htmlentities(): notice when encoding falls back tohtmlspecialcharsbehavior.fread/fwrite: returnfalseon failure (not''/0); failure may notice.- BCMath: warns on ill-formed numeric strings (still treated as zero).
- CURL: serializing
CURLFilethrows earlier;curl_version($nonDefault)warns/ignores non-CURLVERSION_NOW. - Date: dumping
DateTime*no longer leaves stray properties;DateIntervalcomparison warns and is alwaysfalse. - Intl:
idn_to_ascii/idn_to_utf8default variant isINTL_IDNA_VARIANT_UTS46. - MySQLi: embedded server removed; undocumented
$mysqli->statproperty removed—usemysqli::stat(). - OpenSSL:
openssl_random_pseudo_bytesthrows likerandom_byteson failure;$crypto_strongistruewhen no throw. - PCRE: with
PREG_UNMATCHED_AS_NULL, trailing unmatched groups arenull(stable$matchessize). - PDO: serializing
PDO/PDOStatementthrowsException(notPDOException). - Reflection: serializing reflection objects throws; some modifier constant numeric values changed.
- SPL /
ArrayObject:get_object_varsbehavior changed unlessSTD_PROP_LIST;SplPriorityQueue::setExtractFlags(0)throws immediately. - Tokenizer: unexpected bytes become
T_BAD_CHARACTERinstead of holes. - Cookies (from 7.4.11): cookie names are no longer URL-decoded.
Deprecations (fix early)
High-signal items:
- Nested ternaries without explicit parentheses (except the unambiguous middle-operand form).
- Curly brace offsets
$str{0}/$arr{0}→ use[]. (real)/is_real()→(float)/is_float().array_key_exists()on objects →isset()/property_exists().implode($parts, $glue)reverse order →implode($glue, $parts).ReflectionType::__toString()andReflection*::export()→ use APIs likeReflectionNamedType::getName()and string casting of reflection objects.allow_url_include,FILTER_SANITIZE_MAGIC_QUOTES, legacy LDAP paged helpers, and many small functions (money_format(),hebrevc(), …)—see migration74 deprecated.
Other changes & operations (build, INI, perf)
zend.exception_ignore_args: new INI (default may hide function arguments from exception stack traces—check error reporting pipelines).opcache.preload_user: user for preload when not root.- pkg-config migration: many
./configureflags no longer take=DIR; usePKG_CONFIG_PATHorFOO_CFLAGS/FOO_LIBS. - Bundled libs removed: SQLite3, Zip, onig (mbregex)—system libraries required; hash always enabled.
- CSV: empty string
$escapedisables PHP’s escape mechanism;str_getcsv()aligned. - GD:
imagecropauto()behavior synced with system libgd; default mode changed;-1width/height forimagescale()aspect scaling. - PEAR: not installed by default (
--with-pearoptional, deprecated). - Performance: opcode for
array_key_exists()when resolved; UTF-8preg_matchvalidates string once across calls.
Closing thoughts
Treat PHP 7.4 as the bridge release: adopt typed properties and modern serialization before 8.0’s stricter errors. In parallel, grep for integer password algorithms, fn identifiers, $var{idx}, and ArrayObject patterns—and re-test streams, OpenSSL random, and PDO/Reflection serialization in integration tests.