初心者向け タイプヒンティングとはなんなのかというお話

by @dekokun on 2013/12/14 15:33

Tagged as: PHP.

どうも、PHP Advent Calendar 2013の14日目の記事です。

昨日は@ockeghemPHPとセキュリティの解説書12種類を読んでSQLエスケープの解説状況を調べてみたでしたね。

最近、仕事を一緒にし始めた後輩がPHPに慣れておらず、PHPのタイプヒンティングのことを知らなかったので(そして、さっき一緒にお酒を飲んだ別の後輩もタイプヒンティングを知らなかったので)、彼らに向けて、タイプヒンティングとはなにか、なぜタイプヒンティングを使うのかを解説する記事となります。

PHPのことをよく知っている皆様におかれましては特に得るものはないお話なのかなと思います。ご了承ください。なお、下記のような流れを経て、このブログの内容は当初の予定から変遷しました。

タイプヒンティングとは

関数及びメソッドが受け取る引数がどのオブジェクトなのか(もしくはなんのインターフェースを備えるか)、配列なのか、コールバックなのかなどを指定することが出来る機能です。

具体例

こんな感じ

<?php
// この"Iterator $a"の"Iterator"部分がタイプヒンティング
// ここで、$aはIteratorと同様のインターフェースを備えていることが保証される
function func(Iterator $a) {
    return $a->next();
}

上記の “Iterator”部分がタイプヒンティングとなります。

違反するとどうなるの?

例えば、タイプヒンティングでクラスを指定したにもかかわらずスカラー値を指定された場合はエラーになります。

<?php

function func(TestClass $a) {
    return $a;
}

func(1);

上記のようなコードは以下のようなFatal errorが投げられます

PHP Catchable fatal error:  Argument 1 passed to func() must be an instance of TestClass, integer given, called in /private/var/folders/hy/xc91_vr542z40knyzs4h9n2r0000gn/T/vHqWMFW/2 on line 8 and defined in /private/var/folders/hy/xc91_vr542z40knyzs4h9n2r0000gn/T/vHqWMFW/2 on line 4

Catchable fatal error: Argument 1 passed to func() must be an instance of TestClass, integer given, called in /private/var/folders/hy/xc91_vr542z40knyzs4h9n2r0000gn/T/vHqWMFW/2 on line 8 and defined in /private/var/folders/hy/xc91_vr542z40knyzs4h9n2r0000gn/T/vHqWMFW/2 on line 4

このエラーをコード中で捕捉したい場合は、エラーハンドラの中で“E_RECOVERABLE_ERROR”を捕捉しましょうね。

何が嬉しいの?

  • エラーを早めに検知可能
  • (コメントなどと比べて)検証可能、腐ることのない形(=メソッドのドックコメントなどとは違うのですよ)でメソッドの前提条件を指定可能
  • そもそも、変なことが起きた時にエラーが起きて死ぬというのは、エラーを返り値で返しがちなPHPにとっては非常に重要なことだと思います

どんな時に使うの?

少なくとも、現世利益的なことを話しますと、タイプヒンティングによって、期待しないクラス(とか配列とかコールバックとか)が引数にわたってこないことが保証されるため、バリデーションを減らすことができます。

たとえば、以下のようなコードの場合、

<?php

function func(array $a) {
    return $a;
}

$aは配列であることが保証されるため、func関数の中で

if(! is_array($a)) {
    throw new \InvalidArgumentException;
}
// do_something();

などのチェックを行う必要はなくなります。

ライブラリなどの作者の意図をプログラムで伝えやすくなります

以下のようなコードがあった際に、それを使う側は引数に配列が期待されているかどうかは実装を読まないと(もしくはコメントなどを読むことでしか)判断できません。使う側に実装を読ませるのはライブラリの敗北ですし、コメントで意図を伝えると、そのコメントのメンテナンスコストが以降発生します。

更に、間違えて配列以外を突っ込んでしまった際も、以下だとWarningのエラーが発生するだけでプログラムが落ちないために使い方を誤っていることに気づかない危険が高まります。

function func($a) {
    return array_shift($a);
}

しかし以下のように記述すれば、使う側はインターフェースを見るだけで、引数に配列が期待されていることがわかるだけです。そして、誤った使い方をすれば、プログラムが死ぬため、すぐに気づくことができます(これは、ヴァリデーションでも同様のことは可能ですが、それならタイプヒンティング使ったほうが圧倒的に楽ですよね)

function func(array $a) {
    return array_shift($a);
}

コールバック

以下のような引数にコールバックを期待している関数に文字列を渡すと、非常にわかりづらいエラーになります。 引数にコールバックを受け取る関数では、確実にcallableタイプヒントをつけるようにしましょう(PHP5.4~)

function fuga($a) {
    return $a();
}

以下、非常にわかりづらいエラー。

$variable = 'c';
fuga($variable);
PHP Fatal error:  Call to undefined function c() in php shell code on line 2

Fatal error: Call to undefined function c() in php shell code on line 2

とあるメソッドを持っているかどうかの検査

タイプヒンティングにインターフェースを使うなどして、ストラテジーパターンなどを行いやすいですね。

interface runnable
{
    public function run();
}

function test(runnable $a) {
    // タイプヒンティングを行っているので、引数がrunメソッドを持っていることを検証せずに使うことができる
    $a->run();
}

最後

つまり、タイプヒンティングのおかげで、簡単に早い段階でにわかりやすくエラーを検知できるのがとても素晴らしいということですね!そんなこと言っていると「それなら静的型付け言語を使えよ」と言われそうですが、PHPのタイプヒンティングは、固くいきたいところは固く、動的型付けの柔軟さ、生産性の高さ(?)を積極的に使っていきたいところはタイプヒンティングなしで書けるというわけで、そのどっちでも選択できる感じが素敵だなと思います。

便利だから積極的に使って行こったほうがいいと思います。後輩たちにおかれましても、少なくともライブラリのコードを読んで「ここはタイプヒンティングを使っているんだ!!」ということをわかるようになっていればと思います。

というか、なんか色々骨抜きなブログだ!!!!!

他にも、「名前空間の使い方」とか「composerをなぜ使うのか」とか、「migrationをなぜ使うのか」とか、伝えたいことは非常にたくさんあるのですが、本日は時間もないですしこのあたりで筆を置きます。

酔っ払ってるしね。

次回はk-motoyanによる「FWに依存しないDBマイグレーションツール」ですね。楽しみですね

comments powered by Disqus