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
- Trailing commas in function and method calls
- Reference assignments in
list()and[] instanceofwith literalsCompileError- New and updated core helpers
- JSON:
JSON_THROW_ON_ERRORandJsonException - Password hashing: Argon2id
- Notable extensions & stdlib
- Practical recipes
- Backward incompatible changes (migration notes)
- Deprecations (fix early)
- Other changes & operations (build, INI, perf)
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)— avoidreset()/end()key hacks; behavior is defined for your array type (see manual for empty arrays).is_countable($value)—truefor arrays and objects implementingCountable—replaces the commonis_array($x) || $x instanceof \Countableguard beforecount().hrtime($as_number = false)— monotonic high-resolution time (nanoseconds); prefer overmicrotime(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)andsession.cookie_samesite;setcookie()/setrawcookie()array$optionsincludingsamesite. - 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.
continueinsideswitch: now emits a warning (in PHP it behaved likebreak; in other languages you’d expectcontinue 2). Auditswitch+continue.ArrayAccesswith numeric string keys: for literal"123"keys, PHP stops coercing to integer forArrayAccess—offsetGet("123")is called instead ofoffsetGet(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 onTraversableno longer unpack for calls (7.2 accidentally allowed some generators with string keys). - Warnings promoted to exceptions (
EH_THROW): such exceptions no longer populateerror_get_last()—they behave like normal exceptions end-to-end. TypeErrormessages: wrong types display asint/bool, notinteger/boolean.compact(): undefined variables passed by name produce a notice.getimagesize()(and friends): BMP MIME reported asimage/bmp(IANA), notimage/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/booltype 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_skelredesigned (extension authors: update scripts). stream_socket_get_name()IPv6 formatting with brackets.
Deprecations (fix early)
High-signal core items:
- Case-insensitive
define()(trueas third argument) and using a different case than the definition when reading the constant. - Namespaced function named
assert()—avoid; the engine treatsassert()specially. - Non-string needles in
strposfamily—cast explicitly to string or usechr()for byte lookups. fgetss(),gzgetss(),SplFileObject::fgetss(), and thestring.strip_tagsstream filter.FILTER_FLAG_SCHEME_REQUIRED/FILTER_FLAG_HOST_REQUIREDwithFILTER_VALIDATE_URL(redundant; implied).image2wbmp()(GD).Normalizer::NONEwith ICU ≥ 56 (see INTL notes).- Undocumented
mbereg_*()aliases—switch tomb_ereg_*(). - Formal deprecation of
pdo_odbc.db2_instance_nameINI.
Full official list: migration73 deprecated.
Other changes & operations (build, INI, perf)
- Syslog INI:
syslog.facility,syslog.filter,syslog.identwhenerror_log=syslog(filter modes includerawfrom 7.3.8 to mimic older behavior). - GC: cyclic garbage collection improvements—memory/time profiles can shift on long-lived workers.
var_export()onstdClassuses(object) array (...)style output instead of imaginary__setState.- FTP defaults to binary transfers.
- FILTER_VALIDATE_FLOAT accepts a
thousandoption for allowed separators (default remains compatible). - OpenSSL streams:
min_proto_version/max_proto_versionand 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_hackdirective 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.