🐘

強固になったよPHP

2023/12/24に公開

この記事は何?

2023年に仕事で私が得たPHPの知識、経験の棚卸し投稿です。

この投稿を、新卒1年目のころの私やそれに近い人に届けようとしていることを、現在PHPをメインに仕事・活動をしているPHPerの方には許してほしい。

本投稿のゴール

もし現在、

  • 「えらく古いPHPや古いFW、それらを使ったレガシーシステムと付き合っている」
  • 「最新のPHPとそれを取り巻くツール郡はそんな状況だから触っていない」

という状況で一概に 「PHPはな..」 と(自分を含め)思っている方が、
ちょっとPHPのことを見直せるかもしれないPHPerがなんでPHPを肯定するのかを理解できるようになるといったところを着地点にしたいと思います。

型宣言と厳格化

PHPといえば?

PHPといえば動的型付けの例によく挙げられる言語で、

  • スカラ型に関しては勝手に処理されてしまう
  • 引数が何を受け取っているか名前しかヒントがない

という印象は今も今後も消えないと思っています。
実際、過去リファクタ元に出てきたコードには

function sample ($id, $tmp)
{ 
    // めっちゃ長い処理、内容は省略
}

といった具合に、どんな値が入ってくるか本当に検討もつかない、idも現場独自の都合により何種類もあるため何が来るのか読み進めないとわからない
なんてことがざらにありました。($idに関しては名前付けも最悪ですね)
コードを読んでいくと、実は配列でした!複数形のsが抜けているのはタイポです!なんてことも起きていましたね..。
※ 現職の話ではありません

印象が覆るほどの厳格化

そんな中PHP7以降では、

説明 表記
戻り値の型宣言 function sample(): bool {}
null許容・非許容の表現 function sample(?int $num)
クラスプロパティに対するタイプの付与 public int $number;
ユニオン型のサポート function setNumber(int|float $number)

といった機能が追加されていき、型機能の充実化は進んでいきました。
参考: https://www.php.net/manual/ja/language.types.declarations.php#language.types.declarations.mixed

また、これらをただのヒントとさせずに厳格化する strictモード も7以降追加されています。

参考: https://www.php.net/manual/ja/language.types.declarations.php

strictのコードサンプル

とはいえ、ここまででは連想配列や多次元配列もくそみそにarrayと表されてしまいますし、
塊のようなデータを扱うことが多い現代ではいくらか物足りませんね。
それを解消するためのツールも今は存在しています。

PHPStan

PHPStan finds bugs in your code without writing tests. It's open-source and free.
引用: https://phpstan.org/

Deepl翻訳:PHPStanは、テストを書かずにコードのバグを見つけます。オープンソースで無料です。

PHPで静的解析をしてくれるツールです。
PHPでここまでするのか?という疑問はありますが、その強度自体はかなり厳格で(設定による)Maxレベルならば、mixedにたいしても厳密であるかチェックをしてくれます。

  1. be strict about the mixed type - the only allowed operation you can do with it is to pass it to another mixed
    https://phpstan.org/user-guide/rule-levels

レベル7の時点でも、プロパティやメソッドの参照がエラーになるケースがあれば、実行時に実はnullが返されていて..みたいなことも防げるようになります。
TypeScriptでおなじみオプショナルチェーンのようなチェック機構ですね。
PHPStanの出力と厳格なチェックが入ったPHPのソース

画像のサンプルコード
<?php

declare(strict_type=1);

class Exam
{
    /**
     * @param null|array{ foo: int, bar?: string } $arr
     */
    public function __construct(
        public readonly ?array $arr
    ) {
    }
}

$ex0 = new Exam([
    'foo' => 1224,
    'bar' => '',
]);

// これだとエラー
echo $ex0->arr['foo'];

// 型情報として$arrがnullの可能性がある以上はそのチェックが必要
if(!is_null($ex0->arr)) {
    echo $ex0->arr['foo'];
}

$ex1 = new Exam([
    'foo' => 1224,
]);

// barがオプショナルとして定義されているため、この段階ではエラーになる
if(!is_null($ex1->arr)) {
    echo $ex1->arr['bar'];
}

こちらのPHPStanのPlayGroundで実際にエラーが出る様子を確認できます。
https://phpstan.org/try

ここまで書くことを要請されるのであれば、PHPといえど一定の安全性の担保はできると思っていいでしょう。
それでも「あくまでコメントでしかないし、PHPDocにarrayとだけ書いて済ますこともできるんじゃないの?」と思う方もいると思うので実際に試したところ、レベル6以降はそれが出来ない様子でした。

エラー内容
Method Exam::__construct() has parameter $arr with no value type specified in iterable type array.

確認したコード
<?php

declare(strict_type=1);

class Exam
{
    /**
     * @param array $arr
     */
    public function __construct(
        public readonly array $arr
    ) {
    }
}

$ex2 = new Exam([
    'foo' => 1224,
    'bar' => '',
]);

// Level6以降でエラーを確認
echo $ex2->arr['foo'];

これなら紳士協定に頼らずとも、強固なルールのレベルを設定した上でCIを回す or コマンドを叩くだけで不用意なコードを弾くことが出来そうですね。

最後に

本投稿では設定方法などは解説しませんが(先人の残した資料が多分にあるため)、PHPといえど安全性の担保ができるようになってきたということが伝わったでしょうか?

「ゆるいタイプヒンティングではない静的解析による型チェック」という最低限度の保証を取りつつ、PHPの持つ一定の開発速度を確保できる、時間のないスタートアップ企業が選ぶ開発言語としてはやはり魅力的な存在かなと思います。
将来的にPHPを捨て、リファクタや別言語へのリプレイスが走ったとしても、これだけコード中に読み取れる情報があるなら幾らかマシというものでもあります。

もちろん、

  • コメントとして一々書かないといけないこと
  • コメント中にはエディタの補完が効かないこと

など、管理する量が増えれば増えるほど言語機能として強い静的型付けを持っていて欲しさは出てきますし、どこまでも使い続けられるものでは無いというのが個人的な考えです。

状況によって、何を優先するかがそれぞれ違う中で一概に否定せずに選べるときっと良いのでしょう。
もちろん新卒1年目の自分にも、PHPを十把一絡げに恨むなと伝えたいと思います。

ここまで読んでくださった方がいれば、ありがとうございました。

Discussion