RitoLabo

PHP 7.4 の新機能を試してみよう

  • 公開:
  • 更新:
  • カテゴリ: PHP Basics
  • タグ: PHP,7.4

公式ページから、PHP 7.4 での新機能や変更点などを実際に動かして確認してみます。

アジェンダ
  1. 型付きプロパティ
  2. アロー関数
  3. 返り値の型を狭めたり、引数の型を広げたりする
  4. Null の場合の代入演算子
  5. 配列内での値のアンパック
  6. 数値リテラルのセパレータ
  7. Filter 関数のオプション追加
  8. mb_str_split 関数の追加
  9. array_merge() が引数無くても利用可能に
  10. 三項演算子のネスト

型付きプロパティ

クラスのプロパティは、新たに型宣言をサポートするようになりました

クラスのプロパティで型宣言ができるようになりました。

class Member
{
public string $name;
public int $number;
}

$member->name には string のみ、$member->number には integer の値だけが入るように強制できるようになっています。

Nullable 識別子も OK です。

class Member
{
public string $name;
public ?int $number = null;
}

宣言した型と違う型の値を代入しようとするとエラーになります。

<?php
declare(strict_types=1);

class Member
{
public string $name; // 型宣言
public int $number; // 型宣言
}

$member = new Member();
$member->number = "1";
// => Fatal error: Uncaught TypeError: Typed property Member::$number must be int, string used in ...

なお、厳密な型チェックを行うには strict_types=1 のセットが必要です。

もちろんオブジェクトも宣言できます。

class IllegalArgumentException extends RuntimeException {}

class UserName
{
private const MIN = 4;
private const MAX = 20;

private string $name;

/**
* UserName constructor.
* @param string $name
* @throws IllegalArgumentException
*/
public function __construct(string $name)
{
if (strlen($name) <= self::MIN)
throw new IllegalArgumentException(self::MIN . '文字以上');

if (strlen($name) >= self::MAX)
throw new IllegalArgumentException(self::MIN . '文字以内');

$this->name = $name;
}

public function value(): string
{
return $this->name;
}
}

class User
{
private UserName $name;

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

public function getName(): string
{
return $this->name->value();
}
}

$user = new User(new UserName('hogehoge'));

echo $user->getName();
// => hogehoge

新機能なので、7.3 以前で型宣言するとエラーになります。

// PHP Version 7.3.13
Parse error: syntax error, unexpected 'string' (T_STRING), expecting function (T_FUNCTION) or const (T_CONST) in ...

アロー関数

アロー関数は、暗黙的な値スコープを持った関数を定義する簡便な文法を提供します。

クロージャを簡潔に定義できるようになりました。

$secondsToMinutes = fn(int $n): float => $n / 60;

echo $secondsToMinutes(180);
// 7.4 => 3

これを PHP 7.3 までの書き方に戻すと以下のようになります。

$secondsToMinutes = function (int $n): float {
return $n / 60;
};
echo $secondsToMinutes(180);

また、これまでは use して変数を親のスコープから引き継いでいましたが、暗黙値として処理されるようになりました。

$multiple = 10;

// PHP 7.3 まで
$multiplication = function ($n) use ($multiple) {
return $n * $multiple;
};
echo $multiplication(20);
// => 200


// PHP 7.4
$multiplication = fn($n) => $n * $multiple;
echo $multiplication(20);
// => 200

これは慣れなのだろうか... 暗黙 use ちょっと気持ち悪い。

とはいえ、アロー関数自体は一行で書くものに限定されているので、あくまでも簡単なものを記述すると考えれば、一定の理解はできます。(2行以上になる場合はアロー関数は使えません)

返り値の型を狭めたり、引数の型を広げたりする

継承や実装したクラスでメソッドをオーバーライドや定義する際に、返り値や引数の型範囲を変更できるようになりました。

返り値の型を狭める
interface A
{
public function foo(): CarbonInterface;
}

class B implements A
{
function foo(): CarbonImmutable
{
return new CarbonImmutable();
}
}

インターフェースでは戻り値の型として CarbonInterface を指定しているのに対し、具象クラス側では実装時 CarbonImmutable として、戻り値の型範囲を狭めています。

引数の型を広げる
interface C
{
public function foo(CarbonImmutable $carbon);
}

class D implements C
{
public function foo(CarbonInterface $carbon)
{
return $carbon;
}
}

$b = new D();
$carbon = $b->foo(new Carbon());

echo $carbon->toDateTimeString();
// => 2020-01-20 23:20:39

インターフェースでは foo() の引数を CarbonImmutable としているのに対し、実装側では CarbonInterface と範囲を広げています。

広げたり狭めたりするのが良いとか悪いとかっていう感じではないと思うので、この辺は静観。

Null の場合の代入演算子

左オペランドが null の場合の代入値を定義できるようになりました。

厳密には、null でない場合はその値を、null の場合は右オペランドの値を代入するというものです。

class User
{
public $name;
}

$user = new User();

前提として、上記の状態の場合では当然 $user->name は null なわけですが、これが null の場合の代入方法が新たに追加された。というお話です。

// >= PHP 5
if (!isset($user->name)) {
$user->name = 'default name';
}

// >= PHP 5
$user->name = $user->name ? $user->name : 'default name'; // 三項演算子

// >= PHP 7.0
$user->name = $user->name ?? 'default name'; // NULL合体演算子

// PHP 7.4
$user->name ??= 'default name';

echo $user->name;
// => default name

段々とコンパクトになっていっているのがわかります。 対象のプロパティが長いと結構カオスですし、書き方も冗長になりがちなので結構うれしい機能です。

配列内での値のアンパック

配列の中に、スプレッド演算子を用いて別の配列を展開できるようになりました。

$teamA = ['AAA', 'BBB'];
$teamB = ['aaa', 'bbb', ...$teamA, 'ccc'];

print_r($teamB);
// => Array
// (
// [0] => aaa
// [1] => bbb
// [2] => AAA
// [3] => BBB
// [4] => ccc
// )

ジェネレータや関数戻り値の配列も突っ込めるようです。

function generator() {
for ($i = 0; $i < 5; $i++) {
yield $i;
}
}

$generator = generator();

$a = [...$generator, 5, 6, 7, 8];

print_r($a);
// => Array
//(
// [0] => 0
// [1] => 1
// [2] => 2
// [3] => 3
// [4] => 4
// [5] => 5
// [6] => 6
// [7] => 7
// [8] => 8
//)

Traversable オブジェクトもいけます。

class A implements IteratorAggregate
{
protected array $attr;

function __construct(array $values)
{
$this->attr = $values;
}

/**
* @return ArrayIterator|Traversable
*/
function getIterator()
{
return new ArrayIterator($this->attr);
}
}

$a = new A([10, 20, 30]);

$b = [1, 2, 3, ...$a->getIterator()];

print_r($b);
// Array
//(
// [0] => 1
// [1] => 2
// [2] => 3
// [3] => 10
// [4] => 20
// [5] => 30
//)

スプレッド演算子は言語構造なので関数よりも早いというのもあり、使えるシーンがあれば使っていけば良いと思います。 速度もそうですが、記法的に簡潔になって意図がわかりやすくなればそれが一番かなと思います。

数値リテラルのセパレータ

数値リテラルは、桁と桁の間にアンダースコアを挿入できるようになりました。
echo 1_000_000_000_000;
// => 1000000000000


$price = 1_000_000_000_000;
echo number_format($price);
// => 1,000,000,000,000

可読性が上がりますね。

Filter 関数のオプション追加

FILTER_VALIDATE_FLOAT フィルタで min_range と max_range オプションが指定できるようになりました。

$value = 0.9;

$options = [
'options' => [
'min_range' => 1.0,
'max_range' => 1.8,
],
'flags' => FILTER_FLAG_ALLOW_THOUSAND,
];

$result = filter_var($value, FILTER_VALIDATE_FLOAT, $options);

var_dump($result);
// 7.4 => bool(false)
// 7.3 => float(0.9)

PHP 7.4 では min と max が適用されていることがわかります。

mb_str_split 関数の追加

mb_split のマルチバイト対応版が追加されました。

PHP RFC: mb_str_split

$str = "𠀋𡈽𡌛𡑮\u{1F973}";
print_r(mb_str_split($str));
// Array
//(
// [0] => Array
// (
// [0] => 𠀋
// [1] => 𡈽
// [2] => 𡌛
// [3] => 𡑮
// [4] => 🥳
// )
//)

バイト単位ではなくコードポイント単位で分割するので、マルチバイト文字がきちんと分割されます。日本人は待っていた人も多そうですね。

第2引数に数値を渡すと、その文字数で切ってくれます。

$str = "こんにちは";
print_r(mb_str_split($str, 2));
// Array
//(
// [0] => こん
// [1] => にち
// [2] => は
//)

array_merge() が引数無くても利用可能に

array_merge 関数が引数無しでも使用できるようになりました。引数がない場合は空の配列が返ります。

print_r( array_merge() );
// 7.4
// => Array ()

// 7.3
// => Fatal error: Uncaught ArgumentCountError: array_merge() expects at least 1 parameter, 0 given in ...

公式ページでは一瞬スプレッド演算子の事に触れていますが、配列がアンパックされた時に空だった場合を想定しているのかなと思います。

例えば関数にスプレッド演算子で配列を渡すと以下のように展開される事となりますが、それが空だった場合には展開されない = 引数なし となるのでその場合でも空を返すようになる。という感じでしょう。

function sample(int $a, int $b, int $c) {
echo $a . $b . $c;
}

$a = [1, 2, 3];
sample(...$a);
// => 123

$b = [];
sample(...$b);
// => Fatal error: Uncaught ArgumentCountError: Too few arguments to function sample(), 0 passed in ...

strip_tags() をタグ名の配列とともに使う

strip_tags() の第2引数に配列を渡せるようになりました。

$str = '<div><p><a>リンク</a></p></div>';

// これまで
echo strip_tags($str, '<div><p>');

// 配列でも渡せるようになった
echo strip_tags($str, ['div', 'p']);

// => <div><p>リンク</p></div>

strip_tags 関数は基本的に全てのタグを除去する動作をする中で、第2引数に許可する(除去しない)タグ名を渡しますが、こちらの方が可読性が上がりますね。

三項演算子のネスト

三子演算子をネストして使う場合に、カッコで括って評価の順番(というか「かたまり」の方がわかりやすいかも)を明示しなければ E_DEPRECATED が発生するようになりました。

三子演算子は暗黙的に左結合で評価されていくので、そうでない結果が欲しい場合には意図しない結果になってしまうため明示しましょう。という事ですね。

$s = [
'a' => 1,
'b' => 2,
'c' => 3,
'd' => 4,
'e' => 5,
];

// どのくくりで評価していくのかによって結果は違う

echo ($s['a'] ? $s['b'] : $s['c']) ? $s['d'] : $s['e'];
// => 4

echo $s['a'] ? $s['b'] : ($s['c'] ? $s['d'] : $s['e']);
// => 2

// 明示しないのは E_DEPRECATED
echo $s['a'] ? $s['b'] : $s['c'] ? $s['d'] : $s['e'];
// => 4
// 7.4 => Deprecated: Unparenthesized `a ? b : c ? d : e` is deprecated. Use either `(a ? b : c) ? d : e` or `a ? b : (c ? d : e)` in ...

まとめ

これらはほんの一部ですが、使いやすくなっていたり便利な機能など、PHP が着実に進化してきているのがわかります。

全ての新機能や変更点は公式ページ PHP 7.3.x から PHP 7.4.x への移行 を参照してみてください。