PHP5.4 alpha1リリース! PHP5.3からの新機能を見わたす

こんにちは、久保田です。

先月6月28日、PHP5.4 alpha1がリリースされました。PHP5.3では、名前空間や無名関数の追加など、言語機能に大きな追加が行われました。PHP5.4 alpha1 ではtraitが実装され、オブジェクト指向言語としてのPHPに大きな機能追加が行われました。他にも、<?=記法のデフォルト有効化、array dereferenceの追加、無名関数内の$thisの扱いの変化、JsonSerializableインターフェイスの追加など筆記すべき様々な機能追加、改善が見られます。この記事ではPHP5.4 alpha1で見えてきた新機能をサンプルコードを交えながら概観します。

とりあえずNEWSを訳す

PHP5.4 alpha1の変更点を記述した NEWS ファイルを翻訳してみたのが以下です。


PHP                                                                        NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
20 Jun 2011, PHP 5.4.0 Alpha 1
 
- autoconf2.59以上が ./buildconf によるconfigureスクリプトの生成のために
  サポート(そして要求)されるようになった。configureスクリプトのhelpの順番が
  おかしくなるかもしれないのを避けるために、autoconf2.60以上が望ましい。 (Rasmus, Chris Jones)
 
- 古い機能の削除:
  . break/continue $var 文法。 (Dmitry)
  . safeモードとそれに関連する全てのiniオプション。 (Kalle)
  . register_globalsとregister_long_arraysのiniオプション。 (Kalle)
  . import_request_variables関数。 (Kalle)
  . allow_call_time_pass_reference iniディレクティブ。 (Pierrick)
  . define_syslog_variables iniオプションとそれに関連する関数。  (Kalle)
  . highlight.bg iniオプション。(Kalle)
  . セッションバグ互換モード (session.bug_compat42 と session.bug_compat_warn iniオプション). (Kalle)
  . session_is_registered関数、session_register関数、 session_unregister関数 (Kalle)
  . y2k_compliance iniオプション (Kalle)
 
- PECLに移動したエクステンション: (Johannes)
  . ext/sqlite.(訳注: sqlite3の拡張とは別です)
- $_SERVER['REQUEST_TIME']が正確なマイクロ秒を含むように変更 (Ilia)
- "default_charset" php.iniオプションをISO-859-1からUTF-8に変更 (Rasmus)
- array_combine()のパラメータに二つとも空の配列を渡したときにfalseの代わりに
  空の配列を返すように変更。FR #34857。 (joel.perras@gmail.com)
- preg_match_all関数の三番目のパラメータをオプションに変更。FR #53238. (Adam)
- プロパティの追加の際にwarningに鳴ったときにnullかfalseに暗黙的にキャストされるのを変更。 (Scott)
- <?=記法は今やshort_tags設定に関わらず常に利用できる。 (Rasmus)
 
- 一般的な改善:
  . デフォルトでのマルチバイトのサポート。以前はphpをコンパイルする際に 
    --enable-zend_multibyte を使わなければならなかった。
    今ではこれを無効/有効にするには、php.iniのzend.multibyteディレクティブを通じて行う。 (Dmitry)
  . ext/mbstringからのコンパイル時の依存性を削除。 (Dmitry)
  . traitのサポートを追加。 (Stefan)
  . クロージャで再び$thisがサポートされた (Stas)
  . 配列のデリファレンスのサポートを追加。 (Felipe)
  . 配列を通じた非直接的なメソッド呼び出しをできるようにした。FR #47160。 (Felipe)
  . 再帰的なserialize関数の呼び出しでのオブジェクトのリファレンスのサポートを追加。 FR #36424。 (Mike)
  . http_response_code関数の追加。FR #52555。 (Paul Dragoonis, Kalle)
  . HTTPヘッダを送ったりデフォルトのHTTPヘッダが追加された後に優先的かつ即座に呼び出す関数を登録するheader_register_callback関数の追加。 (Scott)
  . DTraceサポートの追加。 (David Soria Parra)
  . 内部のアウトプットレイヤの改善。詳しくは README.NEW-OUTPUT-API を見ること。 (Mike)
  . unserialize関数のパフォーマンスの改善。
    (galaxy dot mipt at gmail dot com, Kalle)
  . unix用のビルドシステムが複数のPHPバイナリSAPIとひとつのSAPIモジュールを同時にビルド出来るようにした。 FR #53271, FR #52410。 (Jani)
  . debug_backtrace関数とdebug_print_backtrace関数に返すスタックフレームの量を制限するオプションパラメータを追加。 (Sebastian, Patrick)
  . ストリームのメタデータAPIサポートとストリームクラス内のstream_metadata()ハンドラの追加。 (Stas)
 
- Zendエンジンのメモリ使用量の改善: (Dmitry)
  . zend_function.pass_rest_by_referenceをZzend_function.fn_flagsのEND_ACC_PASS_REST_BY_REFERENCEに置き換えた。
  . zend_arg_info.required_num_argsの削除。といのも、これは内部関数(internal function)の為だけに必要とされるため。
    現在では、内部関数のための最初のarg_info(特別な意味を持つ)は、zend_internal_function_info構造体によって表される。 
  . zend_op_array.size, size_var, size_literal, current_brk_cont, backpatch_count はコンパイル時にのみ利用するものなので CG(context) に移動した。
  . zend_op_array.start_op はひとつのトップレベルop-arrayのインタラクティブシェルでの実行時のみ使われるものなので EG(start_op) に移動した。
  . zend_op_array.done_pass_twoをzend_op_array.fn_flags内のZEND_ACC_DONE_PASS_TWO に置き換えた。
  . pass_twoの時にop_array.vars array(reallocated)を削除した。
  . zend_class_entry.constants_updated を zend_class_entry.ce_flagsのZEND_ACC_CONSTANTS_UPDATED に置き換えた。
  . 内部クラスとユーザ定義のクラスの持つ違った情報によって生じる重複するメモリ領域を共有することでzend_class_entryのサイズを減らした。
    詳しくは zend_class_entry.info 共用体を見る。  
  . temp_variableのサイズを減らした。
 
- op_array.opcodes の構造を変更。定数の値は opcodeのoperandからseparate literal tableに移動した。 (Dmitry)
 
- Zend Engineのパフォーマンスの弱点や最適化を改善: (Dmitry)
  . 数値計算のためのほぼ確実なコードパスを executor に直接インライン化した。
  . リクエストのstartup/shutdown間の不必要なイテレーションを削除。
  . Changed $GLOBALS を JIT autoglobal に変更した。従って $GLOBALSは実際にそれが使われるときに初期化される。
    (このことはopcodeキャッシュに影響するかも!)
  . @(silence)演算子のパフォーマンスの改善。
  . 文字列のオフセット読み出しをシンプルにした。$str[1][0] は今や不適当な表現ではなくなった。
  . 実行時に繰り返し行う関数、クラス、定数、メソッドの登録を省略するためのキャッシュの追加。
  . interned stringsというコンセプトの追加。コンパイル時に知られている全ての
    文字列定数は、ひとつのコピーとしてメモリ確保され、決して変わらない。
  . 空のハッシュテーブルのためのemalloc/efree呼び出しやメモリ確保に関する最適化を追加。 (Stas, Dmitry)
  . ZEND_RECV は常にその結果としての IS_CV を持つ。
  . ZEND_CATCH は constant class names とともにのみ使われなければならない。
  . ZEND_FETCH_DIM_? は違って順番で配列やdeminsionオペランドを取ってくるかもしれない。
  . ZEND_FETCH_*_R 操作を単純化した。これからは EXT_TYPE_UNUSED フラグとともに利用することはできない。 
    このような操作は非常に稀で無用なケースだ。代わりにこの操作の後に ZEND_FREE が要求されるかもしれない。
  . ZEND_RETURN を分割して、ZEND_RETURN と ZEND_RETURN_BY_REF を新たな命令として追加した。 
  . litaral tableから事前に計算されたhash_valuesを用いて 値を持つグローバル定数へのアクセスを最適化した。 
  . executorの特殊化によって staticプロパティへのアクセスを最適化した。
    クラス名の定数が、前の ZEND_FETCH_CLASS 無しに 直接 ZENE_FETCH_* のオペランドで利用されるかもしれないようになった。
  . zend_stack と zend_ptr_stack のメモリ確保は実際に使われるまでに遅延される。
 
- Improved CLI SAPI: (Johannes)
  . Zendエクステンション名を指定することでその情報を表示する --rz コマンドラインオプションを追加した。
  . Interactive readline シェルの改善:
    . 出力のページャを指定するための "cli.pager" php.ini 設定の追加。
    . シェルプロンプトを設定するための "cli.prompt" php.ini 設定を追加。
    . 実行時にini設定を変更するための #inisetting=value ショートカットの追加。
    . シェルがfatal error時に落ちないように変更。
    . 共有されたreadlineエクステンションと共にインタラクティブシェルが動くよう変更。FR #53878。
 
- FastCGI SAPIの改善: (Dmitry)
  . apache互換の関数を追加: apache_child_terminate(), getallheaders(), apache_request_headers() and apache_response_headers()
  . FastCGIリクエストのパーサーパフォーマンスの改善。
 
- コア関数の改善:
  . number_format関数は マルチパイドのdecimal pointを削除したり、
    最初のバイトの1000セパレータを削除しないようになった。 FR #53457。(Adam)
  . hex2bin関数の追加。 (Scott)
 
- CURLエクステンションの改善:
  . CURLOPT_MAX_RECV_SPEED_LARGE, CURLOPT_MAX_SEND_SPEED_LARGE へのサポートの追加。 FR #51815。 (Pierrick)
 
- Dateエクステンションの改善:
  . エラーを投げずにparseFromFormatメソッドが次のテキストを追跡するための"+"修飾子を追加。 (Stas, Derick)
 
- DBAエクステンションの改善:
  . Tokyo Cabinetを抽象的なDBのサポートに追加。 (Michael Maclean)
  . Berkeley DB 5のサポート。 (Johannes, Chris Jones)
 
- ファイルシステムに関する関数の改善:
  . scandir関数が sorting_order値の様にSCANDIR_SORT_NONE を受け入れるようになった。
    FR #53407. (Adam)
 
- HASHエクステンションの改善:
  . Jenkinsの one-at-a-time ハッシュのサポート。 (Martin Jansen)
  . FNV-1 ハッシュのサポート。 (Michael Maclean)
  . Adler32 アルゴリズムをより早くした。 FR #53213。 (zavasek at yandex dot ru)
 
- intlエクステンションの改善:
  . 視覚的に困惑する文字や他のセキュリティ問題のためのチェックを行えるSpoofcheckerの追加。 (Scott)
 
- JSONエクステンションの改善:
  . JsonSerializableインターフェイスの追加。 (Sara)
  . json_decode関数が$optionsで利用する JSON_BIGINT_AS_STRING定数 を追加。 (Sara)
  . 数値のような文字列を整数に変換する JSON_NUMERIC_CHECK オプションのサポートを追加。json_encode関数で利用する。 (Ilia)
  . json_encode関数に新しいオプション、JSON_PRETTY_PRINTを追加。 FR #44331。 (Adam)
  . json_encode関数に新しいオプション、 JSON_UNESCAPED_SLASHESを追加。 FR #49366。 (Adam)
 
- LDAP拡張の改善:
  . ページングされた結果のサポート。 FR #42060。 (ando@OpenLDAP.org,
    iarenuno@eteo.mondragon.edu, jeanseb@au-fil-du.net, remy.saissy@gmail.com)
 
- MySQL拡張の改善:
  . MySQL: mysql_list_dbs関数を非推奨に。 FR #50667。 (Andrey)
  . mysqlnd: 名前付きパイプのサポート。 FR #48082。 (Andrey)
  . MySQLi: MySQLi内でのイテレータをサポート。 mysqli_result は Traversableインターフェイスを実装。 (Andrey, Johannes)
  . PDO_mysql: 4.1よりも古いMySQL client libraryのサポートを無くした。 (Johannes)
 
- OpenSSLエクステンションの改善 :
  . AESサポートを追加。 FR #48632。 (yonas dot y at gmail dot com, Pierre)
  . SessionTicket TLSエクステンションを無効化する"no_ticket" SSL context オプションを追加。FR #53447。 (Adam)
  . openssl_encrypt関数、openssl_decrypt関数に no padding オプションを追加。 (Scott)
 
- PDO DB-LIBの改善: (Stanley)
  . nextRowsetのサポートの追加。
  . bug #50755の修正。 (PDO DBLIB が OOM で失敗する).
 
- PostgreSQLエクステンションの改善:
  . PGNotify関数に"extra"パラメータのサポートを追加。
    (r dot i dot k at free dot fr, Ilia)
 
- Reflectionエクステンションの改善: (Johannes)
  . ReflectionExtension::isTemporary メソッド、ReflectionExtension::isPersistent メソッドの追加。
  . ReflectionZendExtension クラスの追加。
  . ReflectionClass::isCloneable メソッドの追加。 (Felipe)
 
- Sessionエクステンションの改善:
  . セッションデータにアップロードの進捗具合を保存する機能を追加。 (Arnaud)
  . コンパイル時に /dev/urandom か /dev/arandom があった場合、session.entropy_file のデフォルトをそれにするように変更した。(Rasmus)
 
- SPLエクステンションの改善:
  . RegexIterator::getRegexメソッドの追加。(Joshua Thijssen)
  . SplObjectStorage::getHashメソッドのフックの追加。 (Etienne)
  . CallbackFilterIteratorクラス、RecursiveCallbackFilterIteratorクラスの追加。 (Arnaud)
 
- ZLIBエクステンションの改善:
  . ファイルに関連しない機能を再実装した。(?) (Mike)
 
- SNMP拡張の改善 (Boris Lytochkin):
  . OO APIの追加。 FR #53594。 (php-snmp を書きなおした). 
  . 存在する関数が返す値を無害化した。今では処理が失敗した際にはfalseを返す。
  . GET/GETNEXT/SET クエリ内の ~infinite OID を許可した。リクエストの際には自動的にそれらを max_oids にチャンク化する。
  . 全カバレッジ達成したユニットテストの導入。
  . バグの修正
    . #44193 (snmp v3 noAuthNoPriv が動かない)
    . #45893 (Snmpバッファが2048バイトに制限される)
    . #46065 (snmp_set_quick_print()をリクエスト感から永続的にする)
    . #51336 (snmprealwalk (snmp v1) が OID tree の終わりを正しく扱えない)
    . #53862 (snmp_set_oid_output_format が default値を返すことができない)
 
## 未分類 ##
 
- PDO オブジェクトのバイナリ非互換性を修正。 (Dmitry)
- バグ#52211 の修正 (iconv 関数がエラーの際に文字列の一部を返す). (Felipe)

これはPHP5.4 alpha1リリース時点のものなので、PHP5.4正式リリースの際とは多少違ってくるので注意です。

traitの追加

PHP5.4で現れるであろう最も大きな変化は、恐らくtraitが追加されたことではないでしょうか。

traitとは、クラスから再利用出来るメソッドとプロパティのコレクションです。PHP5.4から導入されるtraitキーワードを用いてtraitを宣言して、クラス内でuse構文を用いると、traitの振る舞いをクラス内で利用できます。


<?php
trait A
{
    protected $hoge = "hoge";
    public function doSomething() 
    {
        echo $this->hoge;
    }
}
class B
{
    function doPiyo()
    {
        echo "piyo";
    }
}
class C extends B
{
    use A;
}
$c = new C;
$c->doSomething(); // "hoge"と出力される

CクラスはBクラスを継承していますが、A traitのメソッドとプロパティを利用しています。traitはクラスの継承関係に影響を与えずにメソッドなどの振る舞いを再利用できます。

このtraitはクラス内で複数指定することもできます。


<?php
trait A
{
    function doA()
    {
        echo "hogehoge\n";
    }
}
trait B
{
    function doB()
    {
        echo "fugafuga\n";
    }
}
class C
{
    use A, B;
}
$c = new C;
$c->doA(); // => hogehoge
$c->doB(); // => fugafuga

さらに以下のようにtrait内で複数のtraitを集約することができます。


<?php
trait A 
{ 
    function hoge() { /* ... */ } 
}
trait B 
{ 
    function fuga() { /* ... */ } 
}
trait C
{
    use A, B;
}

もしtraitを複数使う際、以下のようにメソッド名が重複する場合には、fatal errorが起きます。


<?php
trait A
{ 
    funtion doSomething() { /* ... */ }
}
trait B
{
    funtion doSomething() { /* ... */ }
}
class C 
{
    use A, B; // fatal error
}

これを避けるためには、PHP5.4で新たに導入されるinsteadofキーワードを用いてtraitのメソッドの優先度を設定できます。


<?php
class C
{
    use A, B 
    {
        A::doSomething insteadof B;
    }
}

上のようにAクラスのdoSomethingメソッドをBクラスのdoSomethingメソッドよりも優先する、という宣言をすることで、メソッド名の衝突を解決することができます。

また、asキーワードを用いてtraitメソッドのエイリアスを設定することができます。


<?php
trait A
{
    function doSomething()
    {
        echo "hoge";
    }
}
trait B
{
    function doSomething()
    {
        echo "fuga";
    }
}
class C
{
    use A, B
    {
        B::doSomething as doFugathing;
    }
}
$c = new C;
$c->doSomething(); // => hoge
$c->doFugathing(); // => fuga

エイリアスは、単にそのtraitのメソッドの名前を変えるわけではなく、オリジナルのメソッドの別名を付ける機能です。この事は以下のような例で確かめることができます。


<?php
trait A 
{
    function doSomething()
    {
        echo "hoge";
    }
    function doSomethingTwice()
    {
        $this->doSomething();
        $this->doSomething();
    }
}
class B 
{
    use A 
    {
        doSomething as doHoge;
    }
    function doSomething()
    {
        echo "fuga";
    }
}
$b = new B;
$b->doSomething(); // => fuga
$b->doSomethingTwice(); // => hogehoge

エイリアスとは別に、クラスからtraitを利用する際にasキーワードを用いてtraitのメソッドの属性を一部変更することもできます。


<?php
trait A
{
    function doSomething()
    {
        echo "hoge";
    }
}
class B 
{
    use A
    {
        doSomething as private doAnything;
    }
}
$b = new B;
$b->doSomething(); // => hoge
$b->doAnything(); // fatal error

この例では、traitから持ってきたdoSomethingメソッドにdoAnythingエイリアスを設定しprivateなメソッドにしています。他にもfinalやprotectedな属性をtraitのメソッドに加えることができます。ただし、staticな属性を与えることはできません。

他にもtraitにはabstractなメソッドを定義できます。abstractなメソッドを持ったtraitをクラス内で利用するときには、そのクラスはそのabstractなメソッドを実装する必要があります。


<?php
trait A
{
    abstract protected function doSomething();
    function doSomethingTwice()
    {
        $this->doSomething();
        $this->doSomething();
    }
}
class B 
{
    use A;
    protected funtion doSomething() 
    {
        echo "hoge";
    }
}
$b = new B;
$b->doSomethingTwice(); // => hogehoge

他にもtraitに関係する関数やメソッドが追加されました。関数ではtrait_exists関数、get_declared_classess関数が追加され、ReflectionClassクラスにはisTraitメソッド、getTraitNamesメソッド、getTraitAliasesメソッド、getTraitsメソッドが追加されました。


<?php
trait HogeTrait
{
    function doHoge() {
        echo "hogehoge";
    }
}
trait FugaTrait {
    function doFuga() {
        echo "fugafuga";
    }
}
class Piyo {
    use HogeTrait, FugaTrait {
        HogeTrait::doHoge as doSomething;
    }
}
var_dump(trait_exists("HogeTrait")); // => true
var_dump(trait_exists("Piyo"));      // => false
var_dump(get_declared_traits()); // => array("HogeTrait", "FugaTrait")
$r = new ReflectionClass("HogeTrait");
var_dump($r->isTrait()); // => true
$r = new ReflectionClass("Piyo");
var_dump($r->getTraitNames());
// => var_dump(array("HogeTrait", "FugaTrait")) と同じ
var_dump($r->getTraits());
// var_dump(array(new ReflectionClass("HogeTrait"), new ReflectionClass("FugaTrait"))) と同じ
var_dump($r->getTraitAliases());
// => var_dump(array("doSomething" => "HogeTrait::doHoge")) と同じ

ひと通りtraitができることについてまとめました。

- traitはメソッドとプロパティのコレクション
- クラスは型や継承関係に影響を与えずにクラス内にtraitの振る舞いを持ち込める
- trait同士のメソッド名が衝突しないようにどのメソッドを優先するか設定できる

参考:
- http://www.stefan-marr.de/2010/12/php-next-traits-presentation-for-afup-in-paris/ - traitを提案、実装したStefan Marr氏のtraitに関するスライド
- http://svn.php.net/viewvc/php/php-src/trunk/Zend/tests/traits/ - traitに関連するテストケース

<?= 記法のデフォルト有効化

PHP5.4 alpha1では、short_open_tagsディレクティブに関わらず <?= 記法が利用出来るようになりました。わざわざ "<?php echo $hoge ?>" と書かずに "<?= $hoge ?>" で済みます。

array dereference

PHP5.3では、関数やメソッドが配列を返す場合にその配列のメンバーにアクセスする場合は、一度変数に代入しなければなりませんでした。


<?php
function hoge() 
{
    return array("hoge" => "hogehoge", "fuga" => "fugafuga");
}
$h = hoge();
echo $h["hoge"]; // => hogehoge

5.4 alpha1では、そういったケースでもわざわざ変数に代入しなくても角括弧演算子を用いて配列のメンバーにアクセスできるようになりました。


<?php
function hoge()
{
    return array("hoge" => "hogehoge", "fuga" => "fugafuga");
}
echo hoge()["hoge"]; // => hogehoge

ただし、以下のような例はパーサエラーになってしまいました。


<?php
echo array("hoge" => "hogehoge")["hoge"]; // parser error

無名関数内の$this変数の扱いの変化

PHP5.4 alpha1では、メソッド内で宣言した無名関数の中で$thisを参照するとそのメソッドが属するオブジェクトを指すようになりました。また、無名関数が宣言されたメソッドの文脈も引き継がれるようになり、無名関数の中からprivateやprotectedなメソッドとプロパティにアクセスできるようになりました。


<?php
class A
{
    protected $hoge = "hoge";
    function getHogeCallback() 
    {
        return function() {
            echo $this->hoge;
        };
    }
}
$a = new A;
call_user_func($a->getHogeCallback()); // => "hoge"

JsonSerializableインターフェイスの追加

オブジェクトをJSON化する際に利用されるJsonSerializableインターフェイスが追加されました。JsonSerializableインターフェイスを実装すると、json_encode関数に渡した場合の振る舞いを設定することができます。


<?php
class Hoge implements JsonSerializable
{
    function jsonSerialize()
    {
        return array("hoge", "fuga");
    }
}
echo json_encode(new Hoge); // => '["hoge", "fuga"]'

組み込みウェブサーバ

これはPHP5.4 alpha1リリースに含まれる機能ではないのですが紹介しておきます。

PHPはapacheなど他のウェブサーバと組み合わせて動かすのが普通ですが、この変更でPHPそのものにウェブサーバが組み込まれます。この組み込みウェブサーバは、実際にサイトを運用するためのものではなく開発用に簡単にウェブサーバを立ち上げられるように追加されたものです。

コマンドラインから"-S"オプションを付けて起動すると、PHPがウェブサーバとして立ち上がりHTTPリクエストを受けるようになります。以下マニュアルからの引用です。


$ php -S localhost:8000
PHP Development Server is listening on localhost:8000 in /home/mydir ... Press Ctrl-C to quit.

リクエストを受けるとログがコマンドラインに流れます。


[Fri Jul  1 06:29:04 2011] ::1:63530: /
[Fri Jul  1 06:29:10 2011] ::1:63532: /index.php
[Fri Jul  1 06:29:11 2011] ::1:63533: /myscript.html

PHP5.4 alpha1リリースには含まれなかったようですが、ここを見るとPHP5.4系のこれからのリリースには含まれることになりそうです。

参考:
- http://docs.php.net/manual/en/features.commandline.webserver.php - 組み込みウェブサーバに関するマニュアルのページ
- https://wiki.php.net/rfc/builtinwebserver - 組み込みウェブサーバのRFC

終わりに

PHP5.4 alpha1がリリースされたのを受けて、この記事ではPHP5.4 alpha1の筆記すべき新機能をひと通り紹介しました。正式リリース時にはalpha1とはまた違ってくる部分もあると思われますがPHP5.4の概要が大まかに見渡せたと思います。