PHP 7.3: What's New and Changed

Version StatusSupported
Release Date2018-12-06

PHP 7.3 brings several Quality-of-Life improvements to PHP. Major improvements include the flexible Heredoc/Nowdoc syntax, a high-resolution timer, is_countable() function to help with the numerous PHP notices raised because PHP 7.2 deprecated using count() function on uncountable values, list() improvements, and new Argon2ID password hashing algorithm.

This version does not bring major syntax improvements to the table. It cleans up a lot of legacy aliased functions, and brings modern password hashing algorithm in continuation of the Argon2I introduced in PHP 7.2.

Except for the flexible heredoc/nowdoc syntax and improvements in list() construct supporting references, the rest of the major changes are available as polyfills.

Introduced is_countable() function

TypeNew Feature

PHP 7.2 deprecated quite a lot of functions and buggy use cases. In PHP 7.2, if you call count() on a variable that is not "countable", PHP shows a warning about it. A common fix was to check if the given variable is "countable" before calling count() on it.

A "countable" variable is either an array, or an object of a class that implements \Countable interface. Because there can be a lot of boilerplate code, PHP 7.3 now has a new is_countable() function that returns true if the passed variable is... well... countable.

Polyfill

I have put together a polyfill for is_countable() if you want to use this on pre-PHP 7.3 code.

Here is a simple yet conforming polyfill if you cannot use PHP 7.3 immediately and prefer to not use the package above.

if (!function_exists('is_countable')) {
        function is_countable($var) { 
            return is_array($var) 
                || $var instanceof Countable 
                || $var instanceof ResourceBundle 
                || $var instanceof SimpleXmlElement; 
        }
    }

Backwards compatibility impact

Unless you have declared your own is_countable function, there shouldn't be any issues.

RFC Externals.io discussion Implementation

Heredoc and Nowdoc syntax requirements are more relaxed

TypeNew Feature

Heredoc and Nowdoc syntax, that helped to use multi-line strings had rigid requirements that the ending identifier should be the first string appearing in a new line.

For example:

$foo = <<<IDENTIFIER
the crazy dog jumps over the lazy fox
"foo" bar;
IDENTIFIER

In here, the last IDENTIFIER must be the first string in a new line for this to work. In addition, there must not be any other characters after the last IDENTIFIER (other than a semi colon, which is optional).

The RFC for PHP 7.3 suggested to remove the requirement above with the goal of making the code more readable. Before this RFC, one had to break the indentation used in the rest of the code just so here/now doc tokens can be used. The RFC suggests making these changes to heredoc/nowdoc syntax:

  1. The ending token no longer needs to be the first string of the line.
  2. The ending token can be indented with spaces or tabs
  3. The white-space characters (space or tab) must not be intermixed. If you do so, you will get a Parse error: Invalid indentation - tabs and spaces cannot be mixed in .. on line ...
  4. The exact number of spaces/tabs used in the ending token will be stripped off from the contents within the heredoc/nowdoc expression.
  5. If the number of white-space characters used in the ending token is greater than any of the white-space characters within the expression, you will get Parse error: Invalid body indentation level (expecting an indentation level of at least ..) in .. on line ..
  6. You can add more expressions after the ending token without any errors
$foo = ['foo', 'bar', <<<EOT
  baz
    -  hello world! --
  ahoy
  EOT, 'qux', 'quux'
];

var_dump($foo);

the output would be:

array(5) {         
  [0]=>            
  string(3) "foo"  
  [1]=>            
  string(3) "bar"  
  [2]=>            
  string(29) "baz  
  -  hello world! --
ahoy"
  [3]=>
  string(3) "qux"
  [4]=>
  string(4) "quux"
}  

Notice how the white-spaces used in the heredoc declaration did not make in to the var_dump()'d output, and we continued to add more element to the $foo array after the EOT token.

Backwards compatibility impact

As long as you don't have any heredox/nowdoc string literals that contain the same token as the first positive character in a line, you are golden.

$foo = <<<HELLO
  HELLO_WORLD <-- this will not terminate the string literal
  HELLOWORLD <-- this one will not either. 
  HELLO WORLD<-- this one will
HELLO;

If you have any heredoc/nowdoc syntax similar to the above, note that with PHP 7.3, PHP assumes the HELLO terminates the string literal, and will throw an error on the next line. In earlier versions, the HELLO WORLD is not considered the ending token of the heredoc. Thanks to /u/ImSuperObjective2 on reddit for pointing this out.

RFC Externals.io discussion Implementation

Option to make `json_encode` and `json_decode` throw exceptions on errors

TypeNew Feature

This is one of my favorites. For all these years, json_encode() and json_decode() functions were silent about errors in the provided PHP variables or the JSON string. This was prone to buggy code because not everyone knows this edge case. This was even criticized in the famous PHP: A Fractal bad design post.

json_decode returns null for invalid input, even though null is also a perfectly valid object for JSON to decode to—this function is completely unreliable unless you also call json_last_error every time you use it.

It took us 6 years since that blog post, but we now have an option to make PHP throw an error on JSON operation failures:

try {
    json_decode("{", false, 512, JSON_THROW_ON_ERROR); 
}
catch (\JsonException $exception) {
    echo $exception->getMessage(); // echoes "Syntax error" 
}

The new \JsonException is a subclass of \Exception, and both JSON_THROW_ON_ERROR constant and JsonException exception are declared in the global namespace. I highly recommend you start to use this feature. There are contributed libraries, such as daverandom/exceptional-json that brought similar functionality until PHP 7.2. With this feature now in PHP core, you can remove this package or tons of ugly boilerplate code calling json_last_error() every-time you make a JSON operation.

Backwards compatibility impact

None, unless you have declared your own exception and/or constants with conflicting names.

RFC Externals.io discussion Implementation

Introduced `array_key_first()` and `array_key_last()` functions

TypeNew Feature

PHP has over 75 array functions, but there was no easy way to retrieve the first and last keys of an array without modifying the array pointer or retrieving all keys of the array (with array_keys()) and then retrieving the first or last values of the array.

In PHP 7.3, there are two new functions: array_key_first and array_key_last that let you grab the first and last keys of the given array.

The RFC also proposed array_value_first() and array_value_last() functions, but this portion of the RFC didn't get voted.

Polyfill

If you cannot immediately upgrade to PHP 7.3, you can get the same functionality with the two polyfills below.

if (!function_exists('array_key_first')) {
    function array_key_first(array $array) {
        foreach ($array as $key => $value) { 
            return $key; 
        }
    }
}

if (!function_exists('array_key_last')) {
        function array_key_last(array $array) { 
            end($array); 
            return key($array); 
        }
}

Backwards compatibility impact

Unless you have declared your own array_key_first and array_key_last functions, there shouldn't be any issues.

RFC Externals.io discussion Implementation

Allow trailing comma in function and method calls

TypeChange

This is a simple change, which suggests allowing trailing commas in function and method calls. This does not affect declarations.

For example, the following syntax would be allowed:

// regular functions.  
foo('bar', 'baz',); // Notice the trailing comma after 'baz'.

In pre-PHP-7.3, the snippet above throws an error PHP Parse error: syntax error, unexpected ')' in .. on line ..

You cannot use more than one commas at the end or use commas to skip arguments - the advantage of this change is mainly for those functions with variadic parameters. This change also makes the array syntax (which allows trailing commas already) consistent.

Note that you cannot use this in function/method declarations; this is wrong:

function foo($bar, $baz, ) { // Not allowed.
}

Backwards compatibility impact

None - Your existing code will continue to work. If you have any function calls that accept variadic parameters and believe that you could make diffs cleaner with this, I'd suggest you go ahead and add trailing commas to the calls. If you put a trailing comma for every function call, you are clearly overdoing it.

RFC Externals.io discussion Implementation

References in `list()`

TypeChange

The list() construct is useful to quickly create multiple variables from an array, with array keys being the variable name. Until PHP 7.3, is was not possible to assign the variables by reference. Prior to PHP 7.3, the following snippet would throw a fatal error:

$arr = ['apple', 'orange'];
list($a, &$b) = $arr;
$b = 'banana';
echo $arr[1];
// Fatal error: [] and list() assignments cannot be by reference in .. on line ..

With PHP 7.3, you will be able do so, and the output from echo $arr[1] will be banana. [$a, &$b] = $arr syntax will get this feature too.

You still cannot reference non-referencable variables: list($a, &$b) = [12, 14] will throw Fatal error: Cannot assign reference to non referencable value in .. on line ...

Minor nitpick: while "referencable" is an acceptable spelling, "referenceable" is used a lot more widely.

Backwards compatibility impact

None. Instead of using list() assignment to populate multiple variables, I would suggest you to use value objects to make things cleaner. They will be passed by reference anyway and will make your code much cleaner.

RFC Externals.io discussion Implementation

PCRE to PCRE2 migration

TypeChange

PHP uses Perl Compatible Regular Expressions, or PCRE in short, as the underline library for Regular Expressions. Until PHP 7.2, PHP used the 8.x versions of the legacy PCRE library, and from PHP 7.3, PHP will use PCRE2. Note that PCRE2 is considered to be a new library although it's based on and largely compatible with PCRE (8.x).

PCRE2 library is more aggressive in pattern validation, and may result on your existing patterns being not compiling anymore under PCRE2.

For example, the following snippet will fail with PHP 7.3:

preg_match('/[\w-.]+/', '');

PHP will now throw a warning Warning: preg_match(): Compilation failed: invalid range in character class at offset 3.

The problem is with the pattern: PCRE2 is strict that the hyphen needs to be moved to the end, or escaped for this to work.

preg_match('/[\w\-.]+/', '');

The above code should compile just fine with PHP 7.3 as well as older versions. Note how this new pattern escapes the hyphen (- to \-). This is perhaps the most common problem you'd run into with existing pattern incompatibilities.

This is a quite subtle change, but there is a chance of things going wrong. Error messages are quite useful: which shows the exact offset of the offending pattern. Make sure to thoroughly test your Regular Expression patterns. There are software, such as Regex Buddy that can help you with conversion to PCRE2 syntax. For more information, see PCRE2 syntax and legacy PCRE syntax. Although this can appear annoying at the first glance, PCRE2 is just being less forgiving about regular expressions that are not 100% compliant to begin with.

Backwards compatibility impact

Because PCRE2 is more nagging and stricter about the patterns, some of your preg_match() and similar calls might not work anymore. The fix can range from a simple update to the pattern (for example escaping hyphens inside a character class), to a rewrite of the pattern. Make sure to run your test suite to detect the errors in compilation.

RFC Externals.io discussion Implementation

Deprecate case-insensitive constants

TypeDeprecation

PHP's define() function has a feature that lets you declare a constant in a case-insensitive way. You have to explicitly declare the constant case-sensitive by passing the third parameter of the function as true. This is not enabled by default, and it's not definitely not consistent with the other approaches to declare constants such as with the const keyword.

define('Foo', 'Bar', true);

The above code will throw a deprecation notice:

Deprecated: define(): Declaration of case-insensitive constants is deprecated in ...

Furthermore, when you try to access constants that are declared case-insensitive (e.g as FOO), you will see a quite helpful warning as well:

Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "Foo"

Backwards compatibility impact

You will have to search for your code base that declares case-insensitive constants, and where they are used, and fix them to be consistent everywhere.
It is highly unlikely that you will have any problems with this, because you had to explicitly declare a constant case-insensitive. I could not search GitHub for existing code because they don't allow regular expressions of any sort. However, for Drupal and Wordpress at least (two quite old and mature projects in PHP), there are no case-insensitive constants.

RFC Externals.io discussion Implementation

Deprecate `image2wbmp()` function

TypeDeprecation

image2wbmp() function from GD extension is used to output the WBMP (Wireless Bitmap) format of an image.

This function is deprecated in PHP 7.3 in favor of imagewbmp function. If you are using image2wbmp function, simply replace the function name with imagewbmp and you are good to go!

There are over 5,500 hits for PHP image2wbmp in Github, vs over 39,300 hits for the imagewbmp function. It looks like the PHP team deprecated the less used function over to minimize the impact.

Backwards compatibility impact

If you are using image2wbmp function, replace the calls with imagewbmp. Look for automated tools that can fix this for you as well.

RFC Externals.io discussion Implementation

Deprecate `FILTER_FLAG_SCHEME_REQUIRED` and `FILTER_FLAG_HOST_REQUIRED` flags used with `FILTER_VALIDATE_URL`

TypeDeprecation

When you use filter_var($var, FILTER_VALIDATE_URL), there are two additional flags you could use to ensure the URL is strictly validated: FILTER_FLAG_SCHEME_REQUIRED and FILTER_FLAG_HOST_REQUIRED. These two flags are deprecated in PHP 7.3 onwards.

Since PHP 5.2.1, these two flags are implicitly enforced regardless they were used or not. If you have any code that uses these flags, simply remove them and you will be good. At this point, there are over 5,000 code search results on GitHub with these flags being used, and so could be yours.

Backwards compatibility impact

Because these two flags are now deprecated, you will see a notice like this:

Deprecated: filter_var(): explicit use of FILTER_FLAG_SCHEME_REQUIRED and FILTER_FLAG_HOST_REQUIRED is deprecated in ...

All you have to do is simply remove these two flags, because both of them are implied when you use FILTER_VALIDATE_URL.

RFC Externals.io discussion Implementation