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カンファレンス沖縄
お前誰よ
うさみけんた (@tadsan)
/ Zonu.EXE
本日のお題
‒今日からできる安心型付け入門 - 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
1
値
のような変数代入も式
"2"
値
$a = 1;
1
値
$b = "2";
"2"
式
$c = $a + $b;
1 + "2"
echo $c;
値
3
値
リテラルと式
コードに直接書く値を
リテラル といいます
(literal)
みたいなの
1 2.3 "abc"
のようなコードを
$a + $b
式 といいます
(expression)
値には必ず型がある
int(1)
<?php
string("2")
$a = 1;
int(1)
$b = "2";
string("2")
式
$c = $a + $b;
1 + "2"
echo $c;
int(3)
int(3)
データ型(3) float 浮動小数点数型
, , , ,
0.1 10.0 -0.3 INF -INF
のような値
PHPでは整数の範囲を外れた
数は になる
float
は float
PHP_INT_MAX+1
データ型(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(著
者オブジェクト)を持つ メソッドは今回は割愛
クラスの型付けを考える 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;
}
クラスの型付けを考える 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;
}
クラスの型付けを考える 8.0
以降
<?php // PHP 8.0
class Book
{
public function __construct(
private string $name,
private Author $author
) {
プロパティ代入は不要
//
// $this->name = $name;
// $this->author = $author;
}
仕様変更
😁
著者が複数の本って いっぱいあるんじゃ
著者が複数…
配列で受け取ろう
$author
↓
$authors
変更前 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;
}
変更後 7.4
<?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
変更前 7.4
<?php // PHP 7.4
class Book
{
private string $name;
private array $authors;
public function __construct(
string $name, array $authors
) {
// ...
}
PHPDocでプロパティ型付け
<?php // PHP 7.4
class Book
{
private string $name;
/** @var Authors[] */
private array $authors;
public function __construct(
string $name, array $authors
) {
// ...
}
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
) {
// ...
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