配列に型を付ける(実践篇)

公開日:

オンラインYouTube Liveで開催された『PHPerKaigi petit @福岡 202201』で発表枠(20分)として発表しました。

Download PDF

スライドテキスト

Page 1

配列に型を付ける(実践篇)

Practice to type array

pixiv Inc.
USAMI Kenta

2022-01-22


福岡

PHPerKaigi petit@

Page 2

お前誰よ

  • うさみけんた (@tadsan) / / にゃんだーすわん

Zonu.EXE

  • ピクシブ株式会社 pixiv事業本部 エンジニア
    • 最近はピクシブ百科事典(dic.pixiv.net)を開発しています (PSR-15!)
  • Emacs Lisper, PHPer
    • Emacs PHP Modeを開発しています (2017年-)
  • PHPカンファレンス実行委員、PHPerKaigi コアスタッフ

Page 3

tadsanのあれこれが読める場所

  • https://tadsan.fanbox.cc/
  • https://scrapbox.io/php/
  • https://www.phper.ninja/
  • https://zenn.dev/tadsan
  • https://qiita.com/tadsan
  • https://github.com/bag2php

Page 4

WEB+DB PRESS総集編

Page 5

Software Design 11月号

Page 6

ツナパハに行きたかった2022

Page 7

前回までのあらすじ

Page 8

最後のPHP勉強会

(2020年)

Page 9

PHPカンファレンス沖縄2021

Page 10

PHPカンファレンス2021

Page 11

PHPerKaigi petit 8.1

(2021年)

Page 12

要約:
型宣言arrayだけでは 不十分なので、もっと 詳しく書かねばならぬ

Page 13

著者一覧を出力したい

Book::$authors : array 中身が何かわからない!

class Book

{

function printAuthors($fp): void {
foreach ($this->authors as $author) {
 fwrite($fp, $author->getName());

}


クラスの

Author 


}

メソッドを補完できない

}


Page 14

りくつはわかった

Page 15

ならぬと言われても どう書けばいいのか
わからん

Page 16

配列に型を付ける(実践篇)

Practice to type array

pixiv Inc.
USAMI Kenta

2021.10.02

Page 17

配列は単一の型では
ないことを認める

Page 18

PHPマニュアルより引用

今回の主題はこれではないので
一旦忘れてください

Page 19

重要なのは
データの持ちかた

Page 20

配列はひとつ!じゃない!!

list<int>

array<int,string>

  • ユーザIDがならんだリスト [123, 456]
  • ユーザIDと名前の対応表 [123 => '野比のび太', 234 => '源静香']
  • ユーザを表す構造体 ['id' => 123, 'name' => '野比のび太']
  • ユーザを表す構造体がならんだリスト

array{id:int, name:string}

[
['id' => 123, 'name' => '野比のび太'],
['id' => 234, 'name' => '源静香'],

]

list<array{id:int, name:string}>

Page 21

配列はどこからくるの

Page 22

arrayのふるさと

  • PHPコードに書かれたリテラル
  • HTTPリクエスト ($_GET, $_POST)
  • デシリアライズ (JSON, PHPシリアライズ)・eval
    • json_decode(»le_get_contents(__DIR__ . '/con»g.json'))
    • 外部APIリクエスト
  • PDOStatement::fetch() 系メソッド

Page 23

arrayリテラル

  • 旧式の array() とPHP 5.4以降の [] がある
  • ['a', 'b'] と [0 => 'a', 1 => 'b']
    • キーを省略すると 0 はじまりになる
  • 重要: array() は関数ではない
    • array('key' => 'value') は書けるが
      hoge('key' => 'value')はsyntax errorになる

Page 24

デシリアライズ・eval

  • 文字列に変換されたデータを解凍した結果
    • $a = json_decode("[1, 2]", true);
    • $b = unserialize("a:2:{i:0;i:1;i:1;i:2;}");
    • $c = eval('return [1, 2];');

Page 25

PDOの場合

  • PDO::fetch() に渡すオプションによって配列の形状が変わる
  • SELECT id, name FROM users; のようなSQLを考える
    • PDO::FETCH_ASSOC → ['id' => '1', 'name' => '初音ミク']
    • PDO::FETCH_NUM → [0 => '1', 1 => '初音ミク']
    • PDO::FETCH_BOTH →
      [0 => '1', 1 => '初音ミク', 'id' => '1', 'name' => '初音ミク']

Page 26

PDOの場合 (fetchAll)

  • さらにfetch(の結果が並ぶ)
    • PDO::FETCH_ASSOC → [
      ['id' => '1', 'name' => '初音ミク'], ['id' => '2', 'name' => '鏡音リン'], ['id' => '3', 'name' => '鏡音レン'],
      ]

Page 27

HTTPリクエスト

($_GET, $_POST)

  • HTMLフォームからは実は配列も送れる
    • ?data[]=a&data[]=b
      • $_GET['data'] = ['a', 'b']
    • ?user[name]=a&user[addr]=b
      • $_GET['user'] = ['name' => 'a', 'addr' => 'b']
  • 'user_name', `user_addr` みたいなのを手で展開するより便利

Page 28

なぜ配列に
型がつかないのか

Page 29

デシリアライズ・eval

(再掲)

  • 文字列に変換されたデータを解凍した結果
    • $a = json_decode("[1, 2]", true);
    • $b = unserialize("a:2:{i:0;i:1;i:1;i:2;}");
    • $c = eval('return [1, 2];');

Page 30

実際のデシリアライズ・eval

  • 文字列に変換されたデータを解凍した結果
    • $a = json_decode((string)$res->getBody(), true);
    • $b = unserialize($serialize);
    • $c = eval($code);
  • 変数に入っていたり、メソッドから動的に生成されたりする
    • = ソースコードを見てもわからない

Page 31

型宣言にarrayとしか書かないと

  • メソッドに何を渡せばいいのかわからない
  • どんな形式の配列を渡しても動くという関数は決して多くない
    • 標準関数の count() のようなユーティリティが多め
  • プロパティにも何が入ってるのかわからない
    • 従来のLaravelは @var array まみれだった
    • Laravel 9.xのコードベースでは配列の内部構造に型がついている

Page 32

たとえばこういう関数

からユーザーを取得する

/** DB */

function findUsers(array $ids): array {}

こういうパラメータを渡されても困る

//

$users = findUsers(['hoge' => 'fuga'])

というパラメータから推測しろ? うーんわからん

// $ids

Page 33

たとえばこういう関数

検索する

/** */

function search(string $word, array $opt): array {}

検索したい単語

$result = search(' ');

実装を確認しに行かないとわからない

// 


検索したい単語

$result = search(' ', [?????]);

Page 34

配列につける型

Page 35

従来多用されていた記法

/**
* @param User[] $users
*/

function printUsers(array $users): void {
foreach ($users as $user) { echo $user->name, "\n";

}
}

Page 36

従来記法の欠点

  • 配列インデックスの型を指定できない
  • 以下の配列が区別できない
    • [12 => new User(), 34 => new User()]
    • ['太郎' => new User(), '花子' => new User()]
    • [new User('太郎'), new User('花子')]

Page 37

PSR-5式の配列記法(ジェネリクス)

array<int,User>

  • 配列インデックスの型を指定できる
  • 以下の配列が区別できる

array<string,User>

  • [12 => new User(), 34 => new User()]
  • ['太郎' => new User(), '花子' => new User()]
  • [new User('太郎'), new User('花子')]

list<User>

Page 38

従来多用されていた記法

/**
* @param User[] $users
*/

function printUsers(array $users): void {
foreach ($users as $user) { echo $user->name, "\n";

}
}

Page 39

旧PSR-5式の記法

/**
* @param array<User> $users
*/

function printUsers(array $users): void {
foreach ($users as $user) { echo $user->name, "\n";

}
}

Page 40

変数リテラルや定数は @var を避ける

  • 静的解析ツールの実装にもよるが、リテラルから構造を分析できる
    • PHPStanなどは構造を読み取ってくれる
  • むやみに書くと情報量が落ちる