PHP
PHP7.1
PHP7.2

それでもphp 7.1以降を使うべき7つの理由

はじめに

php 5.xから約2倍の高速化を果たしたphp 7.0,そんなphp 7.0も登場から二年以上が経過し,すっかり枯れた,もとい安心して導入できる段階になりました。

また,昨年末にリリースされたphp 7.2の登場によりphp 5.6.xがサポートされている最後の5.xバージョンとなりました。

そんなphp 5.6.xのサポートも2018年いっぱいで終了することが公式にアナウンスされており,なるべく早く7.xへのマイグレーションを行いたいところです。

では,マイグレーション先は十分に枯れたphp 7.0.xが適切でしょうか?

いいえ,せっかくならphp 7.1以上に移行してしまいましょう。

php 7.1以上を使うべき理由

php 7.1以上への移行を勧める理由は,単なるできるだけ新しいものを使おうという考えではありません。php 7.1以上を用いることで得られる利点が遥かに大きいためです。

今回はphp 7.1を用いる利点を大きく7つ挙げてみました。

1. 厳密かつ型安全なコーディングが可能に

php 7.0からはphp 5.xタイプヒンティングに変わり,型宣言が行えるようになりました。これは仮引数にも返却型にも有効です。

例えば,以下の関数は必ずint型の値を受け取り,必ずstring型の値を返すことが保証されます。

php70_type_definition.php
<?php

function type_defined(int $argument) : string {
    return 'Hello';
}

引数の型及び返却型が指定されていることにより,phpDocを用いなくとも型情報を扱うことが出来ます。また,宣言通りではない値が渡された場合には例外が発生する為,より安全で確実なコーディングが行えます。

しかし,php 7.0における返却型宣言には一つ問題があります。返却値は必ずその型でなければならないのです。

以下のコードは実行時にエラーとなります。

php70_type_definition_error.php
<?php

$name_list = [
    'Luna',
    'Sunny',
    'Star',
];

function get_name(int $name_id) : string {
    return isset($name_list[$name_id]) ? $name_list[$name_id] : null;
}

var_dump(get_name(4));
// PHP Fatal error:  Uncaught TypeError: Return value of get_name() must be of the type string, null returned.

返される値がnullでないことが保証されるのは安全なコードを書く上で確かに正しいものです。

しかし現実には返却対象が存在し得ない場合があります。例えば,データベースからidを元に特定の1行を取得しようとしたが,データベース上に該当するデータが存在しなかったなどという場合です。

php 7.1では,nullable(nullである場合がある)な型宣言が可能となりました。次のコードはphp 7.1で有効です。

php71_type_definition.php
<?php

$name_list = [
    'Luna',
    'Sunny',
    'Star',
];

function get_name(int $name_id) : ?string {
    return isset($name_list[$name_id]) ? $name_list[$name_id] : null;
}

var_dump(get_name(4)); // NULL

この手法は返却型がnullである可能性があるということを明示的に宣言しており,nullが返された場合を想定したコードを書くのも容易です。phpDocと違い,構文レベルで確実性が担保されます。

これをphp 7.0で行おうとするとどうなるでしょうか。

php70_type_definition_workaround.php
<?php

$name_list = [
    'Luna',
    'Sunny',
    'Star',
];

/**
 * @return string|null
 */
function get_name(int $name_id) {
    return isset($name_list[$name_id]) ? $name_list[$name_id] : null;
}

var_dump(get_name(4)); // NULL

返却型の宣言は消滅し,部分的に見ればphp 5.xと同等のコードとなってしまいました。phpDocはコーディングの手助けこそしてくれますが,何も保証してはくれません。
例えこの関数がintを返そうと警告なしに動き続けてしまいます。

2. 「何も返さない」事を保証できる

php 7.0において,返却値を持たない関数は従来通り型宣言を行わない形で定義するしかありませんでした。php 7.1からは返却型としてvoidが追加され,これが宣言されている場合には返却値がないことが保証されます。

php71_void_definition.php
<?php

function print_hello() : void {
    echo 'Hello';
}

// Fatal error : A void function must not return a value.
function print_world() : void {
    return 'world';
}

これはインターフェイスや抽象クラス,継承を用いる時に非常に有効であり,意図しない挙動の変更を防ぐ事ができます。

php 7.1において以下のコードはエラーとなります。

php71_return_type_missmatch.php
<?php

interface SimpleProcedureInterface
{
    public function processProcedure() : void;
}

abstract class AbstractSimpleProcedure implements SimpleProcedureInterface
{
    abstract public function processProcedure() : void;

    public function initializeProcedure() : void
    {
        echo 'initialized';
    }
}

class SimpleProcedure extends AbstractSimpleProcedure
{
    public function processProcedure() : string
    {
        return 'hello';
    }
}

// PHP Fatal error:  Declaration of SimpleProcedure::processProcedure(): string must be compatible with AbstractSimpleProcedure::processProcedure(): void

3. list()からの開放

ちょっと話が脱線しますが,太古の昔のphpという言語は配列を宣言する為に以下のようなコードを書かなければなりませんでした。

php53_array.php
<?php

$name_list = array(
    'Luna',
    'Sunny',
    'Star',
);

これはまだマシな方です。多次元配列を使おうものならばarray地獄に堕ちる羽目になります。

php53_multidimentional_array.php
<?php

$food_list = array(
    'main_dish' => array(
        'rice',
        'bread',
    ),
    'sweets' => array(
        'pancake',
        'chocolate',
    )
);

php 5.4からは,晴れて短縮構文である[]が利用できるようになりました。この変更により多くのPHPerは忌まわしきarray地獄から開放されました。

しかし,配列展開を行うlist()構文に関しては依然としてそのままであり,他言語のようなスマートな方法で値を展開して受けることが出来ませんでした。

以下はphp 7.0で配列展開を用いて変数に値を代入する例です。

php70_array_expansion.php
<?php
list($name, $type, $color) = ['Luna', 'Fairy', 'Yellow'];

echo "name:${name} type:${type} color:${color}";
// name:Luna type:Fairy color:Yellow

php 7.1からはarrayと同様に[]による省略構文が利用できます。

php71_array_expansion.php
<?php
[$name, $type, $color] = ['Luna', 'Fairy', 'Yellow'];

echo "name:${name} type:${type} color:${color}";
// name:Luna type:Fairy color:Yellow

4. list(), []構文におけるキーのサポート

list()及び[]による配列展開にてキーの指定が可能になります。より複雑な値の切り出しも少ないコードで実装可能です。

php71_list_key_support.php
<?php
$user_list = [
    [
        'name'   => 'john',
        'gender' => 'man',
        'age'    => 20,
    ],
    [
        'name'   => 'alice',
        'gender' => 'woman',
        'age'    => 16,
    ],
];

['name' => $name, 'gender' => $gender, 'age' => $age] = $user_list[1];

echo $name; // alice
echo $gender; // woman
echo $age; // 16

5.より厳密なアクセス制限が出来る

php 7.1からはconstに対してアクセス権を設定することが出来るようになりました。以下のコードはphp 7.1で有効です。

(でも正直protectedconstを使う意味ってあるんだろうか...staticなメンバ変数の方が適切なような...)

php71_const_accesssor.php
<?php

class Foo
{
    public const TYPE_PUBLIC       = 1;
    protected const TYPE_PROTECTED = 2;
    private const TYPE_PRIVATE     = 3;

    public function printAllConstValue() : void
    {
        echo self::TYPE_PUBLIC; // 1
        echo self::TYPE_PROTECTED; // 2
        echo self::TYPE_PRIVATE; // 3
    }
}

// インスタンスからアクセス
(new Foo())->printAllConstValue();

// 外部からアクセス
echo Foo::TYPE_PUBLIC; // 1
echo Foo::TYPE_PROTECTED; // Fatal error : Uncaught Error: Cannot access protected const Foo::TYPE_PROTECTED
echo Foo::TYPE_PRIVATE;

6. 負の文字列オフセット

php 7.1からは各種文字列操作関数において負のオフセットが利用可能になりました。substr関数と違いを意識せずに使えるようになります。

php71_strpos_negative.php
<?php
$foo = 'booooooooooooooooongo';

echo strpos($foo, 'o', -1); // 20

7. "正しいメルセンヌ・ツイスタ"の実装

今更かもしれませんが,php 7.1未満におけるメルセンヌ・ツイスタ擬似乱数の実装は誤っていました。
php 7.1からは正しいメルセンヌ・ツイスタが実装され,より擬似乱数として適切な値となりました。
疑似乱数を用いたアプリケーションを構築するにおいて,正しい実装が行われていることは非常に重要な意味を持つように思います。

まとめ

php 7.1php 7.0での大幅な改善から抜け落ちていた多くの部分が追加・修正され,より安全で確かなコードを書くことが出来る言語へと進歩しています。

下位互換性のない変更点もいくつかありますが,今からマイグレーションを行うのであれば,少し無理をしてでもphp 7.1を検討してみても良いのかもしれません。

また,php 7.1へのマイグレーションを行うと,もう一つ嬉しいことがあります。そのコードはphp 7.2ほぼ何の問題もなく動作するであろう,ということです。

参考文献