Skip to content

配列、ジェネリクス、配列で書けない型

公開日:

オンラインYouTube Liveで開催された『PHPカンファレンス2021』でレギュラーセッション(30分)として発表しました。

Download PDF

スライドテキスト

Page 1

配列、ジェネリクス、

PHPで書けない型

公開補訂版

Arrays, Generics, and Types that cannot be type-declared.

pixiv Inc.

USAMI Kenta

2021.10.03

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

今回のお題

Page 7

プロポーザル

Page 8

いきなりですが問題です

Page 9

値をそのまま返す関数

function id(mixed $value): mixed {return $value;}

Page 10

値をそのまま返す関数

PhpStormとかでメソッド名補完がきく

$date = new DateTimeImmutable();$date->diff(

Page 11

値をそのまま返す関数

PhpStormとかでメソッド名補完がき…かない

$date = new DateTimeImmutable();id($date)->diff(

Page 12

値をそのまま返す関数

なんでも受け取ってなんでも返す

function id(mixed $value): mixed {return $value;}

Page 13

本日の目標はこういうときに怯えず型を付けることです

Page 14

個々の静的解析ツール導入方法などは今回は扱いません

Page 15

ただし最新のPhpStorm 2021.2では静的解析ツールを導入しなくてもすんなり動くと思います

Page 16

これまでのあらすじ

Page 17

PHPカンファレンス2016

Page 18

PHPカンファレンス福岡2019

Page 19

PHP勉強会@東京 (2020年2月)

Page 20

PHPカンファレンス沖縄2021

Page 21

型とPHPの歴史

  • 2004年 PHP 5.0で限定的ながら型宣言(type hinting)が可能に
  • arrayおよびクラス名(インターフェイス)のみ記述可能
  • 2012年頃(?) PhpStormがPHPDocベースの静的解析を提供
  • 2015年12月 PHP 7.0リリース、スカラー型宣言とstrict_typesが追加
  • 2016年頃 Phan, PHPStan, Psalmが開発開始
  • 2019年12月 PHP 7.4リリース、プロパティに型宣言が追加

Page 22

型についておさらい

Page 23

型とは何か

  • 基本的には値の種類のこと
  • PHPのデータ型は以下のように分類できる
  • スカラー型: bool / int / Ëoat / string
  • 複合型: array / object / callable / iterable / (mixed)
  • 特殊型: null / resource
  • クラスとインターフェイスをユーザー定義型として利用できる

Page 24

PHPは動的言語

  • 変数には型宣言がない
  • 基本的にどのような値でも入れることができる
  • 値の型は実行時にいつでも確認できる
  • is_int(), is_bool(), is_array() などの関数
  • 関数とプロパティは型宣言できるし、省略もできる
  • パラメータや戻り値の型はReËectionで実行時にいつでも確認できる

Page 25

型チェックは実行時まで遅延する

<?php

$a : array

$b : int

そもそも計算できない

$a = [];

$b = 1;

echo $a + $b;

Page 26

PHPの型宣言の特徴

  • 実行時に型を確実にチェックされる
  • スカラー型(bool, int, Ëoat, string)を宣言したとき、

デフォルトではキャストよりも厳密に値を判定した上で型変換を行う

  • それ以外の型は一致しなかった場合にTypeErrorを発生する
  • ファイル単位で declare(strict_types=1) と書くと厳密な型モードになる
  • 関数の呼び出し側で宣言したモードが反映される

Page 27

型チェックは実行時まで遅延する

返り値でintに

パラメータ

変換する

mixed

1 : int

0 : int

返り値で厳密な

<?php

$toint = fn($n): int => $n;

var_dump($toint('1'));

var_dump($toint((int)''));

var_dump($toint(''));

チェック

Page 28

漸進的型付けで動的言語を型検査する

  • 何も型宣言されていない状態をmixed型(何でもあり)とする
  • より詳細に状態を絞りたいポイントに型宣言やPHPDocなどを付けていく
  • PhpStormやPHPStanなどのツールはPHPDocの内容を考慮してくれる

Page 29

型がついていない関数

function add($a, $b) {return $a + $b;}

Page 30

型宣言すればいい?

int + intって本当にintなの?

function add(int $a, int $b):int {return $a + $b;}

Page 31

floatにすればいい?

ひとつの解決策ではあるが… 不必要にËoatを強制するのか

function add(float $a, float $b):float {return $a + $b;}

Page 32

PHPDocの型注釈(アノテーション)

あえて型宣言を省略する

/*** @param int|float $a* @param int|float $b* @return int|float*/function add($a, $b) {return $a + $b;}

Page 33

Union型宣言(PHP8.0)

function add(int|float $a,int|float $b): int|float {return $a + $b;}

Page 34

かくしてPHPはPHPDocなんか書かなくても良い平和な世界になりました

Page 35

HAPPY END?

Page 36

😇

Page 37

ほんとうにそうか?

Page 38

Bookクラスは著者を持つ

上司「共著を想定できてる?」

class Book{function __construct(private Author $author) {// ...}}

Page 39

Bookの著者は複数居る

arrayにすることで複数であることを表現

Authorクラスの情報が減ってる…

class Book{function __construct(private array $authors) {// ...}}

Page 40

著者一覧を出力したい

Book::$authors : array

中身が何かわからない!

Authorクラスのメソッドを補完できない

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

Page 41

Bookの著者は複数居る

arrayにすることで複数であることを表現

Authorクラスの情報が減ってる…

class Book{function __construct(private array $authors) {// ...}}

Page 42

著者は複数居る

PHPDocの復活

class Book{function __construct(/** @var array<Author> */private array $authors) {// ...}

Page 43

著者一覧を出力したい

Book::$authors :array<Author>

Authorクラスのメソッドが補完できる

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

Page 44

この事例からわかること

  • arrayは独立した型だが、中に何が入っているかはわからない
  • array型宣言されていても、どんな配列でも正常に動くわけじゃない
  • むしろ「arrayなら何でも受け付ける」という関数はかなり少数派
  • 「いったいどんな配列なのか」を詳細に説明する必要が生じる
  • 配列の内部詳細を書くための記法がある

Page 45

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

list<int>

array<int,string>

array{id:int, name:string}

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

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

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

Page 46

配列の記法

  • 従来は Author[] とか int[] みたいに [] を後置する記法が主流だった
  • この記法では構造体のような配列の内部構造を記述できない
  • array<Author> や array<int> で要素を表現できる
  • array<int, Author> のように書くとキーも表現できる
  • array{id:int, name:string} のような値はarray-shapesと呼ばれる

Page 47

型パラメータ

Page 48

array<int>

Page 49

array<Author>

Page 50

いろんなものが書ける

Page 51

再掲: 値をそのまま返す関数

function id(mixed $value): mixed {return $value;}

Page 52

再掲: 値をそのまま返す関数

/*** @template T* @phpstan-param T $value* @phpstan-return T*/function id(mixed $value): mixed {return $value;}

Page 53

値をそのまま返す関数

PhpStormとかでメソッド名補完がきくようになった

$date = new DateTimeImmutable();id($date)->diff(

Page 54

これを活用する

Page 55

配列の先頭要素をとる関数

/*** @template T* @param array<T> $XS* @param T $default* @return T*/function first(array $xs, string $default): mixed {return $value;}

function first(array $xs, mixed $default): mixed {foreach ($xs as $x) return $x;return $default;}

Page 56

配列の先頭要素をとる関数

/*** @template T* @param array<T> $xs* @param T $default* @return T*/function first(array $xs, mixed $default): mixed {foreach ($xs as $x) return $x;return $default;

Page 57

配列の先頭要素を受け取る

PhpStormとかでメソッド名補完がきく

// list<DateTimeImmutable> を返す関数

$dates = get_dates();$first = first($dates, new DateTimeImmutable);$first->diff(

Page 58

これを活用する

Page 59

kane.php

Page 60

フォント

FOT-ハミング Std (M)

BIZ UDPゴシック (Regular)

Courier (Regular)