PHP 7.1 で新しく追加される機能を把握する

 こんにちは、メディアシステム開発部の菅原です。

 PHP 7.1 が 2016 年 12 月 1 日(日本時間では 2016 年 12 月 2 日)にめでたくリリースされました。ちょうど良い機会なので、PHP 7.1 RFC を参考に、新たに追加された機能を見ていきたいと思います。

新機能8選

 今回の記事では、PHP 7.1 の RFC の中から構文に関する新機能のうち 8 つの RFC をピックアップして見ていきます。

  1. nullable 型
  2. void 戻り値宣言
  3. クラス定数のアクセスレベル宣言
  4. 複数の例外の補足
  5. iterable 型
  6. list のキーによる変数宣言
  7. list 短縮構文
  8. 文字列への負数オフセットによるアクセス

 後述のサンプルコードは、下記のPHPの環境で実行しています。なお、Fatal Error が発生しているコードについては、適切な部分をコメントアウトして実行しています。

$ php -v
PHP 7.1.0RC6 (cli) (built: Nov  9 2016 04:45:59) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.1.0-dev, Copyright (c) 1998-2016 Zend Technologies

1. nullable 型

 PHP 7.0 で、スカラー型もタイプヒントが使えるようになり、タイプヒンティングが型宣言に進化しました。これによって、PHP 本体で全ての型をチェックできるようになりましたが、null を許容する場合は型宣言をしないで記述する必要があり、null safety1 とは言えませんでした。しかし、PHP 7.1 で nullable 型が導入されることにより、PHP はメソッドに関しては null safety であると言える2 ようになりました。

<?php
// nullable な引数を持つメソッド
function sayNullable(?string $name): ?string
{
    return empty($name) ? null : 'My name is ' . $name;
}

// non-null な引数を持つメソッド
function sayNonNull(string $name): string
{
    return 'My name is ' . $name;
}

var_dump(sayNullable('安部 菜々')); // string(24) "My name is 安部 菜々"
var_dump(sayNullable(null));       // NULL
// パラメータなしは NULL を渡している訳ではないので例外が発生
var_dump(sayNullable());           // PHP Fatal error:  Uncaught ArgumentCountError: Too few arguments to function sayNullable() ...

var_dump(sayNonNull('緒方 智絵里')); // string(27) "My name is 緒方 智絵里"
// null safe なので例外が発生
var_dump(sayNonNull(null));        // Fatal error: Uncaught TypeError: Argument 1 passed to sayNonNull() must be of the type string, null given, ...

2. void 戻り値宣言

 PHP 7.0 で戻り値の型を明示的に宣言できるようになりましたが、なぜか void 型には対応していませんでした。それほど大きな問題ではないですが、IDE によっては Doc コメントを自動入力させた時に正しい戻り値にしてくれないものがあるため、直すのが地味に面倒ではありました。

 なお、void 型メソッドは、戻り値として null を返してはいけないが、実行時の戻り値を受け取った場合は null という香ばしい仕様になっています。

<?php
// return しない
function hoge(): void {
}
// return する
function foo(): void {
    return;
}
// null で return する
function bar(): void {
    return null;
}

// void 型の戻り値が定義されているメソッドの戻り値を無理やり受け取ると null になる
var_dump(hoge()); // NULL
var_dump(foo());  // NULL
// void 型は null を戻り値とするわけではないので例外発生
var_dump(bar());  // PHP Fatal error:  A void function must not return a value (did you mean "return;" instead of "return null;"?) ...

3. クラス定数のアクセスレベル宣言

 クラス定数のアクセスレベル宣言ができるようになります。デフォルトは従来通り public です。const が使える子になってきたので、define がより一層使えない子になってきました。

<?php
class Hoge
{
    const CURRENT_CONST = 'public_current';
    private const PRV_CONST = 'private';
    protected const PRO_CONST = 'protected';
    public const PUB_CONST = 'public_new';

    public static function getPrivateConst(): string
    {
        return self::PRV_CONST;
    }
}

class SubHoge extends Hoge
{
    public static function getProtectedConst(): string
    {
        return self::PRO_CONST;
    }
}

var_dump(Hoge::CURRENT_CONST);          // string(14) "public_current"
var_dump(Hoge::PRV_CONST);              // Fatal error: Uncaught Error: Cannot access protected const Hoge:: PRV_CONST ...
var_dump(Hoge::PRO_CONST);              // Fatal error: Uncaught Error: Cannot access protected const Hoge::PRO_CONST ...
var_dump(Hoge::PUB_CONST);              // string(10) "public_new"
var_dump(SubHoge::getProtectedConst()); // string(9) "protected"
var_dump(SubHoge::getPrivateConst());   // string(7) "private"

4. 複数の例外の補足

 Java 7 で導入された例外のマルチキャッチが PHP 7.1 にも導入されます。Java とは異なり、大らかな PHP3 なので、特に問題なく気軽に使えるはずです。むしろ、同じコードを二度書かないという DRY 原則を守りやすくなるので、メンテナビリティは向上すると思います。

<?php
function throwException(?string $type) {
    try {
        if (is_null($type)) {
            throw new BadMethodCallException();
        } elseif (empty($type)) {
            throw new InvalidArgumentException();
        }
    } catch (BadMethodCallException | InvalidArgumentException $e) {
        echo 'throw exception is ' . get_class($e) . ".\n";
    }
}

throwException(null); // throw exception is BadMethodCallException.
throwException('');   // throw exception is InvalidArgumentException.

5. iterable 型

 PHP 7.0 までは、C# でいう IEnumerable のような型が存在せず、Traversable 型を実装したクラスと配列は別物でしたので、foreach 可能なオブジェクトに対する型宣言をすることができませんでした。そのため、それらのみを引数として受け取る場合は、引数の型チェックを余分に行う必要がありました。ですが、PHP 7.1 で iterable 型が実装されることにより、型宣言でその制約を付けることが可能になります。

 ただし、PHP 7.1 の時点では、count 関数に関するバグ4 があるため、iterable 型と併用する時は注意が必要です。なぜならば、iterable 型と判定された変数が、Countable とは限らないからです。この問題については、PHP 7.2 で修正される予定です。

<?php
function twice(iterable $object): string
{
    $new_array = [];

    foreach ($object as $val) {
        $new_array[] = $val * 2;
    }

    return implode(', ', $new_array);
}

var_dump(twice(new ArrayObject([ 1, 2, 3 ]))); // string(7) "2, 4, 6"
var_dump(twice([ 1, 3, 5 ]));                  // string(8) "2, 6, 10"

6. list のキーによる変数宣言

 list 言語構造の代入式の右辺に連想配列を渡した場合に、宣言した変数の値に対応するキーの値を割り当てすることができるようになりました。これにより、連想配列の任意のオフセットにあるキーの名前を指定すれば、その値を変数の値にできるようになったため、list の引数に「,」を異様に並べて書く必要がなくなりました。データベースからレコードを取得してきた時に便利になるのではないかと思います。

<?php
$data = [
    [ 'id' => 1, 'name' => '安部 菜々' ],
    [ 'name' => '緒方 智絵里', 'id' => 2 ],
    [ 3, '輿水 幸子' ]
];

// キーの順序が入れ替わっていても割り当てが可能。取得するキーも選択可能。
list('name' => $name_0) = $data[1];
var_dump($name_0);                                 // string(16) "緒方 智絵里"

// キーが定義されていない(連想配列と扱われない)場合
list('id' => $id_2, 'name' => $name_2) = $data[2]; // PHP Notice:  Undefined index: id ...
                                                   // PHP Notice:  Undefined index: name ...

// 2列目は連想配列ではないので Noticeが出る
foreach ($data as list('id' => $id, 'name' => $name)) {
    echo "Name is {$name} (ID={$id})\n";
}
// 0列目: Name is 安部 菜々 (ID=1)
// 1列目: Name is 緒方 智絵里 (ID=2)
// 2列目: PHP Notice:  Undefined index: id ...
//        PHP Notice: Undefined index: name ...

7. list 短縮構文

 list を糖衣構文 [] で記述することが可能になりました。list と同様に PHP 7.1 で追加された新機能であるキーによる変数宣言もできます。

<?php
$data = [
    [ 'id' => 1, 'name' => '安部 菜々' ],
    [ 'name' => '緒方 智絵里', 'id' => 2 ],
    [ 3, '輿水 幸子' ]
];

// キーの順序が入れ替わっていても割り当てが可能。取得するキーも選択可能。
[ 'name' => $name_0 ] = $data[1];
var_dump($name_0);                                 // string(16) "緒方 智絵里"

// キーが定義されていない(連想配列と扱われない)場合
[ 'id' => $id_2, 'name' => $name_2 ] = $data[2]; // PHP Notice:  Undefined index: id ...
                                                 // PHP Notice:  Undefined index: name ...

// 2列目は連想配列ではないので Noticeが出る
foreach ($data as [ 'id' => $id, 'name' => $name ]) {
    echo "Name is {$name} (ID={$id})\n";
}
// 0列目: Name is 安部 菜々 (ID=1)
// 1列目: Name is 緒方 智絵里 (ID=2)
// 2列目: PHP Notice:  Undefined index: id ...
//        PHP Notice: Undefined index: name ...

8. 文字列への負数オフセットによるアクセス

 文字列およびオフセット指定可能な全ての配列関連のビルトイン関数は、負数のオフセットをサポートするようになりました。

<?php
$string = 'mediba';

var_dump($string[-2]);              // string(1) "b"
var_dump('mediba'{-3});             // string(1) "d"
var_dump(strpos($string, 'i', -3)); // int(3)

まとめ

 近年の PHP は、柔軟性と厳格性の程よくバランスが取れた進化をしていると言えると思います。今回の PHP 7.1 のリリースでも、新しく追加される機能はすべて使い勝手に優れた良いものが並んでいます。特に、型関連の機能は PHP 5 時代から考えると、大きな進化を遂げていて、PHP 7.0 でタイプヒンティングが型宣言に進化し、PHP 7.1 ではさらに nullable 機能が追加されました。これによって、完全ではありませんが null safety を手に入れ5、また、型安全性も高くなり、より優秀な言語になってきました。

 今後の進化で期待するところといえば、null safety や型安全性に関する言語機能です。妄想になりますが、下記のように6、安全呼び出しや変数の型宣言、総称型が実装されると、もっと良くなるのではないかと思います。

// 1. 安全呼び出し(safe call)
$obj?->foo($x);
// 2. 変数の型宣言(null safety)
private $val_int: int;
public $val_str: ?string;
// 3. 総称型(※半角の大小記号が消えるので全角にしています)
class Gene<T> {
}

  1. null safety は、Kotlin というプログラム言語由来の用語です。これは、null が原因となる実行時エラーを起こさない性質のことを表しています。なお、Kotlin は、言語機能として null でない変数の場合にだけ、そのメンバにアクセスする safe call(安全呼び出し)という機能を持っています。 ↩︎

  2. non-null と nullable の区別はありますが、安全呼び出しに該当する機能がないことから、完全な null safety ではありません。 ↩︎

  3. Java とは異なり、検査例外とか非検査例外とかそういう例外種別を気にしなくても良い(SPLにはそれっぽいのはありますが)のは良いところです。 ↩︎

  4. count 関数に Countable インターフェイスを継承していないオブジェクトなどを引数として渡した場合に 1 を返却する不具合です。 ↩︎

  5. 他に、null safety ではない言語は、 C言語、C++、C#、Go、Java、Objective-C、Javascript、Ruby、Python などがあります。 ↩︎

  6. どこかで見たことがあると思ったあなた、正解です。Hack/HHVM で実装されているんです。PHP にもフィードバックしてほしいですね。 ↩︎