PHP 7.3: Major Features

PHP 7.3 is a polish-and-safety release in the 7.x line: it smooths daily syntax (flexible heredoc/nowdoc, trailing commas in calls, reference assignments in list() / [] destructuring), adds small high-value helpers (array_key_first(), array_key_last(), is_countable(), hrtime()), and modernizes platform pieces (JSON_THROW_ON_ERROR / JsonException, PASSWORD_ARGON2ID, PCRE2, mbstring upgrades). Plan upgrades around three clusters: string parsing edge cases (heredoc bodies vs closing labels, PCRE character classes), reference semantics (array/property reads, ArrayAccess string keys), and extensions (MySQL fractional seconds, IMAP defaults, cookie name decoding from 7.3.23).

Table of Contents


Flexible Heredoc and Nowdoc

The closing label is no longer required to sit at column 0 with an immediate newline/semicolon dance in the old rigid form: it may be indented, and the same indentation is stripped from every line in the body. That makes embedded SQL, HTML, and CLI help text far more readable in PSR-12–friendly code.

Migration caveat: if the body contains the same token as the closing label (now possibly indented), PHP may treat it as ending the string early—pick unique, long labels (SQL_GET_USER rather than SQL).

$query = <<<SQL
    SELECT id, name
    FROM users
    WHERE active = 1
    SQL;

Trailing commas in function and method calls

Calls may end with a trailing comma—handy for multi-line diffs and generated argument lists:

$this->logger->info(
    'User saved',
    [
        'id' => $user->id,
        'ip' => $request->ip(),
    ],
);

This applies to calls, not to declaring function foo($a,) parameters in 7.3 (parameter list trailing commas arrived on a different schedule—do not confuse the two when reading RFCs).

Reference assignments in list() and []

Destructuring can bind by reference:

[&$head, $middle, &$tail] = $parts;
// same idea with list():
list(&$a, $b) = $pair;

Use this when you intentionally mutate pieces of a shared structure; otherwise prefer values to keep data flow obvious.

instanceof with literals

The left-hand side may be a literal; the result is always false. This exists mainly for consistency and static-analysis friendliness, not for runtime logic you would rely on.

CompileError

CompileError is introduced as a base for certain compile-time failures; ParseError extends it. Initially this shows up most visibly when token_get_all($code, TOKEN_PARSE) hits parse issues—some situations that were fatals may now surface as exceptions in tooling.

New and updated core helpers

  • array_key_first($array) / array_key_last($array) — avoid reset()/end() key hacks; behavior is defined for your array type (see manual for empty arrays).
  • is_countable($value)true for arrays and objects implementing Countable—replaces the common is_array($x) || $x instanceof \Countable guard before count().
  • hrtime($as_number = false) — monotonic high-resolution time (nanoseconds); prefer over microtime(true) for intervals (not wall-clock calendar time).
  • net_get_interfaces() — enumerates network interfaces where supported (platform-dependent).

JSON: JSON_THROW_ON_ERROR and JsonException

Pass JSON_THROW_ON_ERROR to json_encode() / json_decode() to get a JsonException instead of juggling json_last_error(). JSON_PARTIAL_OUTPUT_ON_ERROR still wins when both flags apply.

$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);

Password hashing: Argon2id

When PHP is built with a sufficiently new libargon2, password_hash() accepts PASSWORD_ARGON2ID, giving you Argon2’s i+d hybrid variant alongside existing Argon2i support—subject to build flags and extension availability in your distribution.

Notable extensions & stdlib

  • PCRE: engine upgraded to PCRE2—subtle behavior changes (stricter character-class ranges, etc.); preg_quote() also escapes #.
  • Multibyte (mbstring): Unicode 11, >2GB strings, faster case operations; full case-folding for case-insensitive comparisons; mb_ereg_* named captures aligned closer with PCRE ergonomics (see BC notes if you relied on old match arrays).
  • LDAP: LDAP controls plumbed through search/modify APIs and ldap_parse_result(); option handling for server/client controls improved.
  • MySQLi / PDO MySQL: prepared statements can surface fractional seconds for DATETIME(6) / TIMESTAMP(6) style columns (PDO path especially when native prepares are used).
  • FPM: logging knobs (log_limit, log_buffering, decorate_workers_output); getallheaders() available under FPM.
  • Sessions / cookies: session_set_cookie_params(array $options) and session.cookie_samesite; setcookie() / setrawcookie() array $options including samesite.
  • Stream: IPv6 names from stream_socket_get_name() use bracketed host form (e.g. [::1]:1337).
  • SPL autoload: if an autoloader throws, later autoloaders are skipped (previously exceptions were chained through the whole stack).
  • IMAP: rsh/ssh logins disabled by default—enable only with care (imap.enable_insecure_rsh).

Practical recipes

Guard before count()

if (is_countable($rows)) {
    $n = count($rows);
}

Stable first/last keys

$firstKey = array_key_first($map);
$lastKey = array_key_last($map);

Strict JSON parsing

try {
    $payload = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    // handle or wrap
}

High-resolution timing

$t0 = hrtime(true);
// ...
$elapsedNs = hrtime(true) - $t0;

Backward incompatible changes (migration notes)

Core

  • Flexible heredoc/nowdoc: strings that embed the closing label can change meaning or become parse errors—rename labels or escape content.
  • continue inside switch: now emits a warning (in PHP it behaved like break; in other languages you’d expect continue 2). Audit switch + continue.
  • ArrayAccess with numeric string keys: for literal "123" keys, PHP stops coercing to integer for ArrayAccessoffsetGet("123") is called instead of offsetGet(123). Plain arrays keep their long-standing integral-string key rules.
  • Static properties via reference tricks: you can no longer split inherited static properties using reference assignment—children share the parent storage unless explicitly overridden, as originally intended.
  • References from array/property accesses: references returned from chained array/property reads are unwrapped immediately—you cannot “pause” between read and write to retarget the reference slot the way some edge code did.
  • Unpacking ...$traversable: non-integer keys on Traversable no longer unpack for calls (7.2 accidentally allowed some generators with string keys).
  • Warnings promoted to exceptions (EH_THROW): such exceptions no longer populate error_get_last()—they behave like normal exceptions end-to-end.
  • TypeError messages: wrong types display as int / bool, not integer / boolean.
  • compact(): undefined variables passed by name produce a notice.
  • getimagesize() (and friends): BMP MIME reported as image/bmp (IANA), not image/x-ms-bmp.
  • Cookies (from PHP 7.3.23): incoming cookie names are not URL-decoded (security alignment with later 7.4.x behavior).

BCMath

  • Warnings route through PHP’s standard error handler (not direct stderr writes).
  • bcmul() / bcpow() honor the requested scale more strictly (trailing fractional zeroes may appear where they were previously trimmed).

IMAP

  • rsh/ssh tunneling off unless you explicitly enable imap.enable_insecure_rsh—treat mailbox names as untrusted if you re-enable it.

Multibyte regex (mb_ereg_*)

  • Named captures change match array shape and mb_ereg_replace() substitution syntax—retest regex-heavy legacy code.

MySQLi and PDO MySQL

  • Fractional seconds now visible in bound result paths where the engine provides them—tests comparing exact timestamp strings may need updating.

Reflection

  • String exports use int / bool type spellings in output.

SimpleXML

  • Numeric operations on SimpleXML text nodes choose int vs float more sensibly instead of always coercing through int.

Miscellaneous platform

  • BeOS support removed; ext_skel redesigned (extension authors: update scripts).
  • stream_socket_get_name() IPv6 formatting with brackets.

Deprecations (fix early)

High-signal core items:

  • Case-insensitive define() (true as third argument) and using a different case than the definition when reading the constant.
  • Namespaced function named assert()—avoid; the engine treats assert() specially.
  • Non-string needles in strpos family—cast explicitly to string or use chr() for byte lookups.
  • fgetss(), gzgetss(), SplFileObject::fgetss(), and the string.strip_tags stream filter.
  • FILTER_FLAG_SCHEME_REQUIRED / FILTER_FLAG_HOST_REQUIRED with FILTER_VALIDATE_URL (redundant; implied).
  • image2wbmp() (GD).
  • Normalizer::NONE with ICU ≥ 56 (see INTL notes).
  • Undocumented mbereg_*() aliases—switch to mb_ereg_*().
  • Formal deprecation of pdo_odbc.db2_instance_name INI.

Full official list: migration73 deprecated.

Other changes & operations (build, INI, perf)

  • Syslog INI: syslog.facility, syslog.filter, syslog.ident when error_log=syslog (filter modes include raw from 7.3.8 to mimic older behavior).
  • GC: cyclic garbage collection improvements—memory/time profiles can shift on long-lived workers.
  • var_export() on stdClass uses (object) array (...) style output instead of imaginary __setState.
  • FTP defaults to binary transfers.
  • FILTER_VALIDATE_FLOAT accepts a thousand option for allowed separators (default remains compatible).
  • OpenSSL streams: min_proto_version / max_proto_version and related TLS constants.
  • PDO SQLite: open read-only databases via PDO::SQLITE_ATTR_OPEN_FLAGS + PDO::SQLITE_OPEN_READONLY.
  • XML: external entity ref handler return value respected when built against libxml (may stop parsing).
  • cURL: requires libcurl ≥ 7.15.5.
  • OPcache: stale opcache.inherited_hack directive removed.
  • ODBC: Birdstep / ODBCRouter support removed.

Closing thoughts

PHP 7.3 rewards a focused diff review: grep for continue + switch, compact(, ArrayAccess implementations, heredoc labels, and ...$generator argument unpacking. Re-run regex and date/time integration tests, then roll forward—7.4 will add far larger language features, so clearing 7.3 deprecations early reduces noise on the next jump.