PHP 7.0: new features from the not-so-distant past

September 09, 2016php, php-7.0, php-7.1, english
 This has been written a few years ago and might not be up-to-date…

Cet article est aussi disponible en français.
This is the 5th post in a series about PHP 7.1.


I will publish other posts about PHP 7.1 next week but, to conclude this one, here’s a quick flashback listing some nice features PHP 7.0 brought us last year.
After all, these are available in PHP 7.1 too, and, as many are still using PHP 5, they constitute additional reasons for switching to PHP 7.x ;-)

A couple of new operators

PHP 7.0 introduced two new operators, once again to simplify and shorten some syntax.

Null Coalesce Operator

The first one, ??, aims to help this kind of construct disappear:

$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

We can now write that line this way:

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

This gives the same result as the previous one and will not cause a notice if the variable used as first operand is not defined. The only difference, often an interesting one, is this operand is now evaluated only once.

As isset() returns false when data are null, this operator behaves the same way and we can use it on more complex expressions, like method calls. I’d add it’s lazy-evaluated:

$model = Model::get($id) ?? $default_model;

// With short-circuit
function foo() {
    echo "executed!", PHP_EOL;
}
var_dump(true ?? foo());

‣ The RFC: Null Coalesce Operator


Spaceship Operator

The other operator added with PHP 7.0 is a comparison operator, often called “spaceship” because of how it looks:

function order_func($a, $b) {
    return $a <=> $b;
}

It returns -1 if its left operand is smaller than the right one, 1 if it’s bigger and 0 if they are equal. Because of this, it is quite useful when we are writing a callback function for sorts, for example with the usort() function.

It also works when we need to compare arrays:

function order_func($a, $b) {
    return [$a->x, $a->y, $a->foo]
        <=> [$b->x, $b->y, $b->foo];
}

‣ The RFC: Combined Comparison (Spaceship) Operator


Unicode Codepoint Escape Syntax

If you like using Unicode characters in your strings, this new feature is for you! With PHP 7.0, you can use the \u{xxxx} syntax to specify a character using its code:

$ php -r 'echo "I \u{2665} PHP!\n";'
I ♥ PHP!

Or, for an example with some other nice characters:

$ php -r 'echo "\u{1F408} \u{1F431} \u{1F638} \u{1F639} \u{1F63A} \u{1F63B} \u{1F63C} \u{1F63D} \u{1F63E} \u{1F63F} \u{1F640} - so cute!!!\n";'
🐈 🐱 😸 😹 😺 😻 😼 😽 😾 😿 🙀 - so cute!!!

(Did you know there were so many ‘cat’ symbol in Unicode?)

‣ The RFC: Unicode Codepoint Escape Syntax


Scalar type-declarations

This is probably the new feature of PHP 7: we can now specify type-declarations (they were previously called ‘type-hints’) with scalar types, and not only with objects / arrays / callables.

Scalar type-declarations for parameters

The different scalar types of PHP can be used for parameters: int, float, string and bool.

function add(int $a, int $b) {
    return $a + $b;
}

PHP is still weakly typed and conversions will automatically be applied when possible. When they are not possible, a TypeError exception is thrown:

try {
    var_dump( add(10, 'abc') );
}
catch (TypeError $e) {
    var_dump( $e->getMessage() );
}

In this case, we’d get:

string(148) "Argument 2 passed to add()
    must be of the type integer, string given,
    called in .../test-01.php on line 12"

‣ The RFC: Scalar Type Declarations


Return type declaration

We can also, with PHP 7, specify a return-type for a function or method, be it scalar or one of the types already accepted by PHP 5 for parameters:

function add(int $a, int $b) : int {
    return $a + $b;
}

‣ The RFC: Return Type Declarations


Strict typing

If the weak typing approach is not for you or you are sure of the data-types your code handles, you can enable a strict typing mode for a file:

<?php
declare(strict_types=1);

function add(int $a, int $b) {
    return $a + $b;
}

add(10, '20');

Here, as '20' is not an integer and we are using strict typing mode, a TypeError will be thrown:

Fatal error: Uncaught TypeError:
    Argument 2 passed to add()
    must be of the type integer, string given

Basically, when it comes to the distinction between weak and strict typing:

  • For parameters, it’s up to the caller’s choice: she is the one knowing if she’s working with weak or strict types.
  • For the return value, it’s the callee’s choice: she knowns if the function she wrote uses weak or strict types.

In any case, inside the function, data are of the specified types: the weak or strict mode only determines whether conversions are allowed.


Group use declarations

Now that we are using namespaces everywhere, we often see files beginning with an incredible number of lines looking like this:

use Mon\Espace\De\Nom\ClasseAa;
use Mon\Espace\De\Nom\ClasseBb;
use Mon\Espace\De\Nom\ClasseCc;
use Mon\Espace\De\Nom\Enfant\AutreClasse as ClassDd;

PHP 7 gives us the option to group those use directives:

use Mon\Espace\De\Nom\ {
    ClasseAa, ClasseBb, ClasseCc,
    Enfant\AutreClasse as ClassDd
};

We can also use this new syntax for constants and functions:

use Mon\Nom\ {
    function fonc01,
    const CONST01,
    Class01
};

‣ The RFC: Group Use Declarations


Enhancements on error handling

PHP 7.0 comes with two evolutions around error handling.

Internal fatal errors → exceptions

With PHP 7.0, some internal fatal errors are transformed to instances of Error, which can be caught. For exemple:

$obj = null;
try {
    // Ooops !
    $obj->methode();
}
catch (Error $e) {
    var_dump($e>getMessage());
}

This portion of code would have caused a Fatal Error with PHP 5, ending the execution of the script. It now leads to the following output, showing we entered the catch block:

string(43) "Call to a member function methode() on null"

This same principle is used for other errors, like when we are using invalid PHP code in another analysis context:

$php = <<<'PHP'
$a = 10        // -- Parse error ! -- //
printf("\$a = %d\n", $a);
PHP;

try {
    eval($php);
}
catch (ParseError $e) {
    var_dump($e->getMessage());
}

Let me insist: this change, which will help those running PHP programs for a long time (think: daemons), is for now only applied to some fatal errors — and only when they are raised from PHP’s engine.

‣ The RFC: Exceptions in the engine (for PHP 7)


Errors / exceptions hierarchy

Along with this change, the hierarchy of exceptions classes has been re-thought, introducing a new Throwable interface, shared by all exceptions types.

Usual exceptions still inherit from Exception, while errors extend Error — which is a sister-class of Exception:

interface Throwable
    Exception implements Throwable
        // All usual exceptions
    Error implements Throwable
        AssertionError extends Error
        ParseError extends Error
        TypeError extends Error

This way, the code catching Exception does not magically also start catching Errors with PHP 7.0. This ensures the behavior of your code will not change when upgrading from PHP 5 to PHP 7.

‣ The RFC: Throwable Interface


Anonymous classes

Another new feature of PHP 7.0 is anonymous classes. This extends the idea of anonymous functions, existing since PHP 5.3, adding object-related features: inheritance, implementing interfaces…

For example, let’s instantiate an anonymous object and call one of its methods:

$obj = new class ("Monde") {
    protected $qui;

    public function __construct($qui) {
        $this->qui = $qui;
    }

    public function hello() {
        printf("Hello, %s !\n", $this->qui);
    }
};

var_dump($obj);
$obj->hello();

We’ll get the following output. We can especially notice the "Monde" we gave as a parameter to the constructor, and the fact state has been shared between both methods, using the $qui attribute.

object(class@anonymous)#1 (1) {
  ["qui":protected]=>
  string(5) "Monde"
}
Hello, Monde !

A typical use case would be a callback dealing with two options, like a success() and a failure() method, only using one object implementing the corresponding interface — and not using two separate functions anymore.

‣ The RFC: Anonymous Classes


Evolutions regarding generators

Generators are still a young feature of PHP, as they’ve been added with PHP 5.5 and the community and developers only begin thinking about more advanced way to use them.

‣ If you’d like to know more about generators, here’s a series of posts on the matter, written by several members of the community a few months ago: Generators’ week: here we go!


PHP 7.0 brought two new features to generators.

Generators and return

It previously wasn’t possible to return a value from a generator. PHP 7.0 changed that:

function plop() {
    yield 100;
    yield 200;
    return 42;
}

foreach (($generator = plop()) as $val) {
    var_dump($val);
}

var_dump( $generator->getReturn() );

This would get us the following output, with values dumped from the loop, and then the return value:

int(100)
int(200)
int(42)

How can this help? We can for example think of a function that would generate intermediary values, finally returning some kind of total calculated from these.

‣ The RFC: Generator Return Expressions


Generator Delegation

The yield keyword has been reworked and we can now use yield from to generate values from a traversable (with yield alone, we would have generated only one thing: the traversable itself):

function test() {
    yield from [10, 20, 30];
}

foreach (test() as $val) {
    var_dump($val);
}

Here, we use yield from to generate the three values contained in the array, and get the following output:

int(10)
int(20)
int(30)

As a generator is traversable, we can also use yield from to generate values from other ‘sub-generators’ — hence the idea of ‘delegation’:

function sub_generator_1() {
    yield 10;
    yield 20;
}
function sub_generator_2() {
    yield from [ 'aa', 'bb', ];
}
function delegating_generator() {
    yield from sub_generator_1();
    yield from sub_generator_2();
}

foreach (delegating_generator() as $val) {
    var_dump($val);
}

Here, we’d get:

int(10)
int(20)
string(2) "aa"
string(2) "bb"

This is especially interesting when we need to aggregate data coming from several sources — for instance, to load data from a database (using a first generator) and a CSV file (using another generator).

‣ The RFC: Generator Delegation


What else?

Of course, I have not listed here all new features of PHP 7.0 and only wrote about those I noticed the most. Your turn to play with them now ;-)

And see you next week for more posts about PHP 7.1!