Page 1
今日からできる安心型付け入門
Introduction of typing in PHP
2021-05-29 YouTube
PHPカンファレンス沖縄
公開日:
by USAMI Kenta@tadsan
にオンラインのYouTube Liveで開催された『PHPカンファレンス沖縄2021』でレギュラーセッション(30分)として発表しました。
Introduction of typing in PHP
2021-05-29 YouTube
PHPカンファレンス沖縄
本日のお題
https://fortee.jp/phpcon-okinawa-2021/proposal/b90e3c04-ae04-4430-a28c-3f231e703c37
アジェンダ
PHPの型について
なぜ型をつけるのか
PHPの入出力型の特性
今回含まないもの
各静的解析ツールやIDE(PhpStorm)固有の型・機能
メタプログラミングに対応した型付け
用語について
単に関数と呼ぶとき、メソッドとクロージャを含む
定義に直接記述する型を型宣言(type declaration)、PHPDocを型注釈(annotation)と呼びます
PHPの型について
型の前に
プログラムには依存があるということを認める
依存とは
<?php
$a = 1;$b = 2;
echo $a + $b;
<?php
$a = 1;$b = 2;
echo $a + $b;
マスクされているという状態をコードに反映する
<?php
$a = 1;$b = 2;
echo $a + $b;
<?php
function print_add($a, $b){echo $a + $b;}
print_add(1, 2);
汎用的な処理と実際の値を切り離すことができた
<?php
こいつらが何者かわからん
function print_add($a, $b){echo $a + $b;}
print_add(1, 2);
型をつけよう
型が明確になった
<?php
function print_add($a, $b){echo $a + $b;}
print_add(1, 2);
そもそも型とは
データ型
PHPで使える値の種類のこと
あたいへんすうていすう
値=変数・定数にセットしたり、関数呼び出しに渡せるもの
値は何らかのデータ型に属する
PHPと型
PHPは型システムの分類として弱い動的型付けあるいは「型なし」と分類されがち
しかしPHPは単なる型なしと呼ぶには型を使ってできることが多い
PHPの実行時型検査
関数のパラメータと戻り値、プロパティに型宣言できる
強力な実行時型検査
実行時にコードの型宣言通り型で実行されることが保障
TSやPythonとは異なる
PHP5の型ヒント
Type Hintingと呼ばれてた
array, callableあるいはクラス/インターフェイスしか書けなかった
PHP7.0以降ではスカラー型に対応し、型宣言に改称
PHPの静的型検査
Psalm, PHPStanなどの検査ツールが豊富
PhpStormは検査能力は劣るが簡単に導入できる
今回の発表では個別のツールの導入方法には触れません
データ型(1) int 整数型
0, 1, 2や-100のような数
最大値はPHP_INT_MAXで最小値はPHP_INT_MIN
データ型(2) string 文字列型
"a" "xyz" "あいう" のような文字の並びだと思ってくださいから長さ0の "" (空文字列)も存在する
PHPの文字列値はエンコーディングを持たないバイト列
ここで簡単なコードを考えてみましょう
値
<?php
細かくいうと$a = 1のような変数代入も式
値"2"
値%
値"2"
$a = 1;$b = "2";$c = $a + $b;
式1 + "2"
echo $c;
値%
値%
リテラルと式
コードに直接書く値をリテラル(literal)といいます1 2.3 "abc" みたいなの
$a + $b のようなコードを式(expression)といいます
int(1)
<?php
string("2")
int(1)
string("2")
$a = 1;$b = "2";$c = $a + $b;
式1 + "2"
echo $c;
int(3)
int(3)
データ型(3) float 浮動小数点数型
0.1, 10.0, -0.3, INF, -INFのような値
PHPでは整数の範囲を外れた数はfloatになる
PHP_INT_MAX+1 は float
データ型(4) bool 論理型
true と false の二種類だけの値からなる型
$a==$b や $a < $b のような比較式の結果はbool
PHPでは $a && $b もbool
スカラー型のまとめ
bool, int, float, stringの4種をスカラー(scalar)と呼ぶ
後述する複合型でも特殊型でもないものという共通性
複合型
内部に別の値を持てる型
array (配列)
object (オブジェクト)
※ PHPマニュアルはcallable特殊型とiterable特殊型も複合型として記載
データ型(5) array 配列型
他言語のリストと辞書(ハッシュ)を兼ねた型だが、基本は辞書的
[1, 2, 3] や [] (空配列)
['name' => 'taro', 'birthday' => '09-13']
のように複数の値を格納できる
データ型(5) array 配列型
作成時にキーを指定しないと0からの連番になる
[1]と [0 => 1] は同じ
$a = [3 => 'a'] は
[null, null, 'a']ではない
データ型(6) object
プロパティ(メンバー変数)を持ち、$obj->prop のような構文でアクセスできる値の型
objectには属するクラスがあり、組み込みクラス、ユーザー定義、stdClassがある
型宣言とクラス
型宣言にはクラス名またはインターフェイスを記述できる
事実上のユーザー定義型
インターフェイスを活用すると実装と宣言を分離できる
依存性逆転の原則(DIP)
全てinterfaceで指定すべきか
抽象化でたしかに実装詳細への依存を避けることができる
全ての依存をインターフェイスにすべきかは悩ましい
DTOやValueObjectなど
DataTimeInterface
特殊型
単体で型宣言できない特殊な値
resource (リソース)
null (空値)
※ nullはnullableの一部やPHP8のユニオン型の一部として記述可能
型ではないが特殊な戻り値
function(): void {}
値を返さないことを示す
値を返さないということは、副作用があるというマーク
DBにデータを記録する、メールを送る、例外送出
複合的な型表記
nullable型
?DateTimeとか
ユニオン型
A|Bとかstring|falseとか
PHP7ではPHPDocコメントに書く必要がある
クラスの型付けを考えてみましょう
Book (本)
プロパティ(メンバー変数)として、name(書名)とAuthor(著者オブジェクト)を持つ
メソッドは今回は割愛
<?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;}
<?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;}
<?php // PHP 8.0以降
class Book{public function __construct(private string $name,private Author $author) {// プロパティ代入は不要
// $this->name = $name;// $this->author = $author;}
仕様変更
😁
著者が複数の本っていっぱいあるんじゃ
著者が複数…配列で受け取ろう
$author
↓
$author
s
<?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;}
<?php // PHP 7.4
arrayって…情報量減っちゃってね?
class Book{private string $name;private array $authors;public function __construct(string $name, array $authors) {// ...}
array型宣言は結構困る
今回欲しいのはAuthorだけが入った配列
arrayだけでは不十分
['name'=>'太宰治']みたいな配列がほしいわけではない
ここで出てくるのがPHPDoc
<?php // PHP 7.4
class Book{private string $name;private array $authors;public function __construct(string $name, array $authors) {// ...}
<?php // PHP 7.4
class Book{private string $name;/** @var Authors[] */private array $authors;public function __construct(string $name, array $authors) {// ...}
<?php // PHP 7.4
class Book{private string $name;/** @var Authors[] */private array $authors;/*** @param Authors[] $authors*/public function __construct(string $name, array $authors) {// ...
PHPDoc(型注釈)について
DocComment /** … */に型情報を書くことで、型宣言できない詳細な型を付ける
PHPが対応しない型や、実行時に検査しにくい型を記述できる
PHPDoc(型注釈)にしか書けない型
コレクションの型
ジェネリクス
ユニオン型はPHPDocで長年使われていたがPHP8で型宣言に取り入れられた
PHPDoc(型注釈)にしか書けない型
リテラル型・定数型
array-shapes(Object-like arrays)
<?php
/*** 検索エンジンを単語検索する
*/function search(string $word, string $mode) {// ...}
ユーザーが検索したいワード
検索モードはどんな文字列で
なんでも入れていい
も入れたいわけではない
<?php
/*** 検索エンジンを単語検索する
* @phpstan-param 'exact'|'partial' $mode*/function search(string $word, string $mode) {// ...}期待値をResultでそのまま書
ける
<?php const SEARCH_MODE_EXACT = 'exact';const SEARCH_MODE_PARTIAL = 'partial';
/*** 検索エンジンを単語検索する
* @phpstan-param SEARCH_MODE_* $mode*/function search(string $word, string $mode) {// ...}
ユーザーが検索したいワード
なんでも入れていい
型なしはどこから来るの?
ここまでは値をリテラルで記述するか引数経由で期待通りの値が渡される前提で話をしてきた
<?php
function print_add(int $a, int $b): int|float{echo $a + $b;}
print_add(1, 2);
<?php
function print_add(int $a, int $b): int|float{echo $a + $b;}
print_add($_GET['a'], $_GET['b']);
期待する形式の値が渡
クエリがきちんと渡されたら動く
されなければエラー
<?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 が渡されようが
厳密な型付けを有効化
型が違うので絶対動かない
DB(PDO)から取得した型
PDOはデフォルトでプリペアドステートメントのエミュレーションが有効その場合は全てのカラムが文字列になってしまう
デシリアライズした値
unserialize(),json_decode()など
これらも何が入っているかわからない
まとめ
今回はPHPに組み込みの型の使いかたに絞って説明しました
実際に厳密に型を付けていくと、これだけでは力不足なポイントが多く出てきます
‒arrayの型と向き合う #phpstudy https://tadsan.fanbox.cc/posts/854598
‒array shapes記法(Object-like arrays)と旧PSR-5記法で型をつける
https://qiita.com/tadsan/items/bfa9465166c351da37e5
‒このPHPがテンプレートエンジンのくせに慎重すぎる (前篇)https://qiita.com/tadsan/items/bf61520eb2d455e0e8b4
