LoginSignup
0
0

More than 3 years have passed since last update.

PHPStanで素のPHPをテンプレートとして使うとき、htmlspecialcharsをチェックする

Posted at

前にテンプレートエンジンでも型チェックしたいということを書いたが、まだ一点不満があった。

そもそもテンプレートエンジンを使う唯一にして最大のメリットは、htmlspecialcharsを漏れなく実行することにある。
PHP自体がテンプレートエンジンと言われていても、別途テンプレートエンジンのライブラリを使うのは

<?= $message ?>

というような出力を書いてしまってXSSが発生するということを防ぐのが目的である。
他の機能は、例えばBladeなら

@if ($flg)
  <span>OK</span>
@endif

@foreach ($data as $key => $row)
  <div>{{ $row->id }}: {{ $row->name }}</div>
@endforeach

<?php if ($flg): ?>
  <span>OK</span>
<?php endif; ?>

<?php foreach ($data as $key => $row): ?>
  <div><?= $row->id ?>: <?= $row->name ?></div>
<?php ehdforeach; ?>

素のPHPを使ってもあまり変わらない。
違いは、<?= $row->name ?> の箇所が少々危険である、ということぐらいだ。
おそらく<?= htmlspecialchars($row->name) ?>と書きたかったはずだ。

つまり、素のPHPをテンプレートエンジンとして使ってPHPStanで型チェックをするためには、あと一手htmlspecialcharsをチェックする機能も必要になってくる。

幸いPHPStanは拡張機能というプラグイン的な仕組みがあるので、ちょっとしたエラーチェックのルールを独自に追加できる。

なので作った。
https://github.com/nishimura/phpstan-echo-html-rule

src/ProductDto.php
<?php

namespace App;

class ProductDto
{
    /** @var int */
    public $product_id;
    /** @var string */
    public $name;
    /** @var ?string */
    public $description;
}

このようなデータがあるとき、表示用のHTMLを含むPHPテンプレートクラスは

src/ProductHtml.php
<?php
namespace App;
class ProductHtml {
    public function view(ProductDto $product): void {
?>

<div>
  <div>
    <?= $product->product_id ?>
  </div>
  <div>
    <?= $product->name ?>
  </div>
  <div>
    <?= $product->description ?>
  </div>
</div>

<?php
    }
}

こんな感じになるだろう。
phpstan-echo-html-ruleを使えば

 3/3 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ ---------------------------------------------------- 
  Line   ProductHtml.php                                     
 ------ ---------------------------------------------------- 
  16     Parameter #1 (string) is not safehtml-string.       
  19     Parameter #1 (string|null) is not safehtml-string.  
 ------ ---------------------------------------------------- 


 [ERROR] Found 2 errors

htmlspecialcharsを使わずに出力している箇所のエラーを報告してくれるようにした。
独自の仮想的な型safehtml-stringを導入したので、htmlspecialcharssafehtml-stringを返す必要がある。

functions.php
<?php

/**
 * @param int|string|null $input
 * @return safehtml-string
 */
function h($input)
{
    return htmlspecialchars((string)$input);
}

/**
 * @param int|string|null $input
 * @return safehtml-string
 */
function raw($input)
{
    return (string)$input;
}

このようなユーティリティ関数を用意する。

そしてエラーを修正する。

src/ProductHtml.php
<?php
namespace App;
class ProductHtml {
    public function view(ProductDto $product): void {
?>

<div>
  <div>
    <?= $product->product_id ?>
  </div>
  <div>
    <?= h($product->name) ?>
  </div>
  <div>
    <?= h($product->description) ?>
  </div>
</div>

<?php
    }
}

これでテンプレートエンジンを使わなくてもhtmlspecialcharsなしの出力を網羅的にチェックできるようになった。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0