Skip to content

今日からできる安心型付け入門

公開日:

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

Download PDF

スライドテキスト

Page 1

今日からできる安心型付け入門

Introduction of typing in PHP

2021-05-29 YouTube

PHPカンファレンス沖縄

Page 2

お前誰よ

  • うさみけんた (@tadsan) / Zonu.EXE
  • GitHub/Packagistでは id: zonuexe
  • ピクシブ株式会社 pixiv運営本部
  • Emacs Lisper, PHPer, Rubyist
  • 2018年からEmacs PHP Modeのメンテナ
  • 好きなリスプはEmacs Lispです
  • Qiitaに記事を書いたり変なコメントしてるよ

Page 3

本日のお題

Page 4

‒今日からできる安心型付け入門 - PHPカンファレンス沖縄

https://fortee.jp/phpcon-okinawa-2021/proposal/b90e3c04-ae04-4430-a28c-3f231e703c37

Page 5

Page 6

アジェンダ

PHPの型について

なぜ型をつけるのか

PHPの入出力型の特性

Page 7

今回含まないもの

各静的解析ツールやIDE(PhpStorm)固有の型・機能

メタプログラミングに対応した型付け

Page 8

用語について

単に関数と呼ぶとき、メソッドとクロージャを含む

定義に直接記述する型を型宣言(type declaration)、PHPDocを型注釈(annotation)と呼びます

Page 9

PHPの型について

Page 10

型の前に

Page 11

プログラムには依存があるということを認める

Page 12

依存とは

Page 13

簡単なコードを考える

<?php

$a = 1;$b = 2;

echo $a + $b;

Page 14

簡単なコードを考える

<?php

$a = 1;$b = 2;

echo $a + $b;

Page 15

マスクされているという状態をコードに反映する

Page 16

簡単なコードを考える

<?php

$a = 1;$b = 2;

echo $a + $b;

Page 17

簡単なコードを考える

<?php

function print_add($a, $b){echo $a + $b;}

print_add(1, 2);

Page 18

汎用的な処理と実際の値を切り離すことができた

Page 19

簡単なコードを考える

<?php

こいつらが何者かわからん

function print_add($a, $b){echo $a + $b;}

print_add(1, 2);

Page 20

Page 21

型をつけよう

Page 22

簡単なコードを考える

型が明確になった

<?php

function print_add($a, $b){echo $a + $b;}

print_add(1, 2);

Page 23

そもそも型とは

Page 24

データ型

PHPで使える値の種類のこと

あたいへんすうていすう

値=変数・定数にセットしたり、関数呼び出しに渡せるもの

値は何らかのデータ型に属する

Page 25

PHPと型

PHPは型システムの分類として弱い動的型付けあるいは「型なし」と分類されがち

しかしPHPは単なる型なしと呼ぶには型を使ってできることが多い

Page 26

PHPの実行時型検査

関数のパラメータと戻り値、プロパティに型宣言できる

強力な実行時型検査

実行時にコードの型宣言通り型で実行されることが保障

TSやPythonとは異なる

Page 27

PHP5の型ヒント

Type Hintingと呼ばれてた

array, callableあるいはクラス/インターフェイスしか書けなかった

PHP7.0以降ではスカラー型に対応し、型宣言に改称

Page 28

PHPの静的型検査

Psalm, PHPStanなどの検査ツールが豊富

PhpStormは検査能力は劣るが簡単に導入できる

今回の発表では個別のツールの導入方法には触れません

Page 29

データ型(1) int 整数型

0, 1, 2や-100のような数

最大値はPHP_INT_MAXで最小値はPHP_INT_MIN

Page 30

データ型(2) string 文字列型

"a" "xyz" "あいう" のような文字の並びだと思ってくださいから長さ0の "" (空文字列)も存在する

PHPの文字列値はエンコーディングを持たないバイト列

Page 31

ここで簡単なコードを考えてみましょう

Page 32

簡単なコードを考える

<?php

細かくいうと$a = 1のような変数代入も式

値"2"

値%

値"2"

$a = 1;$b = "2";$c = $a + $b;

式1 + "2"

echo $c;

値%

値%

Page 33

リテラルと式

コードに直接書く値をリテラル(literal)といいます1 2.3 "abc" みたいなの

$a + $b のようなコードを式(expression)といいます

Page 34

値には必ず型がある

int(1)

<?php

string("2")

int(1)

string("2")

$a = 1;$b = "2";$c = $a + $b;

式1 + "2"

echo $c;

int(3)

int(3)

Page 35

データ型(3) float 浮動小数点数型

0.1, 10.0, -0.3, INF, -INFのような値

PHPでは整数の範囲を外れた数はfloatになる

PHP_INT_MAX+1 は float

Page 36

データ型(4) bool 論理型

true と false の二種類だけの値からなる型

$a==$b や $a < $b のような比較式の結果はbool

PHPでは $a && $b もbool

Page 37

スカラー型のまとめ

bool, int, float, stringの4種をスカラー(scalar)と呼ぶ

後述する複合型でも特殊型でもないものという共通性

Page 38

複合型

内部に別の値を持てる型

array (配列)

object (オブジェクト)

※ PHPマニュアルはcallable特殊型とiterable特殊型も複合型として記載

Page 39

データ型(5) array 配列型

他言語のリストと辞書(ハッシュ)を兼ねた型だが、基本は辞書的

[1, 2, 3] や [] (空配列)

['name' => 'taro', 'birthday' => '09-13']

のように複数の値を格納できる

Page 40

データ型(5) array 配列型

作成時にキーを指定しないと0からの連番になる

[1]と [0 => 1] は同じ

$a = [3 => 'a'] は

[null, null, 'a']ではない

Page 41

データ型(6) object

プロパティ(メンバー変数)を持ち、$obj->prop のような構文でアクセスできる値の型

objectには属するクラスがあり、組み込みクラス、ユーザー定義、stdClassがある

Page 42

型宣言とクラス

型宣言にはクラス名またはインターフェイスを記述できる

事実上のユーザー定義型

インターフェイスを活用すると実装と宣言を分離できる

依存性逆転の原則(DIP)

Page 43

全てinterfaceで指定すべきか

抽象化でたしかに実装詳細への依存を避けることができる

全ての依存をインターフェイスにすべきかは悩ましい

DTOやValueObjectなど

DataTimeInterface

Page 44

特殊型

単体で型宣言できない特殊な値

resource (リソース)

null (空値)

※ nullはnullableの一部やPHP8のユニオン型の一部として記述可能

Page 45

型ではないが特殊な戻り値

function(): void {}

値を返さないことを示す

値を返さないということは、副作用があるというマーク

DBにデータを記録する、メールを送る、例外送出

Page 46

複合的な型表記

nullable型

?DateTimeとか

ユニオン型

A|Bとかstring|falseとか

PHP7ではPHPDocコメントに書く必要がある

Page 47

クラスの型付けを考えてみましょう

Page 48

Book (本)

プロパティ(メンバー変数)として、name(書名)とAuthor(著者オブジェクト)を持つ

メソッドは今回は割愛

Page 49

クラスの型付けを考える 5.0

<?php // PHP 5.x~7.3

class Book {/** @var string */private $name;/** @var Author */private $author;public function __construct(string $name, Author $author) {$this->name = $name;$this->author = $author;}

Page 50

クラスの型付けを考える 7.4

<?php // PHP 7.4

class Book{private string $name;private Author $author;public function __construct(string $name, Author $author) {$this->name = $name;$this->author = $author;}

Page 51

クラスの型付けを考える 8.0

<?php // PHP 8.0以降

class Book{public function __construct(private string $name,private Author $author) {// プロパティ代入は不要

// $this->name = $name;// $this->author = $author;}

Page 52

仕様変更

😁

Page 53

著者が複数の本っていっぱいあるんじゃ

Page 54

著者が複数…配列で受け取ろう

Page 55

$author

$author

s

Page 56

変更前 7.4

<?php // PHP 7.4

class Book{private string $name;private Author $author;public function __construct(string $name, Author $author) {$this->name = $name;$this->author = $author;}

Page 57

変更後 7.4

<?php // PHP 7.4

arrayって…情報量減っちゃってね?

class Book{private string $name;private array $authors;public function __construct(string $name, array $authors) {// ...}

Page 58

array型宣言は結構困る

今回欲しいのはAuthorだけが入った配列

arrayだけでは不十分

['name'=>'太宰治']みたいな配列がほしいわけではない

Page 59

ここで出てくるのがPHPDoc

Page 60

変更前 7.4

<?php // PHP 7.4

class Book{private string $name;private array $authors;public function __construct(string $name, array $authors) {// ...}

Page 61

PHPDocでプロパティ型付け

<?php // PHP 7.4

class Book{private string $name;/** @var Authors[] */private array $authors;public function __construct(string $name, array $authors) {// ...}

Page 62

PHPDocでメソッド型付け

<?php // PHP 7.4

class Book{private string $name;/** @var Authors[] */private array $authors;/*** @param Authors[] $authors*/public function __construct(string $name, array $authors) {// ...

Page 63

PHPDoc(型注釈)について

DocComment /** … */に型情報を書くことで、型宣言できない詳細な型を付ける

PHPが対応しない型や、実行時に検査しにくい型を記述できる

Page 64

PHPDoc(型注釈)にしか書けない型

コレクションの型

ジェネリクス

ユニオン型はPHPDocで長年使われていたがPHP8で型宣言に取り入れられた

Page 65

PHPDoc(型注釈)にしか書けない型

リテラル型・定数型

array-shapes(Object-like arrays)

Page 66

パラメータの型

<?php

/*** 検索エンジンを単語検索する

*/function search(string $word, string $mode) {// ...}

ユーザーが検索したいワード

検索モードはどんな文字列で

なんでも入れていい

も入れたいわけではない

Page 67

パラメータのリテラルユニオン型

<?php

/*** 検索エンジンを単語検索する

* @phpstan-param 'exact'|'partial' $mode*/function search(string $word, string $mode) {// ...}期待値をResultでそのまま書

ける

Page 68

パラメータの定数プレフィクス型

<?php const SEARCH_MODE_EXACT = 'exact';const SEARCH_MODE_PARTIAL = 'partial';

/*** 検索エンジンを単語検索する

* @phpstan-param SEARCH_MODE_* $mode*/function search(string $word, string $mode) {// ...}

ユーザーが検索したいワード

なんでも入れていい

Page 69

型なしはどこから来るの?

Page 70

ここまでは値をリテラルで記述するか引数経由で期待通りの値が渡される前提で話をしてきた

Page 71

リテラルは実行時変化しない

<?php

function print_add(int $a, int $b): int|float{echo $a + $b;}

print_add(1, 2);

Page 72

外部入力

<?php

function print_add(int $a, int $b): int|float{echo $a + $b;}

print_add($_GET['a'], $_GET['b']);

期待する形式の値が渡

クエリがきちんと渡されたら動く

されなければエラー

Page 73

厳密な入力

<?php declare(strict_types=1);

function print_add(int $a, int $b): int|float{echo $a + $b;}

print_add($_GET['a'], $_GET['b']);

?a=1&b=2 が渡されようが

厳密な型付けを有効化

型が違うので絶対動かない

Page 74

DB(PDO)から取得した型

PDOはデフォルトでプリペアドステートメントのエミュレーションが有効その場合は全てのカラムが文字列になってしまう

Page 75

デシリアライズした値

unserialize(),json_decode()など

これらも何が入っているかわからない

Page 76

まとめ

Page 77

今回はPHPに組み込みの型の使いかたに絞って説明しました

Page 78

実際に厳密に型を付けていくと、これだけでは力不足なポイントが多く出てきます

Page 79

‒arrayの型と向き合う #phpstudy https://tadsan.fanbox.cc/posts/854598

Page 80

‒array shapes記法(Object-like arrays)と旧PSR-5記法で型をつける

https://qiita.com/tadsan/items/bfa9465166c351da37e5

Page 81

‒このPHPがテンプレートエンジンのくせに慎重すぎる (前篇)https://qiita.com/tadsan/items/bf61520eb2d455e0e8b4