Posted at

型がある時代のPHP、テンプレートエンジンでも型チェックしたい

DBからデータを取得して表示するページがあるとする。


src/Page/Product.php

<?php

class Product
{
public static function index($db)
{
// ...
}

public static function detail($db, $productId)
{
// ...
}
}


こんなページ専用のクラス(アクションクラス、コントローラークラス)があったとして、


src/Model/Product.php

<?php

namespace MyProject\Model;

use MyProject\Dto;

class Product
{
private $db;
public function __construct($db)
{
$this->db = $db;
}

/**
* @return array<Dto\Product>
*/

public function getList($limit, $offset): array
{
// $db->find($this->conditions(), $limit, $offset)
return [];
}

public function get($productId): Dto\Product
{
// return $db->get($productId);
return new Dto\Product();
}
}


こういうモデルを呼び出して、


src\Dto\Product.php

<?php

namespace MyProject\Dto;

use DateTimeInterface;
use DateTimeImmutable;

class Product
{
/** @var int */
public $product_id;
/** @var string */
public $product_name;
/** @var string */
public $product_code;

/** @var int */
public $price;

/** @var ?int */
public $discount;

/** @var DateTimeInterface */
public $created_at;
/** @var ?DateTimeInterface */
public $updated_at;

public $db_created_at;
public $db_updated_at;

public function __construct()
{
// ...
$this->created_at = new DateTimeImmutable($this->db_created_at);
if ($this->db_updated_at &&
$this->db_updated_at != $this->db_created_at)
$this->updated_at = new DateTimeImmutable($this->db_updated_at);
}
}


こういうオブジェクトを取得する。

そうするとページ用クラスは


src/Page/Product.php

<?php

namespace MyProject\Page;

use MyProject\Model;
use MyProject\template;

class Product
{
public static function index($db)
{
$model = new Model\Product($db);
$products = $model->getList(10, 0);

template\product\index::exec($products);
}

public static function detail($db, $productId)
{
$model = new Model\Product($db);
$product = $model->get($productId);

return template\product\detail::exec($product);
}
}


こういう感じになる。$productDto\Product だと分かっているので、テンプレート用クラスは


template\product\detail.php

<?php

namespace MyProject\template\product;

use MyProject\Dto;

class detail
{
public static function exec(Dto\Product $product)
{
// ...
}
}


こうなる。

template.png

nullable がエラーになっていることに気付く。

template2.png

修正して、めでたしめでたし。

なんか今までは色々動的生成して一周回ってベタ書きしたくなってる。

phpstanを使えば簡単なif文までチェックしてくれるのに、テンプレートエンジンで @if とか書いちゃって {{ $product->updated_at->format('Ymd') }} とか書くと静的解析が効かない。

昔は型を書いても意味なかったし、文字でも数字でもnullでも取り敢えず全部テンプレートエンジンに突っ込んで何らかのキーで値を引っ張ってくるのが手っ取り早かった。

今は、というかPHP7.4から先は、上のコードで言うDtoのプロパティにがっつり型を書けるわけだし、そうすると素のPHPでHTML出力した方が型チェック出来て幸せなのでは?とか思ったりしている。

タグジャンプを設定しているなら、テンプレートの変数からコードへの行き来もすぐ出来るので楽ちん。(テンプレートエンジンを使っていてもキーワードで飛べることがあるけど、飛びたい箇所から外れることも多い)

型を書く派の人達は、HTMLと変数を結合する箇所での型チェックはどうしているんだろう。

IDE使わないから知らないだけで、例えばPhpStormとBladeならテンプレートエンジンも標準で型チェックされる、とかになっているのだろうか。