PHPの型システム
変数自体には型は無い
値の方に型を持つ
gettype()で型を調べられる
動的型付け言語
暗黙的型変換
10 + “5” = 15
stringをintに自動で変換されている
10 + “5e” = 15
数値じゃない値が出てきたところで切って自動的にintに変換
10 + “5e2” = 510
5e2:指数表記になっている
型宣言
function double(int $i) : int {}
引数や戻り値に型を指定出来る
関数に入った瞬間はint型を保証
戻り値もint型を保証
double(“5e”) // TypeError
型宣言使うならstrictモードをオンにする
declare(strict_types = 1)
まとめ
値が型を持つ
暗黙的型変換で柔軟に開発
型宣言で厳密に開発
ユーザーが選択(使い分け)出来る
型宣言を利用したコード実装
Demo
型宣言なし
スカラー型で型宣言
ユーザー定義型で型宣言
ユーザー定義型の例
<?php
//strictモードオン
declare(strict_types=1);
//税込価格計算クラス
final class priceWithTaxRateCalculate{
public static function calculate(Price $price, TaxRate $taxRate): PriceWithTax //戻り値は税込価格型にする
{
return new PriceWithTax(intval($price->asInt() * (1 + $taxRate->asFloat())));
}
}
//プライス型
final class price
{
private $value;
public function __construct(int $value)
{
$this->value = $value;
}
public function asInt():int
{
return $this->value;
}
}
//税率型
final class taxRate
{
private $value;
public function __construct(float $value)
{
//消費税が1以下で無い場合は例外
if(!($value < 1)){
throw new InvariantException('Invalid taxRate is given');
}
$this->value = $value;
}
public function asFloat():float
{
return $this->value;
}
}
//税込価格型
final class priceWithTaxRate
{
private $value;
public function __construct(int $value)
{
$this->value = $value;
}
public function asInt():int
{
return $this->value;
}
}
?>
ドメイン特化型の実装Tips
アプリケーションドメインを表現
ドメインルールを検証
型宣言による表現と強制
インスタンスの生成 = ルールを満たしていることを保証
PHPdockは実行時は役に立たない
型宣言で表現できる範囲は限定される
両方を組み合わせて使う
安易に継承を使わない
継承元クラス型への適合を防ぐ
継承元のクラスをそのまま受け継いでしまう
PriceWithTaxがPriceを継承していた場合
税込価格を商品価格として渡してしまう場合チェックが効かない
継承元メソッドが含まれてしまう
必要の無いメソッドが大量に含まれるクラスが出来てしまう
出来るだけドメインに特化したメソッドのみにする
実装を共有したい場合
委譲やトレイトで共有
イミュータブルにすると安全
完全コンストラクタ
不変条件の検証が一度で済む
コンストラクタで値をセットしても、setValue()みたいなメソッドがあると値がいつでも変えられてしまう
ミュータブルな場合は都度検証
ファクトリメソッド
newの乱立を防ぐ
コンストラクタをprivateに
最後に
型宣言で型を強制
ドメインを型で表現
型で気持ちいい開発を!
質疑(一部)
Q;ドメイン特化型を導入した場合どのくらいパフォーマンスに影響があるか
A:ケースバイケース、確かにスカラー型の方がメモリは食わないが優先度による
Q:型を使った実装を少しずつ導入する場合どこから始めればいいか、単体テストもやって無い状況
A:数値で仕様が決まっている部分、日付など。小さなクラスを作ってそれの単体テストを書くところから
Q:PHP7.1でnullableが入ったが、エラーの場合例外で返すか、nullで返すか
A:基本的にはnullで返すのはあまりよろしく無い。例外で返した方がわかりやすいのではないか。nullが返ってきた場合、使う側も扱いづらい。nullオブジェクトを定義する等。
Q:ドメイン型からデータベースの型への変更をどうするか
A:Repository層で処理。DBから取得した値を特化型に変換して返す等。
Q:ソースの別プロジェクトへの流用がやりにくいのでは無いか
A:流用すると、使わないメソッド等が入ったクラスが出てきてしまうため、ドメイン特化型の方がいい。ただ全てを特化型にすると時間がかかるので、ケースバイケースで対応。