🦔

PHPStanから指摘される主要な問題を解決する(No value type specified in it) - 静的解析向上委員会(2)

2024/09/30に公開

一番よく出てくるやつ

PHPStanを使っていて一番発生する、
かつ一番悩ましい指摘 No value type specified in iterable type

例えばこういうの

/**
 * @return array
 */
function getItems(): array {
    return [1 => 'Apple', 2 => 'Banana'];
}

戻り値の配列の型がわからないという指摘がでてくる

5  Function getItems() return type has no value type specified in iterable type array.

https://phpstan.org/r/d68629e3-c374-435d-ae17-2cce1ac2f2ae

このぐらい単純なものであれば解決は簡単なのだが、
実際はそうはいかない
いろんなパターンで解決方法を考えていく

ヾ(・ω<)ノ" 三三三● ⅱⅲ コロコロ♪

------------------- ↓ 本題はここから ↓-------------------

単純リスト型配列

前書きに書いた配列のパターン

/**
 * @return array
 */
function getItems(): array {
    return [1 => 'Apple', 2 => 'Banana'];
}

これは、単純に配列内容を定義すればよい
定義の形は array<string>, array<int, string>, string[]などどれでも可

/**
- * @return array
+ * @return string[]
 */
function getItems(): array {
    return [1 => 'Apple', 2 => 'Banana'];
}

単純連想配列

こんどは連想配列を返す形。
モデル配列変換などよく見かける感じかと思う。

/**
 * @return array
 */
function getMixedValues(): array {
    return [
        'name' => 'Alice',
        'age' => 30,
        'isMember' => true,
        'balance' => 250.75,
    ];
}

https://phpstan.org/r/24f1023b-2c1d-435b-ae10-e1974fa0ce77

方法1: 全て定義する

これは正攻法でもあり、
個人的には使いたくない方法。
他言語ではこういう定義は普通なのだが、
PHPは言語仕様にないのと、
配列が大きければ定義コメントアウトが長くなり可読性が下がる

/**
- * @return array
+ * @return array{name: string, age: int, isMember: bool, balance: float}
 */
function getMixedValues(): array {
  // 省略
}

https://phpstan.org/r/d51b657a-545c-4cc4-8808-b41ad0c2c730

方法2: テンプレート化

方法1と同等だが再利用しやすくはなる方法
PHPDocs標準の @templateは挙動がよくわからないので、
@phpstan-typeがお勧め。
class定義側に記述するとクラス全体で使える

/**
 * @phpstan-type TValue array{name: string, age: int, isMember: bool, balance: float}
 */
class items {
  /**
   * @return TValue
   */
  function getMixedValues(): array {
    // 省略
  }
}

https://phpstan.org/r/0449297b-8dd3-4ff1-b8a6-1c33f5613cf7

方法3: array-key, mixedの使用

配列の添え字、値の型を丸める方法。
人によっては酷く毛嫌いすることもあるが、
個人的にはこれで十分だと思う。
array-keyint|stringの省略形

/**
- * @return array
+ * @return array<array-key, mixed>
 */
function getMixedValues(): array {
  // 省略
}

方法4: DTO, ValueObject

個人的にはこの方法を推したい。
DTO, ValueObjectのような配列ではなくオブジェクトとして取り扱うようなものの利用。
配列の内容をクラス定義して型として使用する。
利用する際もオブジェクトとして取り扱う
ただ、型定義のためにやるのかという議論のあるところ。
なのでDTOやVOの基底クラスになるものを整備して使いやすくするのがよい
(配列に戻すとかJson化するときの基本機能など)

class MixedValues {
  public string  $name= '';
  public int $age = 0;
  public bool $isMember = false;
  public float $balance = 0.0,
}
/**
 * @return MixedValues
 */
function getMixedValues(): array {
  $obj = new MixedValues();
  $obj->name = 'Alice';
  $obj->age = 30;
  $obj->isMember = true;
  $obj->balance = 250.75;
  return $obj;
}

https://phpstan.org/r/191f820b-428a-4b64-a2a8-867c74d7547b

同様のものにstdClassがあるが、
定義ができないうえに、自由にプロパティがセットできてしまうのでお勧めできない。
他にもArrayObjectがあるが、使い勝手が微妙だったり中身全部入れ替えられたたりとこちらもお勧めできないかな。

------------------- ↓ 後書きはここから ↓-------------------

return以外のパターン

配列を使うときは何もreturnに限ったことではない
引数、プロパティなどさまざま。

  • Method ABC::__construct() has parameter $abc with no value type specified in iterable type array.
  • Property ABC::$abc type has no value type specified in iterable type array

ただ、対応方法は全部同じで配列定義をすれば解決する

DTO, VOについて

DTOはデータの持ち運び、VOはデータのインスタンス化という形での利用されるが、
どちらも古くからあるデザインパターンでORMが普及する前はこちらが使われていた。
(PDOはDAO/DTOそのもの)
ORMが主流の今日でDTOの話をするのは何か不思議な感じがする

MVCフレームワーク(この言い方、もうしないか)にModelという概念があるが、
DTOの役割も含むがDTOではない。
(DBアクセスも実行する)

DTOをどう定義するか具体的なことも書こうと思ったが、
長くなったので別の機会に。

Discussion