プログラミングの最近のブログ記事

また PHP 5.3 のお話です。ReflectionFunction::getClosure() 及び ReflectionMethod::getClosure() が追加されてますね。なんだろうこれ?

<?php

$rf = new ReflectionFunction('time');
$time = $rf->getClosure();
$rm = new ReflectionMethod('ArrayObject', 'count');
$count = $rm->getClosure(new ArrayObject(array(1, 2, 3)));

echo 'time(): ', $time(), PHP_EOL;
echo 'ArrayObject::count(): ', $count(), PHP_EOL;
time(): 1219387333
ArrayObject::count(): 3

遅延静的束縛と関連のあるお話です。クラスメソッドを呼び出す際に parent, self, static キーワードを使わず、クラス名を指定してやると呼び出し元がそのクラスになります。

<?php

class A
{
    public static function method()
    {
        echo __METHOD__, ' called by ', get_called_class(), PHP_EOL;
    }
}

class B extends A
{
    public static function method()
    {
        echo __METHOD__, ' called by ', get_called_class(), PHP_EOL;
        return A::method(); // 遅延静的束縛のための情報は引き継がれない
    }
}

class C extends B
{
    public static function method()
    {
        echo __METHOD__, ' called by ', get_called_class(), PHP_EOL;
        return parent::method();
    }
}

C::method();
C::method called by C
B::method called by C
A::method called by A

forward_static_call() 及び forward_static_call_array() は遅延静的束縛の情報を残したままメソッドを呼び出すために(も?)用意されました。

<?php

class A
{
    public static function method()
    {
        echo __METHOD__, ' called by ', get_called_class(), PHP_EOL;
    }
}

class B extends A
{
    public static function method()
    {
        echo __METHOD__, ' called by ', get_called_class(), PHP_EOL;
        return  forward_static_call(array('A', 'method')); // 遅延静的束縛のための情報を引き継ぐ
    }
}

class C extends B
{
    public static function method()
    {
        echo __METHOD__, ' called by ', get_called_class(), PHP_EOL;
        return parent::method();
    }
}

C::method();
C::method called by C
B::method called by C
A::method called by C

継承関係にないクラスを指定しても引き継がれません。また親から子を指定しても引き継がれません。その他の制限として、クラススコープ以外で呼び出すと E_ERROR が発生します。後は call_user_func() 及び call_user_func_array() と使い方は同じのようです。

どんな場面で使うのかは、…どうなんだろう?

__CLASS__ 定数の遅延静的束縛版といったところでしょうか。

<?php

class A
{
    public static function whose()
    {
        return get_called_class();
    }
}

class B extends A
{
    public static function whose()
    {
        return parent::whose();
    }
}

class C extends A
{
    public static function whose()
    {
        return A::whose();
    }
}

echo 'A: ', A::whose(), PHP_EOL;
echo 'B: ', B::whose(), PHP_EOL;
echo 'C: ', C::whose(), PHP_EOL;
A: A
B: B
C: A

ActiveRecord のようにクラスメソッドがバカスカ生えるクラスに使えるかもですね。でもここでは継承される事を想定したシングルトンの例を上げます。そんなのありか!

<?php

class Hoge
{
    private static $instances = array();
    
    protected function __construct()
    {
        if (array_key_exists(get_called_class(), self::$instances))
        {
            throw new RuntimeException('もう作っちゃらめ!');
        }
    }
    
    final public static function getInstance()
    {
        $callee = get_called_class();
        if (!array_key_exists($callee, self::$instances))
        {
            self::$instances[$callee] = new static();
        }
        
        return self::$instances[$callee];
    }
    
    public function __clone()
    {
        throw new RuntimeException('クローン作っちゃらめ!');
    }
    
    public function __toString()
    {
        return get_class($this) . '(' . spl_object_hash($this) . ')';
    }
}

class Fuga extends Hoge
{
}

class Piyo extends Fuga
{
}

echo Hoge::getInstance(), PHP_EOL;
echo Fuga::getInstance(), PHP_EOL;
echo Piyo::getInstance(), PHP_EOL;
echo Hoge::getInstance(), PHP_EOL;
echo Fuga::getInstance(), PHP_EOL;
echo Piyo::getInstance(), PHP_EOL;
Hoge(ea3b5927dbfffe7c0456ff51c413a384)
Fuga(0e67c95a7d082c779dc82db7e8554d9c)
Piyo(3b3af9abb7c1ee816230684be1ec11dc)
Hoge(ea3b5927dbfffe7c0456ff51c413a384)
Fuga(0e67c95a7d082c779dc82db7e8554d9c)
Piyo(3b3af9abb7c1ee816230684be1ec11dc)

5.3 から HTTP ストリームのコンテキストに ignore_errors オプションが利用できるようになるそうです。

01 Aug 2008, PHP 5.3.0 Alpha 1
- Improved streams:
  . Added "ignore_errors" option to http fopen wrapper. (David Zulke, Sara)

今までは fopen() や file_get_contents() の URL に HTTP ストリームを指定した場合、HTTP レスポンスのステータスコードが(最終的に) 2xx でなければレスポンスボディを取得できませんでした。ところが HTTP の仕様としてレスポンスボディを設定できるステータスコードは 2xx だけではありません。PHP 5.3 からはコンテキストに ignore_errors = true を設定すると、エラーとなっていたステータスコードを無視するようになります。次のコードは HTTP サーバが 404 として用意しているレスポンスボディを取得します。

$context = stream_context_create(array('http' => array('ignore_errors' => true)));
echo file_get_contents('http://www.example.com/not-exists-url', false, $context);

また、REST スタイルな Web API にアクセスして取得に失敗した時に、それが認証が必要だったせいなのか、サービスが存在していなかったのか、リクエストの内容が間違っていたのか、さまざまな状態を判別するのに使えるステータスコードを拾えるようになります。

例として、ハイパーテキスト・コーヒーポット制御プロトコルを実装した HTCPCP サーバに HTTP ストリームを使ってアクセスするコードを上げます。

<php
// なにもしない、なにも足さない、HTCPCP サーバ

switch (strtoupper($_SERVER['REQUEST_METHOD']))
{
    case 'POST':
    case 'BREW':
    case 'WHEN':
        header('HTTP/1.1 418 I\'m a teapot', true, 418);
        header('Content-Type: text/plain', true);
        echo '私は急須だ。';
        break;
    
    default:
        header('HTTP/1.1 501 Not Implemented', true, 501);
        echo '要求されたメソッドは未実装です。';
        break;
}
<php
// HTCPCP サーバに対して BREW メソッドを発行するクライアントプログラム

$coffeepot = 'http://www.example.com/coffeepot.php';
$options = array(
    'http' => array(
        'method' => 'BREW',
        'header' => array('Content-Type: message/coffeepot'),
        'content' => 'start',
        'ignore_errors' => true,
    ),
);
$stream = @fopen($coffeepot, 'rb', false, stream_context_create($options));
$meta = stream_get_meta_data($stream);
preg_match('|\\AHTTP/\\d\\.\\d +(?<status_code>\\d{3}) +(?<reason_phrase>.*)\\E|', $meta['wrapper_data'][0], $captures);
echo $captures['status_code'], '/', $captures['reason_phrase'], PHP_EOL;
fpassthru($stream);
fclose($stream);

結果

418/I'm a teapot
私は急須だ。

怒られちゃいましたね。こんな具合に、Web API をちょっと叩いてみたい時に PEAR::HTTP_Client や Zend_Http_Client 等の外部ライブラリに頼らずともできるようになるのはありがたいです。

closure の記事を書いた後に気付いたんですが、クラスに __invoke() メソッドが定義されてると関数のように呼び出せるんですね。こいつぁ使える!

<?php

class A
{
    public function __invoke()
    {
        echo 'ひえひえ細胞';
    }
}

$a = new A();
$a();

http://d.hatena.ne.jp/masugata/20080709#p1 いやっほおおお!日をまたいでいてすぐ眠りたいのですが、が… php-6.0-dev がベンチに座ってチャックを下ろす姿を見てホイホイ落としちゃったのだ。

<?php

$hikonyann = function ($multiplier)
{
    return str_repeat(’ひこにゃん’, $multiplier);
};

echo $hikonyann();
ひこにゃんひこにゃんひこにゃん

うんうんいいよー。レキシカル変数を参照するには?use () で宣言するのか。

$name = 'ひこにゃん';
$hikonyann = function ($multiplier) use ($name)
{
    return str_repeat($name, $multiplier);
};

echo $hikonyann(3) . PHP_EOL;
ひこにゃんひこにゃんひこにゃん

うんうんいいよー。このレキシカル変数は値渡し?参照渡し?

<?php

$name = 'ひこにゃん';
$hikonyann = function ($multiplier) use ($name)
{
    return str_repeat($name, $multiplier);
};

echo $hikonyann(3) . PHP_EOL;
$name = 'しまさこにゃん';
echo $hikonyann(3) . PHP_EOL;
ひこにゃんひこにゃんひこにゃん
ひこにゃんひこにゃんひこにゃん

値渡しですね。& で参照渡しになる、と。

$name = 'ひこにゃん';
$hikonyann = function ($multiplier) use (&$name)
{
    return str_repeat($name, $multiplier);
};

echo $hikonyann(3) . PHP_EOL;
$name = 'しまさこにゃん';
echo $hikonyann(3) . PHP_EOL;
ひこにゃんひこにゃんひこにゃん
しまさこにゃんしまさこにゃんしまさこにゃん

クラスと組み合わせると?

<?php

class Mascot
{
    private $name;
    
    public function __construct($name)
    {
        $this->name = $name;
    }
    
    public function generate()
    {
        return function ($multiplier)
        {
            return str_repeat($this->name, $multiplier);
        };
    }
}

$hikonyann = new Mascot('ひこにゃん');
$generator = $hikonyann->generate();
echo $generator(3) . PHP_EOL;
ひこにゃんひこにゃんひこにゃん

$this は明示的に指定しなくていいんだね。他は、どんな呼び出し方があるのかな?

<?php

$lamda = function () { debug_print_backtrace(); };

$lamda();
$lamda->__invoke();
call_user_func($lamda);
#0  lambda() called at [*:\********\closure.php:5]
#0  lambda()
#1  Closure->__invoke() called at [*:\********\closure.php:6]
#0  lambda()
#1  call_user_func(Closure Object ()) called at [*:\********\closure.php:7]

あら?オブジェクト?

<?php echo get_class(function () {});
Closure

まぁ、Closure クラスですって!ReflectionObject の出番よ。

<?php Reflection::export(new ReflectionObject(function () {}));
Object of class [ <internal> final class Closure ] {

  - Constants [0] {
  }

  - Static properties [0] {
  }

  - Static methods [0] {
  }

  - Properties [0] {
  }

  - Dynamic properties [0] {
  }

  - Methods [0] {
  }
}

まっさら。__invoke() はマジックメソッド扱いなのかしら?

時間が出来たら詳しく調べます…。

hikojiru.pngPHP ではクラスの静的メンバの初期化に定数しか利用できません。例えばオブジェクトをセットしたい場合、どこかのタイミングで初期化してやる必要があります。このタイミングが問題になる例をあげてみます。Hoge は自分のコンストラクタが呼び出された回数を Hoge::$profile->construnction から取得できるクラスです。
class Hoge
{
    public static $profile;

    public function __construct()
    {
        if (self::$profile === null)
        {
            self::$profile = (object)array('construction' => 0);
        }
        
        self::$profile->construction++;
    }
}

// まだ参照しちゃ、らめ!
// echo Hoge::$profile->construction; // Notice: Trying to get property of non-object

// これはよい。
new Hoge();
echo Hoge::$profile->construction; // 1

一度もインスタンスを作成していない場合、Hoge::$profile は NULL のままです。クラス定義の後に続けて Hoge::$profile = (object)array('construction' => 0); としてやれば回避できますが、フローがクラス定義の外へ追い出されてしまいます。そこで、Java の静的初期化子や C# の静的コンストラクタを模倣する手段を考えました。PHP は存在しないクラスが要求された場合にオートローディングが働くよう仕組まれています。これを利用して、オートローダが呼ばれたタイミングで初期化用の静的メソッドをコールすればいいのではと思いました。

実装例を上げます。静的初期化子をサポートするクラスのマーカー的な interface と、それに対応したクラスローダを定義します。

interface IStaticInitializable
{
    static function initializeMember();
}

class ClassLoader
{
    protected $baseClassPath;
    
    public function __construct($baseClassPath)
    {
        $this->baseClassPath = $baseClassPath . DIRECTORY_SEPARATOR;
    }
    
    public function loadClass($name)
    {
        $patterns = array(DIRECTORY_SEPARATOR, ’.’, '_', '::');
        $replacements = array('', '', DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR);
        $filePath = $this->baseClassPath . str_replace($patterns, $replacements, $name) . '.php';
        if (file_exists($filePath))
        {
            require_once $filePath;
        }
        
        if (class_exists($name, false))
        {
            $class = new ReflectionClass($name);
            if ($class->implementsInterface('IStaticInitializable'))
            {
                eval("$name::initializeMember();");
            }
        }
    }
}

ここから IStaticInitializable の実装例を上げます。静的変数(クラスのそれではなく、スコープ修飾子。紛らわしい…)で二度初期化しないよう定義しています。ファイル名はみんな大好き Hikonyann.php です。

class Hikonyann implements IStaticInitializable
{
    public static $stacks;
    public static $eatings;
    public static $initializedBy;
    public static $initializedAt;
    
    public static function initializeMember()
    {
        static $initialized = false;
        
        if (!$initialized)
        {
            self::$stacks = mt_rand(0, 100);
            self::$eatings = mt_rand(0, 100);
            ob_start(); debug_print_backtrace(); $backtrace = ob_get_clean();
            self::$initializedBy = $backtrace;
            self::$initializedAt = time();
            
            $initialized = true;
        }
    }
}

クラスの利用側。ファイルはクラスと同じディレクトリにあると仮定します。

<?php
$loader = new ClassLoader(dirname(__FILE__));
spl_autoload_register(array($loader, 'loadClass'));

printf(
    'ひこにゃんは %2$d 回通路に挟まりました。%1$s'
    . 'ひこにゃんは %3$d 個のだんごを食べました。%1$s'
    . '状態が観測されたのは %4$s です。%1$s'
    . '観測者は次の通りです。%1$s%5$s',
    PHP_EOL,
    Hikonyann::$stacks,
    Hikonyann::$eatings,
    date(DATE_RFC3339, Hikonyann::$initializedAt),
    Hikonyann::$initializedBy
    );

そして出力例。

ひこにゃんは 85 回通路に挟まりました。
ひこにゃんは 27 個のだんごを食べました。
状態が観測されたのは 2008-05-01T12:32:39+09:00 です。
観測者は次の通りです。
#0  Hikonyann::initializeMember() called at [#:\###########\sample.php(32) : eval()'d code:1]
#1  eval() called at [#:\###########\sample.php:32]
#2  ClassLoader->loadClass(Hikonyann)
#3  spl_autoload_call(Hikonyann) called at [#:\###########\sample.php:47]

これで初期化を意識せず済むようになりました。

型の相互変換 - 型キャストより。

(binary) によるキャストや b プレフィックスのサポートは、PHP 5.2.1 で追加されました。
<?php
$binary = (binary)$string;
$binary = b"binary string";
?>

Unicode 導入に向けて、そろそろ意識して使い始めても良さそうです。…べつに PHP 6 なんか待ち遠しくないんだからね!

文字列で思い出したけど、内部表現で使っているサイズを取得できるようになると嬉しいよね。$size = strlen((binary)$string); ではなく、スカラー型全てに対応するものをぜひ。mbstring.func_overload & 2 な環境への対策、例えば $size = mb_strlen($value, 'ASCII'); を強いられてる人を少なくとも一人助けられます!…他にいるか分かんないけどw

Wikipedia の Hello worldプログラムの一覧 より、PHP による記述例を見て思いました。

<?php
echo 'Hello, world!';
exit;
?>

大人しすぎです。もっと自分を出して!

Hello, world!<?php exit; ?>

もっとラディカルに!

Hello, world!
<%=  %> が使いたい。非推奨になっている asp_tags を On にしないと使えない。使いたいけど使えない。使ったら PHP コード込みで整形 XML として扱えなくなる。使いたくないけど使いたい。うんこがついてるのについてない AA のようなジレンマと戦う毎日です。

<%= %> と等価な Processing Instraction があれば解決しますよね。でもないなら作るしかない。とりあえず XSL Transform を利用して <?h expression?> を <?php echo htmlspecialchars(expression); ?> に変換する所まではやりました。ただ式以外の制御構文を入れようと思うと、最後に評価された式を取得する方法が必要なのですが見付からず、最悪構文解析してやるしかないのかなぁと思うと、気分はもうどうにでもな~れ。

途中まで自分がんばった!サンプル一式はこちらです。hikoten.zip(要 PHP 5.2 --with-xsl)

このアーカイブについて

このページには、過去に書かれたブログ記事のうちプログラミングカテゴリに属しているものが含まれています。

前のカテゴリはゲームです。

次のカテゴリは作品です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 4.12