PHP 5.3の新機能と変更点

第1回PHP 5.3の概要と名前空間、レイトスタティックバインディング

PHP 5.3は2009/6/30リリースされた最新のPHPです。PHPの基本的なバージョン付けのポリシーでは、マイナーバージョンで機能追加や細かい仕様変更が行われ、言語機能を拡張する場合にメジャーバージョンを更新することになっています。

しかし、PHP 5.3はマイナーバージョンアップですが機能追加や細かい機能変更が施されたバージョンではありません。言語機能が拡張されたメジャーバージョンアップと言ってよいような変更が行われたバージョンです。

PHP 5.3とPHP6

PHP 5.3にメジャーバージョンアップと言ってよいような変更が追加された理由はPHP6との互換性維持です。

PHP 6は正式にUnicodeがサポートされるようになります。Unicodeがサポートされる、ということは文字列型に文字列を保存すると文字列がUnicodeとして処理されることを意味します。つまり、日本語の1文字がstrlen関数で1文字として計算されるようになります。

  • PHP 5.x: echo strlen('日本語'); // 9を出力
  • PHP 6.x: echo strlen('日本語'); // 3を出力

マルチバイト文字列の計算が正しく行われるようになるのです。文字列型は文字列とバイナリの両方を保存できるコンテナのようなデータ型になります。アプリケーションは文字列とバイナリを必要に応じて区別しなければならないケースが発生します。文字列型の仕様変更は過去のアプリケーションの動作に大きく影響します。さらに、PHP6へはこれから解説する言語仕様の追加等や国際化をサポートするモジュールの追加が予定されていました。

PHP 5.3はPHP 6への緩やかな移行を可能にするために、PHP 6から文字列型のUnicodeサポートを取り除いたPHPと言えるバージョンとなっています。PHP5.3に対応したアプリケーションはUnicodeサポートにだけ注意すれば、比較的容易に移行できるようになります。

PHP 5.2とPHP 5.3

PHPはマイナーバージョンアップが行われると、古いマイナーバージョンのサポートが打ちきられていました。通常であればPHP 5.3のリリースに伴い、PHP 5.2のサポートは打ちきられるはずです。

しかし、PHP 5.3にはメジャーバージョンアップに匹敵する言語仕様の変更が行われいるので、特別にPHP 5.2へのサポートは継続されることになっています。PHP 5.2のサポート期間は明確にされていませんが、少なくともPHP 6がリリースされるまでは継続されると思われます。

PHP 5.2のサポートの終了は明確なリリースポリシーが決まっていないためはっきりしたことは言えません。しかし、少なくとも1年くらい前にはアナウンスされると考えられます。PHP 6のリリースはまだまだ先になるので、恐らく、最短でも2年はPHP 5.2のサポートは継続すると予想されます。

PHP 5.3かPHP 5.2か?

既にアプリケーションを開発し、運用しているのであれば無理にPHP 5.3にアップグレードする必要はありません。PHP 5.2のメンテナンスが終了する時期までにアップグレードすればよいでしょう。

これから新規にアプリケーションを開発する場合、恐らくPHP 5.3で開発することが望ましいでしょう。PHP 5.3はPHP 5.2よりもかなり長いサポート期間を期待できます。

しかし、良いことだけではありません。今のところ予定されていませんが、PHP 6がリリースされる頃にPHP 5.4がリリースされる可能性もあります。PHP 5.3はPHP 6からUnicodeサポートを除いたバージョンとして開発されてきましたが、既に違いが多くなっているからです(当初は2008年第1四半期のリリースを目標にしていた⁠⁠。このため、今のことろ可能性はあまり高くありませんが、PHP 5.4がリリースされる可能性も残っています。

もしPHP 5.4がリリースされた場合、PHP 5.3系のサポートは打ちきられる可能性が高いです。このため、新規にアプリケーションを開発する場合、PHP 5.2、PHP 5.3のどちらを利用して開発するか迷うことになると思います。PHP 5.4がリリースされた場合、早急にPHP 5.3から5.4にバージョンアップしなければならないかも知れません。したがってバージョンアップ体制に問題がないならPHP 5.3を選択するとよいと思います。

PHP 5.3はPHP 5.2とかなり高い互換性を持っています。PHP 5.2用にアプリケーションを開発しても、PHP 5.3への移行を見据えて開発すれば、アプリケーションにはそれほど多くの変更は必要ないでしょう。

PHP 5.3とフレームワーク

PHP 5.3にはフレームワーク開発者に嬉しい機能が追加されています。PHP 5.3用のフレームワークは新しい機能を使って、より使いやすく実装される、と考えられます。

特にフレームワーク開発者に嬉しい機能は、次に紹介します。

PHP 5.3の目玉機能

PHP 5.3は目玉となる機能は次の言語拡張です。

  • 名前空間のサポート
  • レイトスタティックバインディング
  • クロージャ

目玉機能呼ぶにはあまりふさわしくありませんが、制限付きgoto文もサポートされました。goto文が無いために、ループ処理が不必要に複雑になってしまうケースも在ります。goto文は悪い機能のように言われますが、正しく使えば良い機能です。

今回はこれら3つ(4つ)のうち、名前空間のサポートとレイトスタティックバインディングについて紹介します。

名前空間のサポート

名前空間とは、同じクラス名が定義されるなどでエラー(名前の衝突)が発生することを防ぐ機能です。

名前空間サポートにより追加された機能

  • 名前空間を定義するnamespace文
  • 名前空間のエイリアスを定義するuse文
  • 名前空間名を保存した特殊定数__NAMESPACE__定数

ソフトウェアを開発する場合に、同じクラス名を付けたくなることがよくあります。たとえば、スケジュールを管理するクラスにScheduleと名前を付けると、フレームワークやほかのクラスライブラリのクラス名と衝突する可能性があります。

今まではクラス名や関数名にプレフィックスを付けて名前の衝突を回避してきました。名前空間機能を使用すると名前の衝突を自由に回避できます。

名前空間の使い方

PHPの名前空間はnamespace文で定義されます。名前空間の区切り文字は⁠\⁠(バックスラッシュ)を使用します。最初の⁠\⁠は省略できますが、省略した場合は現在の名前空間の下に新しい名前空間を定義することになります。

デフォルトの名前空間の名前はなく、グローバルがデフォルトとなります。PHPのモジュール関数やクラス、定数が定義されている名前空間がデフォルトのグローバルの名前空間です。

:デフォルトの名前空間の関数にアクセス
<?php
echo \strlen('abc'); // 3を出力
?>
:名前空間の定義
<?php
namespace my\name\space;

const MY_CONST = 1;
?>

名前空間は最初に定義されなければなりません。PHPは埋め込み型言語ですが、<?php ?>の外のテキスト等は、内部的にはecho文で出力しているのと同じことです。従って次のコードはエラーとなります。

:名前空間定義エラー
<html>
<?php
namespace MySpace;
?>
出力
Fatal error: Namespace declaration statement has to be the very first statement in the script in /home/yohgaki/ext/src/php/php-5.3.0/- on line 3

名前空間の宣言は最初の文でなければなりませんが、別の名前空間を定義する場合はコードの途中で宣言しても構いません。

:複数の名前空間
<?php
namespace X;
const A = 1;

namespace Y; 
const A = 2;

echo \X\A.' '.A; // \を付けるとデフォルトの名前空間からアクセスできる
                 // \X\A で最初のconst A=1;にアクセス
                 // Aのみの場合、現在有効な\Y名前空間のA(2つ目のconst A=2)にアクセス
?>
出力
1 2

名前空間の設定はファイル単位です。つまり、名前空間を定義しているファイルからinclude/require文でほかのファイル読み込むと、読み込んだ先のファイルではデフォルトのグローバルの名前空間が利用されます。

:読み込まれるスクリプト(test.php)
<?php
function bar() { echo __FUNCTION__.PHP_EOL; }
?>
:読み込むスクリプト
<?php
namespace MySpace;
function bar() { echo __FUNCTION__.PHP_EOL; }

include 'test.php';

bar();  // MySpace\bar()
\bar(); // test.phpのbar()
?>
出力
MySpace\bar
bar

出力を見ると

  • MySpace\bar

のように、__FUNCTION__定数には名前空間名も記述されていることが分かります。

名前空間だけを知りたい場合もあるでしょう。現在の名前空間名を保存した特殊定数__NAMESPACE__が利用できます。

:__NAMESAPCE__定数
<?php
namespace A;
echo __NAMESPACE__.PHP_EOL;

namespace B;
echo __NAMESPACE__.PHP_EOL;

namespace X;
echo __NAMESPACE__.PHP_EOL;
?>
出力
A
B
X

名前空間名は非常に長くなる場合もあります。\very\long\name\space\name\function()のようにアクセスしなければならないのでは使いづらいです。この問題を解決するために名前空間名を別の名前(エイリアス名)で利用する⁠use⁠文が利用できます。

:use文の使い方
<?php
namespace very\long\name\space\name;
function foo() { echo __FUNCTION__.PHP_EOL; }

namespace short;
function foo() { echo __FUNCTION__.PHP_EOL; }

use very\long\name\space\name as X; // エイリアスを定義

X\foo();
foo();
?>
出力
very\long\name\space\name\foo
short\foo

名前空間のエイリアス名でアクセスしても、名前空間名はエイリアス名ではなく、元の名前が利用されていることが分かります。

名前空間を利用するとフレームワーク/ライブラリとアプリケーションの両方で簡潔で分かりやすい関数/クラス名などを利用しつつ、名前の衝突を心配しなくすむようになります。名前空間のサポートは、フレームワークやクラスライブラリ開発者のみでなくアプリケーション開発者にとっても嬉しい機能です。

レイトスタティックバインティング

レイトスタティックバインディングは継承により今までのPHPのクラスの静的バインディングの問題を解消する機能で、クラスライブラリを作成する場合などに役立つ機能です。この機能により現在アクセスしているオブジェクトのクラス名が取得できるようになります。

追加された機能

  • staticキーワードをメソッド、プロパティに利用可能となった(スタティックコールの制御)

  • get_called_class関数が追加され、呼ばれたクラス名が判別可能となった

  • スタティックコールの場合に呼び出される、特殊メソッドの__getStatic, __setStatic, __callStaticが追加された

以前のPHPはこの機能が無かったため、ORマッパー(ActiveRecordパターン)を実装する際に、アクセスしているオブジェクトのクラス名がオブジェクトから取得できませんでした。このため、綺麗な実装ができない状況でした。PHPに良いORマッパーが無かった理由はこれが原因の一つでしたが、PHP 5.3の登場で改善すると思われます[1]⁠。

レイトスタティックバインディングの使い方

レイトスタティックバインディングの効果は次のコードを理解できます。

:PHP 5.2までの制限
<?php
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        self::who();
    }
}

class B extends A {
    public static function who() {
         echo __CLASS__;
    }
}

B::test();
?>
出力
A

Aクラスのメソッドにアクセスしたい場合もあるでしょう。しかし、Bクラスのメソッドに多くの場合はアクセスしたいでしょう。

ORマッパーのようなクラスライブラリを実装する場合、B::test()を実行した時に、Bクラスとしてアクセスしているので、testメソッド内のself::who()でBクラスのwhoメソッドにアクセスしたいのですが、出来ませんでした。

PHP 5.3から新しくstaticキーワードがクラスメソッドのアクセスに利用できるようになりました。Aクラスのtestメソッドのself::who()をstatic::who()に書き換えることにより、望み通りの結果が得られるようになります。

:レイトスタティックバインディング
<?php
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        static::who(); // レイトスタティックバインディング
    }
}

class B extends A {
    public static function who() {
         echo __CLASS__;
    }
}

B::test();
?>
出力
B

PHP 5.2までは

static::who(); // レイトスタティックバインディング

self::who(); // スタティックバインディング

と書くしかありませんでした。これではBクラスから呼び出しても、出力は

A

になってしまいます。ベースとなるTableクラスを多数の実在するテーブルクラスで継承して利用る場合、親クラスで自分のクラス名が取得できない仕様は非常に厄介な問題でした。

この例では動作が理解できても、なぜ便利であるかわかりづらいです。Singletonパターンでの使用例を紹介します。

:レイトスタティックバインディングを利用したシングルトン
<?php
// 通常のシングルトン。継承不可
class Singleton {
  private static $singleton;

  final public static function getInstance() {
    if (Singleton::$singleton === null) {
      $className = __CLASS__;
          Singleton::$singleton = new $className();
    }
    return Singleton::$singleton;
  }
}

// 継承可能なシングルトン
class ExtendableSingleton {
  protected static $singleton = array();
  final public static function getInstance() {
    $className = get_called_class(); // レイトスタティックバインディングのクラス名   
    if (!isset(self::$singleton[$className])) {
      self::$singleton[$className] = new $className();
    }
    return self::$singleton[$className];
  }

  final protected function __clone() { throw new Exception('Should not clone object'); }
}

class MySingleton extends Singleton {}
class AnotherSingleton extends Singleton {}

$o1 = MySingleton::getInstance();
$o2 = AnotherSingleton::getInstance();
if ($o1 === $o2) {
  echo '$o1 === $o2, want diffrent one...', PHP_EOL;
} else {
  echo 'expecting this, but it\'s not', PHP_EOL;
}

class StaticMySingleton extends ExtendableSingleton {}
class StaticAnotherSingleton extends ExtendableSingleton {}

$o1 = StaticMySingleton::getInstance();
$o2 = StaticAnotherSingleton::getInstance();
$o3 = StaticMySingleton::getInstance();

if ($o1 === $o2) {
  echo '$o1 === $o2, something wrong', PHP_EOL;
} else {
  echo 'should be differ', PHP_EOL;
}

if ($o1 === $o3) {
  echo '$o1 === $o3', PHP_EOL;
} else {
  echo 'something wrong', PHP_EOL;
}
?>
出力
$o1 === $o2, want diffrent one...
should be differ
$o1 === $o3

期待通りの出力が得られています。

レイトスタティックバインディングにより、継承可能なシングルトンパターンが実装できるようになりました。ORマッパーを作成する場合、クラス名で分かっているにも関わらずテーブル名をプロパティやパラメータとして渡さずに済むようになります[2]⁠。

レイトスタティックバインディングはクラスライブラリを作成する開発者にとっては、名前空間よりも重要な機能といえるでしょう。


次回は残りの目玉機能のひとつである「クロージャ」の紹介と、goto文の紹介を行います。

参考文献
PHP マニュアル
PHP 5.2 to 5.3マイグレーションガイド
PHP 5.3ソース

おすすめ記事

記事・ニュース一覧