PHP 8.0 feature focus: quality of life improvements

Larry Garfield
Larry Garfield
Director of Developer Experience
14 Oct 2020

Our last article looked at a new way to make null values nicer to work with. PHP 8.0 has a lot of such features that make everyday coding more fun, but there are too many of them to give them all their own articles. So today we’ll cover a grab bag of changes and improvements that aren’t web-shattering but should make working with PHP overall more pleasant.

New string functions

PHP has a huge array of string functions (sorry, too easy), from the very useful to the rather obscure. PHP 8.0 brings three new functions to the table, all of which replace common past idioms.

str_starts_with() and str_ends_with() came as a matched set and do exactly what they say on the tin. Both take a string to check and a partial string to test and return a boolean if the first string begins with (or ends with) the second.

<?php
$needle = 'hello';
if (str_starts_with('hello world', $needle)) {
    print "This must be your first program.";
}

Both functions do an exact match, in ASCII, and have no case-insensitive option as that was deemed to complicate it too much. If you need a case-insensitive check, run strtolower() on both strings first. These functions come to us courtesy of an RFC by Will Hudgins.

In a similar vein, str_contains() is also fairly self-explanatory. It checks if the first string contains the second string anywhere within it at all.

<?php
$needle = 'on';
if (str_contains('peace on earth', $needle)) {
    print "Yes, let's do that.";
}

As with the earlier functions, str_contains() is a case-sensitive ASCII comparison. The RFC for this addition comes from Philipp Tanlak.

All three functions were possible to mimic in user-space via clever uses of strpos(), where “clever” is a polite way of saying “error-prone.” One of the most common newbie PHP developer mistakes (made by many very experienced developers) was to do a str_contains() type check like so:

<?php

$its_in_there = strpos($haystack, $needle) == false;

However, due to PHP’s type juggling, that basic-equality (==) check is insufficient, because a position of 0 (meaning the $needle is the first part of $haystack) would be == false. The check needs to be identity (===), which is easy to forget. It’s also a perfect example of why “return a value or false on error” is a terrible pattern that should never be used, ever.

Fortunately, with PHP 8.0 all of that cleverness is no longer needed, as the trio of new functions type-safely return the boolean value you wanted in the first place.

throw expressions

PHP supports exceptions via the throw keyword. For various internal implementation reasons, throw was implemented as a statement, meaning it didn’t return a value and just “was a thing.”

In PHP 8, throw was converted to an expression, meaning it technically can return a value. That’s not all that useful (throw breaks the current line of execution, by design), but it means throw can now be used in expression places in code.

The best example of where that’s useful are ternaries, null coalesce, and other shortened expression symbols. Consider:

<?php

// $value is non-nullable.
$value = $nullableValue ?? throw new InvalidArgumentException();
 
// $value is truthy.
$value = $falsableValue ?: throw new InvalidArgumentException();

In PHP 7, those are syntax errors. In PHP 8.0, they do what you’d expect: assign the value if possible or throw if the condition fails.

An even more fun combination is mixing it with the new match() expression.

<?php

$value = match($size) {
  0 => throw new ThatsNotASize(),
  1 => throw new ThatsTooSmall(),
  3, 4 => 'small',
  5, 6 => 'medium',
  default => 'large',
};

throw expressions come to us courtesy of Ilija Tovilo.

Non-capturing catch

Speaking of exceptions, there are cases where you want to catch an exception, but what you’ll do in response doesn’t depend on the exception data itself. You can now catch an exception without assigning it to a variable, like so:

<?php

try {
    changeImportantData();
} catch (PermissionException) {
    echo "You don't have permission to do this";
}

That catch will trigger for PermissionException exceptions, but the exception itself won’t be assigned to a value.

Max Semenik was responsible for the contents of this RFC.

Trailing commas in function definitions and closures

A long time ago on a mailing list far far away (that is, back in 2017), there was a proposal to allow a trailing comma in all places where PHP supported a list of values. That, unfortunately, mostly failed, but subsequent RFCs to add comma support to each list construct independently have passed. Go figure. ¯\_(ツ)_/¯

The latest additions for PHP 8 are in function definitions and closures. For function definitions, it’s now possible to put a comma after the last parameter definition, which is mainly useful when many parameters are split across several lines.

<?php
Class Address
{
    public function __construct(
        string $street,
        string $number,
        string $city,
        string $state,
        string $zip,     // This is new.
    ) { 
    // ... 
    }
}

Trailing commas in function calls were already added in PHP 7.3.

This update is thanks to the ever-popular Nikita Popov.

Trailing commas are now also allowed in use clauses in closures, for the same reason:

<?php
$a ='A';
$b = 'B';
$c = 'C';

$dynamic_function = function(
  $first,
  $second,
  $third,       // This is new, as above.
) use (
  $a,
  $b,
  $c,            // This is also now new.
);

This change comes courtesy of Tyson Andre.

Token objects

Finally, we have a change that is a tab obscure but still very important for people doing code analysis or code generation.

PHP’s token_get_all() function lets PHP parse PHP code files and get back an array of arrays, representing the stream of tokens in that file. However, a giant array of arrays is not always the most useful way to represent, well, anything. For one, PHP arrays are very memory hungry. For another, the nested arrays are positional, and the meaning of each position varies with the type of token. That architecture pattern is known in academic literature as “fugly.”

PHP 8.0 introduces a new class, PhpToken, with a corresponding getAll() static method. PhpToken::getAll() is passed a string of PHP code and returns an array of PhpToken objects. Because it’s using objects, this method is faster, uses half as much memory, and is easier to use than token_get_all(). (Tastes great and is less filling!)

PhpToken also includes a few utility methods to get information about the token, such as getTokenName() or isIgnorable(). (The RFC has more details.) There’s even a __toString() implementation that renders the object back to its source code string form. What’s more, it’s also possible to extend the class and add your own custom methods.

<?php

$code = '...'; // Some PHP source code.

class LowerCaseToken extends PhpToken implements Stringable {
    public function getLowerText() {
        return strtolower($this->text);
    }

    public function __toString(): string {
        return $this->getLowerText();
    }
}

$tokens = LowerCaseToken::getAll($code);

print implode($tokens);

In this (extremely contrived) example, LowerCaseToken::getAll($code); returns an array of LowerCaseToken objects. implode() then converts each to a string and concatenates them, with no extra separator. “Converts to a string” means that __toString() is called, which returns a lower-case version of the string representation of the token. The net result is to take a piece of code and force it to all lowercase without otherwise modifying the code or its structure.

This is admittedly not the most practical example, but one could imagine reading in a file of PHP source code, modifying the array in some structured way, and then trivially serializing it back out in modified form, while still respecting the structure and alignment of the text. Alternatively, one could add additional methods to help with advanced parsing for code formatting, static analysis, or other meta-coding tasks.

It should be no surprise that this RFC comes to us courtesy of Nikita Popov. Again.

Tune in next week for another grab-bag of smaller changes that make PHP more predictable and logical but might impact your code.

You can try out pre-release copies of PHP 8.0 today on Platform.sh, with just a one-line change. Give it a whirl, and let us know what your favorite features are.