callableタイプヒントの追加と配列から文字列への暗黙の変換時の警告 (PHP5.4 Advent Calendar 2011 Day 21)

PHP5.4 Advent Calendar 2011 21日目の記事です。
20日目は Travis CI で PHP 5.4 も CI する, PHPUnit も Behat もやる – Born Too Late (@yuya_takeyamaさん)でした。

つい先日、Windows7でビルトインサーバを動かしてみたばかりのPHP5.4初心者ですが、個人的に嬉しいふたつの変更点について紹介させていただきます。

タイプヒンティングに callable が追加された(Beta1より)

15 Sep 2011, PHP 5.4.0 Beta1
- General improvements:
. Added callable typehint. (Hannes)

callableという擬似型をタイプヒンティングに指定できるようになりました。
従来、is_callable()関数に対してtrueを返すユーザーコールバックは、文字列・配列・無名関数・マジックメソッド__invoke()実装クラスのインスタンスなど様々な値を取り得る(PHPマニュアルの 疑似的な型および変数参照)ため、タイプヒンティングが利用できない問題がありました。
また、PHP5.4以前ではそれぞれの実行方法も異なっていたため、あらゆるcallableの登録や実行を適切に処理するには煩雑な記述が必要でした。
PHP5.4からは、callableタイプヒントの追加とコールバックの配列を無名関数のように呼べるようになった(@rskyさんの記事 PHP 5.4 の配列Tips で解説されています)ことにより、簡潔に書けるようになります。

まずは検証に当たって、エラータイプおよびメッセージの確認のため下記のようなエラーハンドラを設定しました。

<?php
set_error_handler(function($errno, $errstr, $errfile, $errline){
    $titles = array (
        E_ERROR => 'Fatal error',
        E_WARNING => 'Warning',
        E_NOTICE => 'Notice',
        E_STRICT => 'Strict standards',
        E_RECOVERABLE_ERROR => 'Catchable fatal error',
        E_DEPRECATED => 'Depricated',
    );
    echo sprintf('<p>%s[%d]: %s</p>',
        (isset($titles[$errno])) ? $titles[$errno] : 'Unknown error',
        $errno, htmlspecialchars($errstr, ENT_QUOTES, 'UTF-8'));
    return true;
});

※以下、サンプルコードの動作確認はWindows7、PHP5.3環境はXAMPP1.7.7添付の5.3.8、PHP5.4環境は5.4.0RC3ビルトインサーバで行いました。

<?php
namespace Acme;

class U {
    public static function exec($var, callable $callback) {
        return $callback($var);
    }
    public static function foo($var) {
        return sprintf('%s! foo!', $var);
    }
    public function bar($var) {
        return sprintf('%s! bar!', $var);
    }
    public function __invoke($var) {
        return sprintf('%s! invoke!', $var);
    }
}

$text = '12345';

// 関数名の文字列
echo U::exec($text, 'number_format');
// 12,345
// (PHP5.3の場合) Catchable fatal error[4096]: Argument 2 passed to Acme\U::exec() must be an instance of Acme\callable, string given, called in *** on line ** and defined

// スタティックメソッドを指定したcallback配列
echo U::exec($text, array(__NAMESPACE__ . '\U', 'foo'));
// 12345! foo!

// インスタンスメソッドを指定したcallback配列
echo U::exec($text, array(new U(), 'bar'));
// 12345! bar!

// 無名関数(Closure)
echo U::exec($text, function($var) {
    return sprintf('%s! closure!', $var);
});
// 12345! closure!

// __invoke()実装クラスのインスタンス
echo U::exec($text, new U());
// 12345! invoke!

// callableではない(存在しないクラスのスタティックメソッドを指定したcallback配列)
echo U::exec($text, array('U', 'foo'));
// E_RECOVERABLE_ERROR が発生
// Catchable fatal error[4096]: Argument 2 passed to Acme\U::exec() must be callable, array given, called in *** on line *** and defined
// Fatal error: Class 'U' not found in...

PHP5.3ではcallableタイプヒントは実行時にユーザー定義のcallableクラスとして解釈され、全てのケースで「must be an instance of Acme\callable」のE_RECOVERABLE_ERRORが発生しましたが、PHP5.4ではcallableタイプヒントにより、無効なcallback配列が指定された最後のケースのみ「must be callable」のE_RECOVERABLE_ERRORが発生しました。

余談になりますが、サンプルではユーザー定義のエラーハンドラでtrueを返しているため、E_RECOVERABLE_ERROR発生後も処理が継続され、最後に「Class ‘U’ not found」のE_ERRORが発生してデフォルトのエラーハンドラが呼ばれて終了しています。
ユーザー定義のエラーハンドラを使用しない場合は、E_RECOVERABLE_ERROR発生時点で処理が中断されました。

"Closure"クラスについて

callableタイプヒントについては、こちらの PHP: rfc:callable [PHP Wiki] を拙い英語読解力で読んだところ、将来実装が変更されるかもしれない "Closure" クラスをタイプヒントに使えないことへの対策としての意味もあったようです。
ただ、日本語版のドキュメント PHP: Closure – Manual を確認したところ、現在はこのような記述に変わっていました。

無名関数は PHP 5.3 で実装された機能で、この型のオブジェクトを生成します。かつてこれは、内部実装がたまたまそうなっているだけという扱いでした。しかし今では、この事実を前提として考慮してもかまいません。PHP 5.4 以降ではこのクラスにメソッドが用意され、生成した無名関数をさらにコントロールできるようになります。

PHP5.4で bind(), bindTo() が追加されたことでClosureクラスの実装が確定したということでしょうか。
ともあれ、依然としてClosureではないcallableは存在しますので、特にインタフェース定義などでcallableタイプヒントが有用に機能するケースはあると思います。

配列から文字列への暗黙の変換時に NOTICE が発生するようになった(RC1より)

11 Nov 2011, PHP 5.4.0 RC1
- General improvements:
. Changed silent conversion of array to string to produce a notice. (Patrick)

PHP5.4以前では、echo や print の引数とされた場合、結合演算子により文字列と結合された場合、あるいは sprintf(‘%s’, array()) など、配列から文字列への変換が発生した際、何の警告もなく文字列 "Array" が返されていました。
このような配列から文字列への変換が発生した際に、E_NOTICEレベルのエラーを発生するようになったという変更です。
注意深い方にはそれほど影響はないと思いますが、私のような粗忽者には嬉しい改善です。

<?php
$array = array();

echo $array;
// Notice[8]: Array to string conversion
// Array

'text' . $array;
// Notice[8]: Array to string conversion

var_dump((string)$array);
// Notice[8]: Array to string conversion
// string(5) "Array"

var_dump(strlen(sprintf('%s', $array)));
// Notice[8]: Array to string conversion
// int(5)

$array = new \ArrayObject();
var_dump((string)$array);
// Catchable fatal error[4096]: Object of class ArrayObject could not be converted to string
// string(0) ""

PHP5.3では警告なく「Array string(5) "Array" int(5)」と出力されたのに対して、PHP5.4ではいずれも「Array to string conversion」のE_NOTICEが発生しました。
(string)による明示的なキャストでも警告されますので、そういうトリッキーなコードを書いている方はお気をつけください。(あまりないとは思いますが…)

また、最後のケースでは念のためArrayObjectの挙動を確認しましたが、当然ながらArrayObjectは配列ではなく__toString()メソッドも定義されていないためPHP5.3、PHP5.4共に「could not be converted to string」のE_RECOVERABLE_ERRORが発生しました。
(例によってユーザ定義のエラーハンドラで処理が継続され空文字が返されていますが、デフォルトのエラーハンドラでは処理が中断されました)

以上、PHP5.4の情報をリアルタイムでチェックされていた方にとっては何を今更という感じの小ネタですが、少しばかり膨らませて紹介させていただきました。

明日22日目は @do_aki さんです。

このエントリをつぶやくこのWebページのtweets このエントリーを含むはてなブックマークはてなブックマーク - callableタイプヒントの追加と配列から文字列への暗黙の変換時の警告 (PHP5.4 Advent Calendar 2011 Day 21) この記事をクリップ!Livedoorクリップ - callableタイプヒントの追加と配列から文字列への暗黙の変換時の警告 (PHP5.4 Advent Calendar 2011 Day 21) BuzzurlにブックマークBuzzurlにブックマーク @niftyクリップに追加 newsing it! Bookmark this on Delicious Share on Tumblr
This entry was posted in PHP and tagged , . Bookmark the permalink.

コメントを残す

メールアドレスが公開されることはありません。

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" cssfile="">