LoginSignup
46
31

More than 5 years have passed since last update.

PHPで配列の中身の型を保証する3つの方法

Last updated at Posted at 2018-08-02

PHP5からはクラスと配列に限って引数の型を宣言できるようになり、PHP7からはstringintなどのスカラ型も型宣言できるようになった。これにより、契約によるプログラミングや防衛プログラミングがより一層しやすくなった。

一方で、arrayは他の言語では配列、マップ、タプル、オブジェクトなどに細分化される型をどれも引き受けることができるため、arrayを受け取る関数を実装する側の防衛プログラミングは少々複雑な記述が必要になる。

防衛されていない実装

整数(int)の配列を受け取り、その合計値を計算する関数を想像してほしい。前述したとおり、arrayはその中身を制約することができないため、int以外の配列でも次のコードは動作してしまう。標準出力にWarningは出るがロジックが止まることはなく、0が計算結果になる。

// ノーガードな実装
function sum(array $numbers): int
{
    $sum = 0;
    foreach ($numbers as $number) {
        $sum += $number;
    }
    return $sum;
}

assert(sum([1, 1, 1]) === 3); // 正常系
assert(sum(['a', 'b', 'c']) === 0); // Warning: A non-numeric value encountered

配列の中身を保証する方法

いくつかパターンがあるので紹介したい。

assertion

ini_set('assert.exception', '1');

// assertionでガードした実装
function sum(array $numbers): int
{
    foreach ($numbers as $number) {
        assert(is_int($number), '値は整数でなければなりません');
    }

    $sum = 0;
    foreach ($numbers as $number) {
        $sum += $number;
    }
    return $sum;
}

assert(sum([1, 1, 1]) === 3);
try { 
    sum(['a', 'b', 'c']);
} catch (AssertionError $e) {
    assert($e->getMessage() === '値は整数でなければなりません');
}

assertの使い勝手がPHP7で改良されるまではInvalidArgumentExceptionを投げる方法がよくあった。

Adderメソッド

class Numbers
{
    /**
     * @var int[]
     */
    private $numbers;

    /**
     * @param int[] $numbers
     */
    public function __construct(array $numbers)
    {
        foreach ($numbers as $number) {
            $this->addNumber($number);
        }
    }

    // adderがintでない型を検出する
    private function addNumber(int $number): void 
    {
        $this->numbers[] = $number;
    }

    public function sum(): int
    {
        return array_sum($this->numbers);
    }
}

assert((new Numbers([1, 1, 1]))->sum() === 3);

array_walkを使って簡略化することもできる。


class Numbers
{
    ...
    public function __construct(array $numbers)
    {
        array_walk($numbers, [$this, 'addNumber']);
    }
    ...
}

array_map + クロージャー

class Numbers
{
    /**
     * @var int[]
     */
    private $numbers;

    /**
     * @param int[] $numbers
     */
    public function __construct(array $numbers)
    {
        $this->numbers = array_map(
            function(int $number) { // ここの型宣言で`int`じゃないものを検出できる
                return $number;
            }, 
            $numbers
        );
    }

    public function sum(): int
    {
        return array_sum($this->numbers);
    }
}

可変長引数 + 型宣言

以前にジェネリクスがないPHPでも配列中身のタイプヒントを可能にする「Splat Operator」で取り上げた、可変長引数と型宣言を組み合わせる方法。最もシンプルに書けるが、他の方法と比べて使える状況が限られる。

class Numbers
{
    /**
     * @var int[]
     */
    private $numbers;

    /**
     * @param int[] $numbers
     */
    public function __construct(int ...$numbers)
    {
        $this->numbers = $numbers;
    }

    public function sum(): int
    {
        return array_sum($this->numbers);
    }
}

assert((new Numbers(...[1, 1, 1]))->sum() === 3);
46
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
31