php[architect] logo

Want to check out an issue? Sign up to receive a special offer.

The Workshop: What’s New in PHP 7.4

Posted by on November 13, 2019
By Joe Ferguson

PHP 7.4 brings typed properties, arrow functions, coalesce assignment operators, and more. PHP 7.4.0RC4 was released on October 17th. There’s still plenty of time as the current planned date for general availability of 7.4 is November 28th, 2019, according to the PHP 7.4 timetable.

Get the Full Issue

This article was published in the November 2019 issue of php[architect] magazine. Download the Free Article PDF to see how it looks in the published magazine.

If you'd like more articles like this one, become a subscriber today!. You can get each monthly issue in digital and print options or buy individual issues.

Subscribe

Typed Properties

In January and February 2019 I wrote a two-part series The Road to PHP 7.3 where we used static analyzers Phan and PHPStan to analyze our code to prepare for upgrading PHP versions. In this series, we covered the idea of adding static typing to our code allowed static analyzers to know more about our code and enabled these tools to help us find bugs without running the code itself. We have been able to type-hint objects and interface variables since PHP 5, PHP 7 introduced scalar type hints, and 7.2 saw the addition of the object type.

The next step for the ability to strongly type your PHP application is coming in 7.4 in the form of typed properties. A benefit of strongly typed code is we can use a static analyzer such as PHPStan or Phan to find potential errors without executing any code. However, these tools only know what they can read in our source. If we add more types to our applications, these static analysis tools become much smarter in finding bugs and highlighting problems in code. If we look back at our PHP Easy Math project, we can see our Addition class is using annotations to tell our IDE what our parameter and return types should be in Listing 1.

Listing 1

<?php

namespace EasyMath;

class Addition
{
   /**
    * Sum 2 numbers
    * @param float $x
    * @param float $y
    * @return float
    */
   public function add($x, $y): float {
      return $x + $y;
   }
}

We could refactor our code to resemble Listing 2.

Listing 2

<?php

namespace EasyMath;

class Addition
{
   /** @var int $x */
   private $x;
   /** @var int $y */
   private $y;

   public function __construct(int $x, string $y) {
      $this->x = $x;
      $this->y = $y;
   }

   public function add(): int {
      return $this->x + $this->y;
   }
}

Granted, this does change how we call our class by shifting our parameters from the method to the class; there’s still a bit of cruft there with our DocBlock annotations type hinting our parameters $x and $y.

PHP 7.4 and typed properties allow us to remove those annotations completely while still retaining our types, as you can see in Listing 3.

Listing 3

<?php

namespace EasyMath;

class Addition
{
   public int $x;
   public int $y;

   public function __construct(int $x, string $y) {
      $this->x = $x;
      $this->y = $y;
   }

   public function add(): int {
      return $this->x + $this->y;
   }
}

This change supports all types except for void and callable. The PHP internals team decided void was not useful and unclear in many cases. callable was not supported due to the requirement of context around its use.

Arrow Functions

The arrow functions RFC brings the short function syntax to PHP 7.4. If you’ve worked with modern JavaScript, you may already be aware of this syntax, as many frameworks have begun to leverage it for closures. I was excited to see this RFC included in PHP because I’m a fan of its usage in JavaScript. The result is a more readable code without as much boilerplate, which adds to readability in its own right.

If we dive into an example of using array_map to pull array values from keys this would be the code you’d write today in PHP 7.x:

function array_values_from_keys($arr, $keys) {
    return array_map(function ($x) use ($arr) {
         return $arr[$x];
    }, $keys);
}

We could remove some of the cruft refactoring this method:

function array_values_from_keys($arr, $keys) {
    return array_map(fn($x) => $arr[$x], $keys);
}

The syntax boils down to fn(parameters) => expression. Variables in the expression which were defined in their parent scope are used as expected without invoking the use($variable) syntax. If we wanted to skip using the Addition class from our previous PHP Easy Math library, we could use this short syntax to squash our three-line function into one:

$y = 1;

$getTotal = function ($x) use ($y) {
    return $x + $y;
};

$getTotal(42); // 43

Becomes:

$y = 1;
$getTotal = fn($x) => $x + $y;
$getTotal(34); // 35

It’s important to remember the goal of this change is to reduce the amount of boilerplate code in your applications, which increases readability. As developers, we are human code linters, and anything we can do to increase readability directly makes our code easier to understand. Also, remember it’s important to have moderation in all things, including moderation. You shouldn’t refactor your entire codebase to a bunch of one-liner statements just because you can. Keep your code easy to understand, and remember there may be others who follow behind you in maintaining a codebase.

Coalesce Assignment Operator

While PHP 7.0 brought us the Null Coalesce Operator which allows us to write code such as:

$_GET['user'] = $_GET['user'] ?? 'nobody';

The new coalesce assignment operator in PHP 7.4 brings us the ability to simplify this code to the following:

$_GET['user'] ??= 'nobody';

This new operator also has the benefit of more readable code, since we’re not duplicating $_GET['user'] in a single line. In our example, if $_GET['user'] is null, the value nobody will be assigned. An important distinction is ?? is a comparison operator while ??= is an assignment operator.

Spread Operator In Array Expressions

PHP 5.6 bought us the new argument unpacking feature. This feature allows us to use ...$var syntax to “unpack” the contents of the argument $var in a function call. Given the following array, we can clean up our code and also support a variable amount of arguments in our functions (see Listing 4).

Listing 4

<?php

function foo(...$args) {
   var_dump($args);
}

$pets = ['dog', 'cat', 'fish'];

// Instead of this:
foo($pets[0], $pets[1], $pets[2]);

// or even worse:
foo($pets[0]);
foo($pets[1]);
foo($pets[2]);

// we can use unpacking
foo(...$pets); // outputs: dog, cat, fish

// We can even chain them together
$wildlife = ['bear', 'moose', 'squirrel'];
foo(...$pets, ...$wildlife);
//outputs: dog, cat, fish, bear, moose, squirrel

// we can't use a conditional argument after
// unpacking an argument
foo(...$pets, 'bear');

// instead put those parameters first
foo('bear', ...$pets);
//outputs: bear, dog, cat

The important reason to use the spread operator ... is because the performance is faster since the operator is a language structure as opposed to a function like array_merge(). The code comes out more readable in my experience.

While the spread operator as isn’t new to PHP 7.4, understanding how it works is important because 7.4 brings us the ability to use the operator in array expressions. We can also mix and match array elements with the spread operator such as:

<?php
$pets = ['dog', 'cat', 'fish'];
$wildlife = ['bear', 'moose', 'squirrel'];
$animals = [...$pets, 'bigfoot', ...$wildlife];
//$animals: dog, cat, fish, bigfoot, bear, moose, squirrel

In PHP 7.4, the code runs without any output. In PHP versions 7.3 and lower, we’ll see a parse error: Parse error: syntax error, unexpected '...' (T_ELLIPSIS), expecting '].

The key to this new functionality lies in the ability to unpack, or spread, any item which is Traversable. The item must be implemented by IteratorAggregate or Iterator. The Traversable interface itself is not something we would implement in our code; it’s only available to the engine under the hood of PHP. Listing 5 is an example using the ArrayIterator class.

Listing 5

<?php
$animals = [
   'cat' => 'Garfield',
   'dog' => 'Snoopy',
   'moose' => 'Bullwinkle',
   'squirrel' => 'Rocky'
];

$animal_object = new ArrayObject($animals);
$iterator = $animal_object->getIterator();

// Count our items
echo 'Iterating over: ' . $animal_object->count() . ' values' . PHP_EOL;

while ($iterator->valid()) {
   echo $iterator->key() . ' is a ' . $iterator->current() . PHP_EOL;
   $iterator->next();
}

Why would we use iterators when we could use foreach()? Performance! If we run the previous code through 3v4l.org, the online PHP editor and click the performance tab below the response, we can see this code uses 14.87 MB of memory to run in PHP 7.4 (Figure 1).

Figure 1

Let’s refactor our code (Listing 6) without using iterators and run it again.

Listing 6

<?php
$animals = [
   'cat' => 'Garfield',
   'dog' => 'Snoopy',
   'moose' => 'Bullwinkle',
   'squirrel' => 'Rocky'
];
// Count our items
echo 'Iterating over: ' . count($animals) . ' values' . PHP_EOL;

foreach ($animals as $animal => $name) {
   echo $animal . ' is a ' . $name . PHP_EOL;
}

Figure 2 shows that our refactored code uses 14.94 MB of memory—only slightly more—but remember we’re dealing with a tiny amount of data. In the real world, this memory adds up and can greatly impact your overall performance.

Figure 2

Preloading

While we wait for what the JIT can do for our performance, we can leverage another feature brand new to 7.4: preloading. While OPcache saves the opcodes of our interpreted PHP applications, we can preload entire files for classes and functions to be compiled once at server startup. It saves time during the execution of our application. One caveat to preloading is you cannot preload unlinked files, which means all the classes you preload need to be linked together via traditional object-oriented linking such as extending or implementing. That is, you can not preload a child class without also preloading its parents. Likewise, you can not preload a class that uses an interface or trait without also preloading those interfaces or traits. If we wanted to preload the entire Laravel framework, we would need to loop over all the PHP files in the framework and load them from one location via the following line in our php.ini configuration file.

opcache.preload=/laravel/project/preload.php

You’ll need an automated way to update this preloaded list of files and then also reload PHP-FPM or your webserver to reparse the autoloaded file.

The real value for preloading likely comes from tools like Composer or framework helpers supporting this new feature by automatically preloading all of our dependencies for us with minimal developer interaction. If you’re eager to test drive preloading, check out this package for Laravel, which preloads the files for you, leaving you the task to add the path to the preload file to your php.ini file.

Deprecations

The list of deprecations for this version, while not empty, does not include many significant ones. One worth mentioning, for security’s sake, is the deprecation of the allow_url_include INI setting. If you have code which depends on including PHP code from a remote machine, it’s time to rethink your solution. For the full list of deprecations, see PHP RFC: Deprecations for PHP 7.4.

You can find all these changes and more in the PHP 7.4 upgrade doc in the php-src repo. As of this writing, PHP 7.4RC3 is the latest available, and you can find the entire upgrade document on GitHub.

Happy Upgrading!

Biography

Joe Ferguson is a PHP developer and community organizer. He is involved with many different technology related initiatives in Memphis including the Memphis PHP User group. He’s been married to his extremely supportive and amazing wife for a really long time and she turned him into a crazy cat man. They live in the Memphis suburbs with their two cats. @JoePFerguson

Tags: ,
 

Leave a comment

Use the form below to leave a comment: