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

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 ?Type or a default value if “unset” is valid.
  • callable is 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.preload and often opcache.preload_user.
  • mb_str_split(): like str_split() but on code points.
  • proc_open(): command as array (no shell), plus redirect / null descriptors.
  • 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* accepts flags (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, or resource like $x['k'] emits a notice.
  • get_declared_classes(): no longer lists anonymous classes until instantiated.
  • fn: reserved keyword for class/function names.
  • <?php at EOF without newline: now parsed as an open tag (was ambiguous with short_open_tag).
  • Stream wrappers: include/require on streams may call stream_set_option(STREAM_OPTION_READ_BUFFER)—implement or return false to avoid warnings.
  • Serialization: o format removed (only mattered for crafted payloads).
  • Password constants: PASSWORD_* identifiers are strings ('2y', 'argon2id', …), not ints—code comparing to 1/2/3 breaks.
  • htmlentities(): notice when encoding falls back to htmlspecialchars behavior.
  • fread/fwrite: return false on failure (not ''/0); failure may notice.
  • BCMath: warns on ill-formed numeric strings (still treated as zero).
  • CURL: serializing CURLFile throws earlier; curl_version($nonDefault) warns/ignores non-CURLVERSION_NOW.
  • Date: dumping DateTime* no longer leaves stray properties; DateInterval comparison warns and is always false.
  • Intl: idn_to_ascii / idn_to_utf8 default variant is INTL_IDNA_VARIANT_UTS46.
  • MySQLi: embedded server removed; undocumented $mysqli->stat property removed—use mysqli::stat().
  • OpenSSL: openssl_random_pseudo_bytes throws like random_bytes on failure; $crypto_strong is true when no throw.
  • PCRE: with PREG_UNMATCHED_AS_NULL, trailing unmatched groups are null (stable $matches size).
  • PDO: serializing PDO/PDOStatement throws Exception (not PDOException).
  • Reflection: serializing reflection objects throws; some modifier constant numeric values changed.
  • SPL / ArrayObject: get_object_vars behavior changed unless STD_PROP_LIST; SplPriorityQueue::setExtractFlags(0) throws immediately.
  • Tokenizer: unexpected bytes become T_BAD_CHARACTER instead 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 objectsisset() / property_exists().
  • implode($parts, $glue) reverse order → implode($glue, $parts).
  • ReflectionType::__toString() and Reflection*::export() → use APIs like ReflectionNamedType::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 ./configure flags no longer take =DIR; use PKG_CONFIG_PATH or FOO_CFLAGS/FOO_LIBS.
  • Bundled libs removed: SQLite3, Zip, onig (mbregex)—system libraries required; hash always enabled.
  • CSV: empty string $escape disables PHP’s escape mechanism; str_getcsv() aligned.
  • GD: imagecropauto() behavior synced with system libgd; default mode changed; -1 width/height for imagescale() aspect scaling.
  • PEAR: not installed by default (--with-pear optional, deprecated).
  • Performance: opcode for array_key_exists() when resolved; UTF-8 preg_match validates 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.