もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

php8を試してみた

はじめに

こんばんは。

緊急事態宣言でましたね。

ぼくは、以前同様家族で引きこもる形になると思います。

今回は触ろう触ろうと思って触ってなかったphp8を触ってみます。

phpマニュアルみながら、「使いそうだな〜」と思った機能を厳選して触ってみます。

本題

名前付き引数

引数が何個も多いようなメソッドや関数で、他の引数は別に指定しなくていいんだけど、ここの引数だけ指定したいみたいなニーズあると思うのですが、そんなときにこれ便利ですね。

<?php
function fn_example(?int $foo = null, ?int $bar = null, ?int $baz = null, ?string $hoge = null, ?string $fuga = null) {
        var_dump($foo);
        var_dump($bar);
        var_dump($baz);
        var_dump($hoge);
        var_dump($fuga);
}

# php8未満
fn_example(null, null, null, null, 'aaaaa');

# php8以降
fn_example(fuga: 'aaaaa')

/*
> # php example1.php
NULL
NULL
NULL
NULL
string(5) "aaaaa"
*/

アトリビュート

読んだときに、アノテーションとどう違うのかよくわからなかったのですが、こちらのブログで解説されていました。

パフォーマンスの向上

アノテーションの Doc コメントは文字列で取得することしか出来ず、パースするのに時間がかかります。そのため doctrine/annotations では一度取得したアノテーションをキャッシュすることが可能になっています。 Attributes の形式だと Reflection から取得出来るようになるので、パースの時間を省略することが出来ます。

取得容易性の向上

ReflectionClass::getAttributes() などのメソッドで、 Reflection から簡単に Attributes を取り出せるようになったので、 Doc コメントアノテーションに比べ取得が格段に簡単になります。

明確な型インスタンス

Attribute::newInstance() メソッドを使って Attribute クラスのインスタンスを生成することが出来るため、明確な型を使って利用ロジックを実装することが出来ます。そのため、静的解析やIDE補完などが容易に行えるようになります。

PHP Core や Extensions での利用

PHP拡張の実装時に Attributes を使うことによって、拡張機能により実装された Attributes をアプリケーションコードに落とし込むことが出来るようになります。

個人的には パフォーマンスの向上と取得容易性の向上がアツいと思いました。

php マニュアルそのまま試してみましたが、まだ、実際の実装時にどこまで活用していけるかは想像できていません。

<?php

#[Attribute]
class MyAttribute
{
    public $value;

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

#[MyAttribute(value: 1234)]
class Thing
{
}

function dumpAttributeData($reflection) {
    $attributes = $reflection->getAttributes();

    foreach ($attributes as $attribute) {
       var_dump($attribute->getName());
       var_dump($attribute->getArguments());
       var_dump($attribute->newInstance());
    }
}

dumpAttributeData(new ReflectionClass(Thing::class));
/*
string(11) "MyAttribute"
array(1) {
  ["value"]=>
  int(1234)
}
object(MyAttribute)#3 (1) {
  ["value"]=>
  int(1234)
}
*/

コンストラクタのプロモーション

クラスのコンストラクタで引数をもらって、その引数をプロパティに詰め替えるとかってよくやってると思いますが、それがぐんと簡単になります。

<?php

# php8未満
class Foo
{
    public function __construct(int $aaa, int $bbb, int $ccc, int $ddd, int $eee)
    {
        $this->aaa = $aaa;
        $this->bbb = $bbb;
        $this->ccc = $ccc;
        $this->ddd = $ddd;
        $this->eee = $eee;
    }

    public function toArray(): array
    {
        return [
            $this->aaa,
            $this->bbb,
            $this->ccc,
            $this->ddd,
            $this->eee,
        ];
    }
}

# php8以降
class Foo
{
    public function __construct(
        private int $aaa, 
        private int $bbb, 
        private int $ccc, 
        private int $ddd, 
        private int $eee
    ){}

    public function toArray(): array
    {
        return [
            $this->aaa,
            $this->bbb,
            $this->ccc,
            $this->ddd,
            $this->eee,
        ];
    }
}



$foo = new Foo(10, 20, 30, 40, 50);
var_dump($foo->toArray());

/*
# php example3.php
array(5) {
  [0]=>
  int(10)
  [1]=>
  int(20)
  [2]=>
  int(30)
  [3]=>
  int(40)
  [4]=>
  int(50)
}
*/

union型

int か floatで来るみたいなときに、どっちにも対応したいからとりあえず stringでみたいな逃げ方をしなくても良くなります。

<?php
class Item {
    private int|float $rate;

    public function __construct(private string $itemName, private int $price) {}

    public function setRate(int|float $rate): void
    {
        $this-> rate = $rate;
    }
    
    public function calc(): int|float
    {
       return $this->price * $this->rate;
    }
}

$item1 = new Item("toybox1", 50);
$item1->setRate(10);
var_dump($item1->calc());

$item2 = new Item("toybox2", 55);
$item2->setRate(8.9);
var_dump($item2->calc());

/*
# php example4.php
int(500)
float(489.5)
*/

match式

switchに似てますが、より厳密によりシンプルに書けるみたいです。 match式が出てきたことにより match予約語になってます。

<?php

$type = 'red';

echo match ($type) {
    'blue'   => 'BLUE',
    'green'  => 'GREEN',
    'red'    => 'RED',
    'yellow' => 'YELLOW',
};

> RED

switchだとネストも激しくなるし、あと厳格な比較ができないのが難点だったので、ifを多用していたのですが、これですごくシンプルにかけます。

nullsafe 演算子

個人的に激アツ機能です。 取得先のオブジェクトがnullでメソッドチェーンでつなげてやってしまい、 PHP Fatal error: Uncaught Error: Call to a member function xxxx() on null みたいなってめちゃめちゃ経験ありますよね。。 if文で大体回避するかもしれませんが、それを解消してくれます。 ただ、多様してると追いにくくなりそうな気もしていますので、使い所は気をつけないとと思いました。

<?php

class User
{
    public function __construct(private string $name, private int $age){}

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

class UserRepository
{
    private array $userList = [
        1 => ['name' => 'AAA', 'age' => 30],
        2 => ['name' => 'BBB', 'age' => 35],
    ];

    public function findUser(int $id): ?User
    {
        if (!isset($this->userList[$id])) {
            return null;
        }

        return new User(
            $this->userList[$id]['name'],
            $this->userList[$id]['age'],
        );
    }
}

$repository = new UserRepository();
var_dump($repository->findUser(1)?->name());
var_dump($repository->findUser(2)?->name());
var_dump($repository->findUser(3)?->name());

/*
# php example6.php
string(3) "AAA"
string(3) "BBB"
NULL
*/

throw が式として使える

地味に嬉しい機能でした。 シンプルに書けるようになります。

例えば試してないけど、こんな感じにもかけるのかな?

<?php
$this->User->save();
$userId = $this->User->getLastInsertId() ?? throw new UserRegisterException('ユーザーを登録できませんでした。');

str_contains()の実装

文字列が含まれているかを調べる関数です。

もともと php8未満でも再現可能ですが、専用の関数としてできて扱いやすくなった印象ですかね?

var_dump(str_contains("初めまして!! Kojirockですよ!", "Kojirock")); // true
var_dump(str_contains("初めまして!! Kojirockですよ!", "kojikoji")); // false

終わりに

ほかにも新機能色々とあるみたいなのですが、個人的に使いやすそうなのをピックアップしてみました。

php7 -> php8のバージョンアップはそこまで苦ではないと思いますが、使用しているFWのバージョンアップのほうが鬼門ですよね。

現場からは以上です。