What's new and changing in PHP 7.3

Tue, 2018-04-17 23:46

This is a live document (until PHP 7.3 is released as generally available) on changes and new features to expect in PHP 7.3, with code examples, relevant RFCs, and the rationale behind them, in their chronological order.

Syntax

  1. Heredoc and Nowdoc syntax requirements are more relaxed
  2. Allow trailing comma in function and method calls
  3. References in list()
  4. Deprecations

  5. Deprecate image2wbmp() function
  6. Deprecate FILTER_FLAG_SCHEME_REQUIRED and FILTER_FLAG_HOST_REQUIRED flags used with FILTER_VALIDATE_URL
  7. Deprecate case-insensitive constants
  8. New features

  9. Option to make json_encode and json_decode throw exceptions on errors
  10. Introduced is_countable() function
  11. Introduced array_key_first() and array_key_last() functions
  12. Changes

  13. PCRE to PCRE2 migration

Heredoc and Nowdoc syntax requirements are more relaxed

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 above requirement 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
  7. Here is an example snippet that takes advantage of this new feature without violating the newly enforced rules:

    $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.

    RFC, Externals.io discussion, Implementation

    Backwards compatibility impact
    As long as you don't have any heredox/nowdoc string litrals 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.

    Allow trailing comma in function and method calls

    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, ) { // nah, you can't do this.
    }

    RFC, Externals.io discussion, Implementation

    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.

    Option to make json_encode and json_decode throw exceptions on errors

    This is one of my favorites. For all these years, json_encode() and json_decode() 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.

    RFC, Externals.io discussion, Implementation

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

    References in list()

    The list() is useful to quickly assign a variable from an array of variables. 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"! Imagine happy minions from Despicable Me! The [$a, &$b] = $arr syntax will get this feature too of course.

    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.

    RFC, Externals.io discussion, Implementation

    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.

    Introduced is_countable() function

    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 whose class implements \Countable interface. Because there can be a lot of boilerplate code, PHP 7.3 now has new is_countable() function that returns if the passed variable is... well... countable.

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

    RFC, Externals.io discussion, Implementation

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

    Deprecate image2wbmp() function

    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. It looks like the PHP team deprecated the less used function over to minimize the impact.

    RFC, Externals.io discussion, Implementation

    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.

    Introduced array_key_first() and array_key_last() functions

    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.
    There are two new functions, array_key_first and array_key_last that lets 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.

    RFC, Externals.io discussion, Implementation

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

    PCRE to PCRE2 migration

    PHP use 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).

    The new 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 not 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 things can go wrong. Notice how helpful the error message, 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,
    RFC, Externals.io discussion, Implementation

    Backwards compatibility impact
    Because PCRE2 is more nagging and strict 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), or a rewrite of the pattern. Make sure to run your test suite to detect the errors in compilation.

    Deprecate FILTER_FLAG_SCHEME_REQUIRED and FILTER_FLAG_HOST_REQUIRED flags used with FILTER_VALIDATE_URL

    This is a straight forward one. 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.
    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.

    RFC, Externals.io discussion, Implementation

    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.

    Deprecate case-insensitive constants

    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