PHP 8.0 final version has just been released. This is excellent news for all PHP developers out there! Beyond all technical improvements it brings, there are a bunch of new features that are very much awaited.

If you didn’t really follow all of it, let me take you to a tour of new features, in no particular order.

Named arguments

It’s possible to pass function or method’s arguments using their variable name, and by doing so, ignoring their declaration order. We can find a similar feature in Python, with their named arguments.

It’s very useful because it also allows ignoring optional parameters which can be unrelated to what we want to do.

For example, if we want to perform a search where we only care about the case:

function search(string $terms, $sortBy = 'dates', $caseSensitive = false) {}

// PHP 7.x
$results = search('my query', 'dates', true);

// PHP 8.0
$results = search(caseSensitive: true, terms: 'my query');

We can clearly see the benefit of this feature in this example. $sortBy parameter doesn’t need to be explicitly given, so we only rely on the default value defined by the function itself. Therefore, coupling is slightly reduced.

The counterpoint, which has been raised during this feature vote, is that we create a strong dependency on the variable name that didn’t exist before. It will be more difficult to rename parameters, especially in libraries.

Attributes

It might be THE feature awaited in PHP 8.0: attributes - at least by me. It’s also called annotations in other languages such as Java. Currently, most frameworks and libraries have worked around the lack of attributes using PHPDoc, but it’s now officially supported.

Chosen syntax is the same as in Rust, i.e. #[Attribute()].

Example with Symfony routing:

// PHP 7.x
class SomeController
{
    /** @Route("/path", name="action") */
    public function someAction() {}
}

// PHP 8.0
class SomeController
{
    #[Route('/path', name: 'action')]
    public function someAction() {}
}

It is interesting to note that the debates have been quite heated on the best syntax to choose. In reality, the chosen one is not even the one that was voted on in the first place. It was originally <<Attribute()>>, but it turned out that this syntax posed problems in terms of implementation within the language.

Similarly, it was not possible to choose @Attribute(), since the “at” character is already reserved in PHP (to make errors silent, don’t use it!). The other option was @@Attribute().

Note: additional benefit of the chosen syntax for attributes, it’s does not generate syntax error on PHP 7.x as it is interpreted as a comment.

Union types

Union types allow specifying several types on the same parameter, function/method return, or class attribute.

This refines a little bit more type hinting reworked in PHP 7.0, which have already had some improvements since their release (nullable, attributes, etc.).

Example :

// PHP 7.x
/**
 * @param int|float|null $threeshold
 * @return ArrayAccessImplementation|array
 **/
public function getListByPrice($threeshold) {}

// PHP 8.0
public function getListByPrice(int|float|null $threeshold): ArrayAccessImplementation|array {}

It is obviously to be used sparingly, since the advantage of declaring strictly typed methods is to avoid ending up with any type as input/output of them.

Note: it’s not necessary to make a complete section about it, but the mixed type has also been added to the language, which makes it possible to never have undefined typing again, and greatly simplifies the life of linters’ configuration.

Declare class attributes in the constructor

Also called constructor property promotion, the title describes this feature quite well. Class attributes can be directly and only declared in constructor parameters.

Let’s see:

// PHP 7.x
class BlogPost 
{
  protected string $title;
  protected TagList $tags;
  protected DateTimeInterface $createdAt

  public function __construct(string $title, TagList $tags, DateTimeInterface $createdAt) {
    $this->title = $title;
    $this->tags = $tags;
    $createdAt = $createdAt;
  }
}

// PHP 8.0
class BlogPost
{
  public function __construct(
    protected string $title, 
    protected TagList $tags, 
    protected DateTimeInterface $createdAt,
  ) {}
}

In fact, with an advanced IDE such as PHPStorm, that was already more or less the volume of code written. But I have to admit that it’s way more readable that way.

Trailing comma in parameters

You may have noticed a subtle detail in the previous constructor declaration: the last parameter is followed by a comma… and it’s not a typo.

Actually, parameters list now accept a trailing comma, just like arrays, while being called or declared. There is no doubt that I’ll add this rule to my PHPCS configuration.

For the example, take a look at the previous section.

New str_* function

Ten years ago, one of the first versions of Shaarli already included functions startsWith() et endsWith() to check strings. I’ve seen these functions and other derivatives based on strpos() in almost every project I had to work on.

A decade later, 3 new functions are now available in the standard library, and their name are self-explanatory: str_contains, str_starts_with() et str_ends_with().

Usage:

// All return true.
str_contains('All work and no play makes Jack a dull boy', 'Jack');
str_starts_with('All work and no play makes Jack a dull boy', 'All work');
str_ends_with('All work and no play makes Jack a dull boy', 'a dull boy');

New interface Stringable

Stringable is a new interface of the standard library. To implement it, you just need to include a __toString() method which will be called when the object is converted to string.

In fact, all of this already exists, and you do not need to specify explicitly implements Stringable. It is just used to improve type hinting.

To be clear, this new interface allows you to do this:

class Greeting
{
  public function __toString(): string {
    return 'hi!';
  }
}

class NotGreeting
{
  public function __toString(): string {
    return 'come back later';
  }
}

class Greeter
{
  public function greet(Stringable $greeting): string {
    return 'Greeting: ' . $greeting;
  }
}

$greeter = new Greeter();
echo $greeter->greet(new NotGreeting()); // 'Greeting: come back later'
echo $greeter->greet(new Greeting()); // 'Greeting: hi!'

In PHP 7.x, it wouldn’t have been possible to specify the type of greet() method parameter without grouping NotGreeting and Greeting under the same interface. This is now available by default.

Nullsafe operator

One of my favorite features of PHP 7.0 was null coalesce operator. There is nothing more counterproductive than writing 8 lines of code of if to check that isset, array_key_exists, !== null or other instanceof.

PHP 8.0 brings me joy with a new nullsafe operator, which allows calling methods on a null object.

// PHP 7.x
if (!$blogPost instanceof BlogPost || $blogPost->getCreatedAt() === null) {
  return null;
}
return $blogPost->getCreatedAt()->format('Y-m-d');

// PHP 8.0
return $blogPost?->getCreatedAt()?->format('Y-m-d');

And if $blogPost or $createdAt are null, it simply returns null, without generating a fatal error.

Raise exceptions in expressions

While we are talking about small improvements which increase productivity and code readability, it is now possible to raise exception in any expression. I never understood why it wasn’t already possible - maybe due to implementation issues in PHP source code - but it is now!

// PHP 7.x
$item = $collection->getItem($id);
if ($item === null) {
  throw new ItemNotFoundException();
}
return $item;

// PHP 8.0
return $collection->getItem($id) ?? throw new ItemNotFoundException();

match, cousin of switch

I relatively rarely use switch {} statements, maybe unconsciously because it’s quite verbose… switch, case:, break, and repeat. PHP 8.0 now includes match statement which syntax is similar to what exists in Java.

Concretely, it meets the same need as the switch, but it’s more readable and compact with an array-like syntax.

Let’s see:

// PHP 7.x
switch ($locale) {
  case 'en_GB':
  case 'en_US':
    $language = Languages::ENGLISH;
    break;
  case 'de_DE':
    $language = Languages::GERMAN;
    break;
  default:
    $language = Languages::FRENCH;
}

// PHP 8.0
$language = match ($locale) {
  'en_GB', 'en_US' => Languages::ENGLISH,
  'de_DE' => Languages::GERMAN,
  default => Languages::FRENCH,
};

JIT (Just In Time) compiler

JIT compiler is a feature of PHP 8.0 which compiles re-usable PHP code at runtime, in a similar way as how cache operates. This can lead to great performances improvements in certain contexts.

It’s very promising, but it’s quite a complex feature that I’d rather develop in a dedicated article. Stay tuned!

static return type

I won’t write about the entire self vs static argument here, but know that it is now possible to use static return type hint, in addition to self which was already available.

class Entity {
  public function setProperty(int $property): static {
    $this->property = $property;

    return $this;
  }
}

Use ::class on objects

It is now possible to call ::class directly on objects and not only on classes. Again, a small improvement that simplifies developers’ life.

// PHP 7.x
$className = get_class($myObject);
$className = MyClass::class;

// PHP 8.0
$className = $myObject::class;
$className = MyClass::class;

DateTime createFromInterface()

Have you already tried to manipulate DateTimeInterface objects, create DateTimeImmutable objects and work on all of that? No? Well, you are lucky, because 7.x implementation has a hole in it, and you might have lost some hair.

PHP 8.0 just fixed that by adding a method called createFromInterface() to classes DateTime and DateTimeImmutable. This method allows to easily convert object from one class to the other.

Example (to at least let you get the idea):

public function addMonth(DateTimeInterface $dateTime): DateTimeImmutable {
  $dateTime = DateTime::createFromInterface($dateTime);
  $dateTime->add(new DateInterval('P1M');

  return DateTimeImmutable::createFromInterface($dateTime);
}

Conclusion

There are already quite a few things to get your teeth into. This article is not supposed to be a complete release note so we will stop here.

Regarding PHP 8.0 breaking changes, there are not a lot of them. It has always been a strong will of PHP developers - the counterpart being that this can slow down developments.

To sum it up, PHP 8.0 upgrade should go smoothly if you don’t have twisted the language with complicated syntax, except for one thing: a fair number of warnings and notices have been replaced by real errors. You may have to review your error handling system then.

Also, if we want to be honest here, upgrading PHP version is usually more an issue of dependencies handling that the language itself. You should obviously wait for a few weeks for things to stabilize.

A few bonus changes to conclude this:

  • Private methods can no longer be declared as final, which didn’t make sense anyway.
  • It is now possible to catch an exception without using it and without declaring its associated variable.
try {
} catch (SpecificException) {
  // Do something not related to the exception variable
}
  • Abstract method signature in Trait now must be strictly respected in class that use them.
  • WeakMap implementation has been added in PHP 8.0. I will probably come back to it in a dedicated article.