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

公開日:

オンライン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

1

のような変数代入も式

"2"

$a = 1;

1

$b = "2";

"2"

$c = $a + $b;

1 + "2"

echo $c;

3

Page 33

リテラルと式

コードに直接書く値を
リテラル といいます

(literal)
みたいなの

1 2.3 "abc"

のようなコードを

$a + $b

式 といいます

(expression)

Page 34

値には必ず型がある

int(1)

<?php

string("2")

$a = 1;

int(1)

$b = "2";

string("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

は float

PHP_INT_MAX+1

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

$authors

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