親方、空から覚え書きが!でタグ「PHP」が付けられているもの

また 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)

foreach によるイテレータのキーって数値(int型)か文字列しか使えないんですね。ショック…

// .NET Framework の Dictionary<TKey, TValue> や Java の IdentityHashMap<K, V> みたいな振る舞いをします
$dic = new Dictionary();
$dic[new stdClass()] = 1;
$dic[new stdClass()] = 2;
$dic[new stdClass()] = 3;

// Illegal type returned from KeyValuePairIterator::key() in ...
foreach ($dic as $key => $value) {}

べ、べつに foreach なんてなくたって平気なんだからね!

for ($i = $dic->getIterator(); $key = $i->key(), $value = $i->current(), $i->valid(); $i->next()) {}
図でまとめやり直し。クリックで原寸表示します(縦横 3000px)。基の FreeMind ファイルはこちらです。zf103.zipzf103.png

Zend Framework 1.0.3 の Zend_Controller について少しまとめました。キレイなソースで読みやすかったぁ。

XML Inclusions (XInclude)を使ったテンプレートのレイアウト方法はどうでしょうかというお話。

PHP でテンプレートと言えば生の PHP でぺたっと書くか、Smarty やらといったテンプレートエンジンを利用するのが一般的だと思います。このテンプレート、デザイナさんから見ると嫌な存在ではないでしょうか?テンプレート毎に文法を覚えたり、対応したエディタを用意したり。せっかく覚えるならいろんな言語で通用する技術がいいですよね。XSLT もいいですが、ちょっとひねくれて XInclude を使っちゃいましょう。

ここでは、PHP で XInclude に触れてみたいと思います。まず、デザイナさんに一枚の XHTML を渡します。

Template.xhtml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xi="http://www.w3.org/2001/XInclude" xml:lang="ja-JPN">
    <head>
        <!-- この部分にヘッダを書いて下さいネ。 -->
        <!-- <link rel="stylesheet" type="text/css" href="" /> -->
        <!-- <script type="text/javascript" src=""></script> -->

        <xi:include href="Page.xhtml" xpointer="xmlns(xh=http://www.w3.org/1999/xhtml) xpointer(/xh:html/xh:head/*)">
            <xi:fallback>
                <!-- この部分は実行時にページ固有のヘッダに置き換えられます。 -->
                <title>デザイン中です</title>
            </xi:fallback>
        </xi:include>
    </head>
    
    <body>
        <xi:include href="Page.xhtml" xpointer="xmlns(xh=http://www.w3.org/1999/xhtml) xpointer(/xh:html/xh:body/*)">
            <xi:fallback>
                <!-- この部分は実行時にページ固有の本本に置き換えられます。 -->
                <!-- 本本はだいたい次の XHTML となります(CSS 確認用に使って下さいネ)。 -->
                <h1>本文</h1>
                <p>どうたらこうたら</p>
                <h2>概要</h2>
                <p>どんどこどんどん</p>
                <h2>以下略</h2>
                <p>...</p>
            </xi:fallback>
        </xi:include>
    </body>
</html>

xxxnyann-seiza.png

定数のような振る舞いをする array やオブジェクトが必要になるケースとはどのような場面でしょうか。 例えば、あるキャラクタの性格を array へ定義しておきそれを参照するケースにて、第三者によって後付設定を付加されたり書き換えられてしまい、 キャラクタが作者の意図しない方向へ進んでしまう可能性がある場合です。

<?php

/**
 * プロフィールを定義します。ここ以外で変更しちゃダメ!
 * 
 * @type array
 */
$profile = array(
        'image' => 'xxxnyann-zukai.png',
        'nickname' => 'モチ',
        'favorite' => 'お団子',
        'hobby' => '散歩',
        'skill' => 'じゃんけん',
        'option' => '井伊直政当世具足',
    );

$profile['favorite'] = 'お魚、お肉'; // な、何するだァー!
$profile['option'] = 'しっぽ'; // な、何するだァー!

?>

<table summary="ひこにゃんのプロフィールです。">
    <caption>ズバリ!これがひこにゃんだ!</caption>
    <thead>
        <tr>
            <th>いめーじ</th>
            <th>ふぃーちゃー</th>
            <th>すぺっく</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td rowspan="5"><img src="<?php echo htmlspecialchars($profile['image']); ?>" width="120" height="160" /></td>
            <td>ひこにゃんマウス</td>
            <td><?php echo htmlspecialchars($profile['favorite']); ?>をおもむろにむさぼるぞ!</td>
        </tr>
        <tr>
            <td>ひこにゃんハンド</td>
            <td><?php echo htmlspecialchars($profile['skill']); ?>でどんな逆境も順境に!</td>
        </tr>
        <tr>
            <td>ひこにゃんレッグ</td>
            <td><?php echo htmlspecialchars($profile['hobby']); ?>で鍛えた足腰で傾斜角60度の坂でもぐいぐいのぼる!</td>
        </tr>
        <tr>
            <td>ひこにゃんオプション</td>
            <td><?php echo htmlspecialchars($profile['option']); ?>がとても魅力的だぞ!</td>
        </tr>
    </tbody>
</table>
profile.png

これを防ぐ手立てとして、要素を変更できない array が欲しくなります。残念ながら PHP には読み取り専用の配列は用意されていないので、自作する必要があります。

xxnyann.png2007年も残すところ一ヶ月を切り、PHP 4 の開発・メンテナンスと、築城400年祭と、商標使用期限の終了が迫っています。既に PHP 5 への移行は済んだと思われますが、この機会にクラスを定義したファイルの読み込み方法について見直してみましょう。

クラスを定義したファイルを1ファイル1クラスで保存している場合、PHP 4 から使用できる手段として、コードの先頭で require_once() を使用して読み込んでいました。使用するクラスの数だけ列挙が必要なため、オブジェクト同士の協調を図るコードを書く場合など、非常に多くの手間が掛かります。
例えば…

<?php
/**
 * クリスマス・イヴ未明からクリスマス終日にかけてひこにゃんが一緒に居てくれます。
 */

// require_once() からクラス定義ファイルを相対パスで読めるようにして。
set_include_path(get_include_path() . PATH_SEPARATOR . '/path/to/my/');

require_once 'My/DateTime.php';
require_once 'My/TimeSpan.php';
require_once 'My/Schedule.php';
require_once 'My/HikonyannPool.php';
require_once 'My/NakanohitoNadoInaiException.php';
require_once 'My/PoolOverflowException.php';
require_once 'My/TrademarkExpiredException.php';


// 私「2007-12-24T00:00:00Z から」
$when = new My_DateTime(2007, 12, 23, 15, 0, 0);
// 私「二日間ほど」
$howlong = new My_TimeSpan(2, 0, 0, 0);
// 私「借りたいの!」
$schedule = new My_Schedule($when, $howlong);

// 私「ダメ?」
try
{
    $hikonyann = HikonyannPool::borrow($schedule); 
    echo $hikonyann->letEnjoy();
    
    /****
            /i     iヽ
  ワーワー     ((/□\))
           ソ_∠ニ二ス  キャーキャー
      ヽ○ノ ∠シ,, ・ェ・ )ゝ
        |○ノ  (   ) ヾ○ノ
        へ|  ○ ∪∪    |
         /> /|ヽ      lヽ
           ∧ 
     ****/
}
// 市「ダメ(中の人などいない的に)」
catch (My_NakanohitoNadoInaiException $e) 
{
    echo 'ひこにゃんもクリスマスを楽しんでいます。';
}
// 市「ダメ(着ぐるみ数が限界的に)」
catch (My_PoolOverflowException $e)
{
    echo 'ひこにゃんは忙しいです。';
}
// 市「ダメ(登録商標的に)」
catch (My_TrademarkExpiredException $e)
{
    echo '尻尾をつけたのが不味かったか!?';
}

?>

この膨大な require_once にうんざりした PHP 5 は、解決手段としてオブジェクトのオートローディングを用意しました。未定義のクラスが使用された時点で、__autoload() がユーザー定義されていれば引数にクラス名を与えて呼び出され、クラスを定義する機会が与えられます。
例えば…

問題

interface 宣言を含んだ PHP スクリプト ファイルを、PDT の PHP エディタで開いた時に "Error Opening the editor (Time of error: 日付) Reason: java.lang.NullPointerException" というエラー メッセージが出て開けない。

環境

Windows XP SP 2、Eclipse 3.3.0 + PDT 1.0 M2 プラグイン

対処

Eclipse のメニューから [Window] > [Preferences...] > [PHP] > [Editor] > [Code Folding] を開き、[Enable folding.] のチェックを外す。

原因

ヌルポが発生していた箇所は org.eclipse.php.internal.ui.folding.projection.ProjectionModelNodeAdapterPHP.createPosition(Unknown Source)。org.eclipse.php.internal.ui.folding はコード折りたたみに関係したパッケージかな。折りたたみは便利だけど、カット&ペーストでかなりの確率でハングアップする 0.7 を考えたらぜんぜん困らない、十分使えます。これなら次のプロジェクトから導入できるかも…。9月には 1.0 安定板がリリースされるということなのでとても期待。

PHP 6.0 開発版を試す

| | コメント(6)

PHP Snapshots の PHP 6.0 開発版を試してみました。

Apache の設定とか

PHP 5.x の時と同じ手順で動きました。今回使った設定

    LoadFile "C:\PHP\6.0.0-dev-200707112230\php6ts.dll"
    LoadModule php6_module "C:\PHP\6.0.0-dev-200707112230\php6apache2_2_filter.dll"
    AddInputFilter PHP php
    AddOutputFilter PHP php
    PHPINIDir "C:\PHP\6.0.0-dev-200707112230"

php.ini

register_globals や magic_quotes、safe_mode 辺りがばっさり削られてました。その他、Unicode のディレクティブが追加されています。

Unicode への対応が強化

まず設定ディレクティブから

ドキュメントが(見付から)ない部分は憶測で書いています。

unicode.semantics
unicode 機能が使用可能かどうか。設定できる箇所は、PHP_INI_PERDIR なので php.ini, .htaccess または httpd.conf です。(php.ini-recommemded で off ってのはどうかしら?どうかしら?)
unicode.runtime_encoding
実行エンジンが内部で取り扱う文字符号化方式。詳しくはこちらを参照
unicode.script_encoding
PHP スクリプト ファイルの文字符号化方式。御法度だった Shift_JIS での記述も可能に。
unicode.output_encoding
出力時の文字符号化方式。mbstring 関数の mbstring.http_output に相当する機能?
unicode.from_error_mode
不正な文字をどうするか。U_INVALID_SUBSTITUTE は unicode.from_error_subst_char で指定したコード ポイントを持つ文字に置き換え。
unicode.from_error_subst_char
不from_error_mode = U_INVALID_SUBSTITUTE 時に置き換える文字。Unicode のコード ポイントで指定?

指定できるエンコーディングは、Unicode 関数が利用しているライブラリ ICU がサポートしているものをそのまま使うようです。

使ってみる

普通に使う

// 文字列リテラルは Unicode 扱い
$string = "文字列リテラルは Unicode 扱い";
var_dump($string);
# unicode(19) "文字列リテラルは Unicode 扱い"

// \u により、Unicode 文字をコード ポイント指定で埋め込める
$string = "\u307a\u3061\u3071\u30fc";
var_dump($string);
# unicode(4) "ぺちぱー"

// カウントばっちり
var_dump(strlen($string));
# int(19)

// 切り取りもばっちり
var_dump(substr($string, 9, 7));
# unicode(7) "Unicode"
バイナリから Unicode へのキャスト

string 型から unicode 型へは unicode.runtime_encoding の文字符号化方式に基づいて変換しているようです。

例えば、unicode.runtime_encoding = Shift_JIS、その他を UTF-8 な環境化では…

;;;;;;;;;;;;;;;;;;;;
; Unicode settings ;
;;;;;;;;;;;;;;;;;;;;

unicode.semantics = on
unicode.runtime_encoding = Shift_JIS
unicode.script_encoding = UTF-8
unicode.output_encoding = UTF-8
unicode.from_error_mode = U_INVALID_SUBSTITUTE
unicode.from_error_subst_char = 3f
# Shift_JIS で符号化された文字列「あいう」
$binary = pack('C*', 0x82, 0xa0, 0x82, 0xa2, 0x82, 0xa4);

// バイナリを扱う関数の戻り値は string のままらしい
var_dump($binary);
# string(6) "??????"

// unicode 型へキャスト。
$unicode = (unicode)$binary;
var_dump($unicode);
# unicode(3) "あいう"
そんな訳で

PHP 6.0 で文字列を取り扱う時は、従来の string 型と Unicode 専用の unicode 型で分けて考えるようになりました。

正規表現関数とパターン修飾子 u

正規表現関数 にて、UTF-8 での処理を有効にするパターン修飾子 u を使うと、Unicode のカテゴリが使用できるというお話。

文章的に意味がなさそうな文字列を判別しちゃう(サイコロを振るよりはマシ程度)

$strings = array(
        '0123456789',
        'Latin',
        'ひらがな',
        'カタカナ',
        '漢字',
        '한글',
        'αβγ',
        '+-*/=<>', 
        '「」()『』',
        pack('C*', 0xe2, 0x80, 0x8b), // U+200B (Zero Width Space) 
    );

foreach ($strings as $string)
{
    $hasLetter = preg_match('/\\p{L}/u', $string);
    printf('"%s" has letter? %s' . "\r\n", $string, $hasLetter ? 'Yes' : 'No');
}



// "0123456789" has letter? No
// "Latin" has letter? Yes
// "ひらがな" has letter? Yes
// "カタカナ" has letter? Yes
// "漢字" has letter? Yes
// "한글" has letter? Yes
// "αβγ" has letter? Yes
// "+-*/=<>" has letter? No
// "「」()『』" has letter? No
// "​" has letter? No
 

絵文字の変換

// U+E000 ~ U+E8FF までのブロックは Private Use Area

function pictographToText($capture)
{
    $table = array(
            pack('C*', 0xee, 0x98, 0xbe) => '晴れ', // U+E63E (i-mode 基本絵文字 "晴れ")
            pack('C*', 0xee, 0x98, 0xbf) => '曇り', // U+E63F (i-mode 基本絵文字 "曇り")
            pack('C*', 0xee, 0x99, 0x80) => '雨',   // U+E640 (i-mode 基本絵文字 "雨")
        );

    return isset($table[$capture[0]]) ? $table[$capture[0]] : '?';
}


$string = '今日の天気は '
    . pack('C*', 0xee, 0x98, 0xbe)
    . ' です。'
    . '明日の天気は '
    . pack('C*', 0xee, 0x99, 0x80)
    . ' かも?';

echo preg_replace_callback('/\\p{Co}/u', 'pictographToText', $string);


// 今日の天気は 晴れ です。明日の天気は 雨 かも?

参考

PHP のバージョン毎のチェックを楽に

Apache HTTP Server の Windows 版には、Apache Service Monitor という、タスクバーから Apache を制御できる便利なユーティリティが備わっています。このユーティリティは、Windows サービスに登録された Apache のサービスを制御するものです。これを利用して、バージョンの異なる PHP を切り替えて実行できる環境を整えてみるお話です。

Apache Service Monitor のウィンドウ

手順

  1. PHP のインストール
  2. PHP の設定
  3. Apache 2.2 のインストール
  4. Apache 2.2 の設定
  5. Windows サービスへの登録
  6. Windows サービスの補足
PHP のインストール

PHPPHP: Downloads から、適当なバージョンをダウンロードします。Windows Binaries 版を使うと、手抜きができて最高です。自分は次の図のように C:\PHP へバージョン別にぶち込んでいます。

PHP のインストール パス

PHP の設定

それぞれインストールした場所の php.ini を適当に変更します。

; 拡張が必要ならば…
extension_dir = "C:\PHP\5.2.3\ext"
extension=php_mbstring.dll
extension=php_pdo.dll

同じバージョンで複数の php.ini を使い分けたい場合は、後述の Apache 2.2 の設定 にて指定する事が可能です

Apache 2.2 のインストール

Apache HTTP Server Projectダウンロード ページ から、適当なバージョンをダウンロードします。Win32 Binary (MSI Installer) 版を使うと、手抜きができて最高です。

この後の手順では、Apache HTTP Server をインストールしたパスを C:\Program Files\Apache Software Foundation\Apache2.2 として進めます。

Apache 2.2 の設定

C:\Program Files\Apache Software Foundation\Apache2.2\conf\http.conf を開き、次の2行を末尾に追加します。

# PHP configuration
Include conf/extra/php.conf

C:\Program Files\Apache Software Foundation\Apache2.2\conf\extra\php.conf を作成し、次のルールで記述します。

<IfDefine パラメータ名>
    ディレクティブ
</IfDefine>

パラメータ名 は適当な名前で ok です。IfDefine ディレクティブは、Apache が起動された時に、指定したパラメータ名が定義されていれば内包するディレクティブを有効にします。 ディレクティブ には、PHP モジュールに関する設定を記述します。以下に一例を上げます。

# latest version on major 5
<IfDefine PHP5>
    LoadFile "C:\PHP\5.2.3\php5ts.dll"
    LoadModule php5_module "C:\PHP\5.2.3\php5apache2_2_filter.dll"
    AddInputFilter PHP php
    AddOutputFilter PHP php
    PHPINIDir "C:\PHP\5.2.3"
</IfDefine>

# latest version on major 4
<IfDefine PHP4>
    LoadFile "C:\PHP\4.4.7\php4ts.dll"
    LoadModule php4_module "C:\PHP\4.4.7\sapi\php4apache2.dll"
    AddType application/x-httpd-php .php
    PHPINIDir "C:\PHP\4.4.7"
</IfDefine>

# specified 5.1.6
<IfDefine PHP5_1_6>
    LoadFile "C:\PHP\5.1.6\php5ts.dll"
    LoadModule php5_module "C:\PHP\5.1.6\php5apache2_2.dll"
    AddType application/x-httpd-php .php
    PHPINIDir "C:\PHP\5.1.6"
</IfDefine>

IfDefine ディレクティブのパラメータ名については、次の Windows サービスへの登録 へ進んで下さい。

モジュールが必要とするファイルは、LoadFile ディレクティブであらかじめ読み込んでおきます。

PHP モジュールが使用する php.ini ファイルは PHPINIDir ディレクティブで指定できます。

Windows サービスへの登録

Windows サービスへ Apache を登録するには、コマンドプロンプトから次のコマンドラインを実行します。

httpd -k install -n "サービス名" -Dパラメータ名
-k install
このオプションは、Windows サービスへ Apache を登録する事を表します。
-n "サービス名"
このオプションは、登録するサービスの名前を表します。
-D パラメータ名
このオプションは、IfDefine ディレクティブで使用するパラメータ名を表します。

試しに、PHP 5 用のサービスを登録します。コマンドプロンプトを起動して、次のように打ちます。

C:\Program....\Apache2.2\bin>httpd -k install -n "Apache2.2 with PHP5" -D PHP5

登録に成功したの図。

実際に登録されているかどうかは、[コントロール パネル] → [管理ツール] → [サービス] を開いて確かめます。(同じ画面を Apache Service Monitor の [Services] ボタンからでも開けます。)

サービス一覧。

自分が登録した Apache2.2 with PHP5 がきちんとありました。もし表示されていなければ F5 キーで画面を更新して下さい。それでも表示しなければ登録できていません。ちなみに Apache2 はインストーラが登録したサービスです。

間違ったサービス名で登録してしまったり、削除したい場合は次のコマンドラインを実行します。

httpd -k uninstall -n "サービス名"
-k uninstall
このオプションは、Windows サービスから Apache を削除する事を表します。
-n "サービス名"
このオプションは、削除するサービスの名前を表します。

サービスへの登録が終わると、Apache Service Monitor にも反映されます。

新しいサービスが登録された Apache Service Monitor。

これで、タスクバーのアイコンから切り替えが行えるようになりました。

Windows サービスへの登録についての詳しいドキュメントは、Apache HTTP サーバ バージョン 2.2 ドキュメント 内のプラットフォーム固有の情報、 Microsoft Windows 内の Running Apache as a Service にあります。

Windows サービスの補足

登録したサービスは Windows 起動時に自動的に実行されます。これを、自分で指定したタイミングで起動させる方法は次の通りです。

[コントロール パネル] → [管理ツール] → [サービス] を開いて、目的の Apache サービスを選択し、プロパティを表示します。スタートアップの種類を手動にすると、サービスは Apache Service Monitor で開始を選ぶまで開始しません。自動にすると、 Windows 起動時にサービスも開始します。無効にするとサービスを実行不可能にします。

サービスのプロパティ画面。

アプリケーションの設定を保存するには、文字列化してファイルなり DB なり、ストレージ コンテナへ保存するのが一般的です。PHP にはこの文字列を作成するのに便利な関数が用意されています。

string serialize (mixed value)
値の保存可能な表現を生成する
mixed unserialize (string str)
保存用表現から PHP の値を生成する

値型、配列型、オブジェクト型などを簡単に文字列に変換・復号できます。これをシリアライズと言います。日本語で言うと永続化、直列化ですね。
PHP のセッション変数 $_SESSION もシリアライズを用いて実現されています。 ただし文字列に出来ない型もあり、fopen や fsockopen 等で使用するリソース型がそうです。リソース型は PHP のプロセスの状態に左右される為です。

以下は FTP 設定を記憶するサンプルです。
Settings クラスは自身のシリアライズ・デシリアライズを行います。
このクラスを継承して FTP の項目を表すメンバを持った FtpSettings クラスを用いて実現します。

Settings.class.php の定義です。


<?php
/**
 * アプリケーションの設定を表します。
 */
class Settings
{
    /**
     * 現在の設定を指定したファイルへ保存します。
     *
     * @param    string    ファイル名
     */
    function save($fileName)
    {
        // ファイルストリームを確立します。
        // 追記読み書きモードで開く理由は以下の二つです。
        // 1. ファイルがない場合に作成する。
        // 2. ロック権が確保される前にファイルサイズを
        //    0 bytes にさせない。
        $fp = fopen($fileName, 'a+');
        
        if ($fp === false)
        {
            trigger_error('ファイルストリームを開けません。', E_USER_ERROR);
        }
        
        if (!flock($fp, LOCK_EX))
        {
            fclose($fp);

            trigger_error('ファイルの書き込み権を取得できません。', E_USER_ERROR);
        }
        
        if (!ftruncate($fp, 0))
        {
            fclose($fp);

            trigger_error('ファイルのサイズを変更できません。', E_USER_ERROR);
        }
        
        // 自身を表す文字列を作成します。
        $serializedString = serialize($this);

        rewind($fp);
        fwrite($fp, $serializedString);
        
        flock($fp, LOCK_UN);
        
        fclose($fp);
    }
    
    /**
     * ファイルから設定を読み込みます。
     *
     * @param    string    ファイル名
     * @return    mixid    Settings オブジェクト。失敗したら false を返します。
     */
    function & load($fileName)
    {
        $fp = fopen($fileName, 'a+');
        
        if ($fp === false)
        {
            trigger_error('ファイルストリームを開けません。', E_USER_ERROR);
        }
        
        if (!flock($fp, LOCK_SH))
        {
            fclose($fp);

            trigger_error('ファイルの読み込み権を取得できません。', E_USER_ERROR);
        }
        
        rewind($fp);
        $serializedString = fread($fp, filesize($fileName) + 1);
        
        flock($fp, LOCK_UN);

        fclose($fp);
        
        // 設定をデシリアライズします。
        // デシリアライズに失敗したら
        // 新しい設定を作成します。
        $settings = unserialize($serializedString);
        
        return $settings;
    }
}

?>

serialize_test.php の定義です。


<?php
require_once('Settings.class.php');

/**
 * FTP 用の設定を表します。
 */
class FtpSettings extends Settings
{
    var $host;
    var $port;
    var $homeDirectory;
    var $userName;
    var $password;
}


// 設定を読み込みます。
define('SETTING_FILENAME', 'settings.ini');

$settings =& FtpSettings::load(SETTING_FILENAME);

if ($settings === false)
{
    $settings =& new FtpSettings();
}


// 上書きする設定があれば保存します。
if (isset($_POST['action']) && $_POST['action'] === 'execute')
{
    if (isset($_POST['host']) && is_string($_POST['host']))
    {
        $settings->host = $_POST['host'];
    }

    if (isset($_POST['port']) && is_string($_POST['port']))
    {
        $settings->port = $_POST['port'];
    }

    if (isset($_POST['userName']) && is_string($_POST['userName']))
    {
        $settings->userName = $_POST['userName'];
    }

    if (isset($_POST['password']) && is_string($_POST['password']))
    {
        $settings->password = $_POST['password'];
    }

    if (isset($_POST['homeDirectory']) && is_string($_POST['homeDirectory']))
    {
        $settings->homeDirectory = $_POST['homeDirectory'];
    }

    $settings->save(SETTING_FILENAME);
}

?>

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html>
    <head>
        <title>シリアライズ・デシリアライズのサンプル</title>

        <style type="text/css">
            kbd { padding: 0 0.4em; font-weight: bold; border: 1px outset gray; }
        </style>
    </head>
    
    <body>
        <fieldset>

            <legend>設定</legend>
            
            <form action="<?php echo $_SERVER['SCRIPT_NAME']; ?>" method="post">
                <input type="hidden" name="action" value="execute" />

                <table>
                    <tr>
                        <td><label for="Host">ホスト <kbd>H</kbd></label></td>

                        <td><input id="Host" type="text" name="host" value="<?php echo htmlspecialchars($settings->host); ?>" accesskey="H" /></td>
                    </tr>

                    <tr>
                        <td><label for="Port">ポート <kbd>P</kbd></label></td>
                        <td><input id="Port" type="text" name="port" value="<?php echo htmlspecialchars($settings->port); ?>" accesskey="P" /></td>

                    </tr>
                    <tr>
                        <td><label for="UserName">ユーザー名 <kbd>U</kbd></label></td>
                        <td><input id="UserName" type="text" name="userName" value="<?php echo htmlspecialchars($settings->userName); ?>" accesskey="U" /></td>

                    </tr>
                    <tr>
                        <td><label for="Password">パスワード <kbd>P</kbd></label></td>
                        <td><input id="Password" type="password" name="password" value="<?php echo htmlspecialchars($settings->password); ?>" accesskey="P" /></td>

                    </tr>
                    <tr>
                        <td><label for="HomeDirectory">ホーム ディレクトリ <kbd>M</kbd></label></td>
                        <td><input id="HomeDirectory" type="text" name="homeDirectory" value="<?php echo htmlspecialchars($settings->homeDirectory); ?>" accesskey="M" /></td>

                    </tr>
                </table>                
                
                <input type="submit" name="submit" value="更新" />
            </form>
        </fieldset>
    </body>

</html>

実行結果のイメージです。
figure1.png

PDO関数 単純な接続

| | コメント(2)

実際に MySQL サーバーに接続しデータを取得してみます。 サンプルではホスト名 localhost に接続し、testdb データベースの testtable テーブルにある全ての行を取得します。

<html><head><title>PDO サンプル - 接続テスト</title></head>
<body>
<style type="text/css">
table { border-collapse: collapse; }
caption { font-weight: bold; }
th, td { padding: 0.2em 1em; border: 1px solid gray; }
th { background-color: whitesmoke; border-bottom-width: 2px; }
td.id { text-align: center; }
col.id { background-color: aliceblue; }
</style>

<?php
/**
 * DSN (Data Source Name) です。
 * PDO は DSN を元に DB へ接続します。
 *
 * 'mysql:localhost; dbname=testdb;' の場合、
 * MySQL 用の PDO ドライバを使用し、
 * ホスト名 "localhost" にある MySQL サーバに接続し、
 * デフォルトで使用するデータベースは "testdb" とします。
 *
 * 書式は使用する PDO ドライバによって異なります。
 * 詳しくは PDO ドライバ毎のマニュアルを参照下さい。
 * http://www.php.net/manual/en/ref.pdo.php#pdo.drivers
 */
$dsn = 'mysql:host=localhost; dbname=testdb;';

/**
 * DB サーバの接続に使用するユーザ名です。
 */
$userName = 'username';

/**
 * DB サーバの接続に使用するパスワードです。
 */
$password = 'password';

try
{
	// 接続を確立します。
	//
	// なんらかの理由により接続出来なかった場合、
	// PDOException 例外が発生します。
	$connection = new PDO($dsn, $userName, $password);
	
	// 接続毎の設定を行います。
	//
	// PDO::ATTR_ERRMODE 定数は「エラーが発生した時の通知方法を設定する」を示し、
	// PDO::ERRMODE_EXCEPTION 定数は 「例外を発生させる」を示します。
	//
	// もし例外ではなく E_WARNING エラーを発生させたい時は
	// $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
	// とします。
	//
	// setAttribute メソッドに渡せる定数はこちらを参照下さい。
	// http://www.php.net/manual/en/function.pdo-setattribute.php
	$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
	
	// 接続に対しコマンドを発行します。
	// PDO::query メソッドはステートメント実行後の結果セットを表す
	// PDOStatement オブジェクトを返します。
	//
	// なんらかの理由によりエラーが発生した時、
	// PDO::ATTR_ERRMODE の値にそってエラーが通知されます。
	// デフォルト、もしくは値が PDO::ERRMODE_SILENT の場合エラーは通知されず、
	// 代わりに戻り値が false となります。
	// このスクリプトでは PDOException 例外が発生します。
	//
	// // デフォルト動作時のエラーチェック
	// $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
	// $statement = $connection->query('でたらめSQL');
	// if ($statement === false)
	// {
	//     trigger_error('よくわからないけどダメだった', E_USER_WARNING);
	// }
	$statement = $connection->query('select * from `testtable`');
	
	// 結果セットが表すデータの取得方法を設定します。
	// PDO::FETCH_NAMED 定数は「カラム名を添え字にした配列として受け取る」を
	// 表します。
	$statement->setFetchMode(PDO::FETCH_NAMED);
	
	// 結果を取得します。
	echo '<table summary="testtable の中身">';
	echo '<caption>testtable の中身</caption>';
	echo '<col class="id" /><col class="name" /><col class="tel" />';
	echo '<thead><tr><th>ID</th><th>Name</th><th>Tel</th></tr></thead>';
	echo '<tbody>';

	while (($row = $statement->fetch()) !== false)
	{
		echo '<tr>';
		echo '<td class="id">' . htmlspecialchars($row['Id']) . '</td>';
		echo '<td class="name">' . htmlspecialchars($row['Name']) . '</td>';
		echo '<td class="tel">' . htmlspecialchars($row['Tel']) . '</td>';
		echo '</tr>';
	}
	
	echo '</tbody>';
	echo '</table>';

	// 接続を切断します。
	//
	// PDO クラスには明示的に切断するメソッドが無く、
	// オブジェクトが破棄される時に切断されます。
	// 通常スクリプト終了時にオブジェクトも破棄されますので
	// 明示的に切断する必要はありません。
	//
	// もしスクリプトの途中で切断が必要になった時は、
	// オブジェクトを GC(ガベージ コレクト) によって破棄する必要があります。
	// GC とは、不要になった資源(メモリ・各種接続等)を開放する PHP の機能です。
	// PDO オブジェクトを GC の対象にさせるには、PDO オブジェクトを参照している
	// 変数を削除するか、NULL 等を代入してオブジェクトへの参照をなくします。
	//
	// unset($connection);
	// $connection = null;
	// $connection = "お前など絶縁だ!";
	//
	// これらは全て PDO オブジェクトを破棄させるのに有効です。
}
catch (PDOException $e)
{
	echo "エラー: " . $e->getMessage();
}

?>

</body></html>

実行結果のイメージです。
実行結果のイメージ

続く...

PDO 関数

| | コメント(4)

PHP 5.1 になり、PDO 関数が標準でサポートされた。PDO とは PHP Data Object の頭文字で、データベースの為のクラス郡。かっこよく言えばデータベース抽象化レイヤーであり、さかしげに言えばインピーダンス ミスマッチを解消するO/Rマッパーではない。シティーボーイなら、データベース アブストラクト レイヤなんて言っちゃうかもしれない。この辺りの考え方に対するネーミングに惑わされてはいけない。結局はプログラムとデータベースの接続を楽チンにしたいだけなのだから。同じような物に PEAR:DB 等があるが、PDO はシンプル・軽量が売りだそうな。身内向けのドキュメントを書く事があったのでその準備編。

準備

PDO 関数を利用する為には PHP 5 と PDO エクステンション、それと使用するデータベース用の PDO ドライバの3つが必要です。
導入例として、Win32 環境で稼動している Apache™ 2.0 に PHP をモジュールとしてインストールする方法、及び PDO を用いて MySQL™ にアクセスする際の手順を示します。

  1. PHP のウェブサイトから PHP 5 のパッケージをダウンロードします。
    ダウンロードしたパッケージを、適当なフォルダへ解凍します。
    今回は PHP 5.1.1 zip package を用意し、 "C:\PHP" へ作成しました。
  2. PHP の設定を行います。
    php-recommanded.ini を複製し、それを php.ini にリネームします。

    作成した php.ini を開き、設定を確認します。

    変更前 変更後 備考
    display_erros = Off display_erros = On 実行時エラーを出力するかどうかを表します
    display_startup_errors = Off display_startup_errors = On PHP の起動シーケンスで発生するエラーを表示するかどうかを表します
    extension_dir = "./" extension_dir = "C:\PHP\ext" エクステンションが置いてあるディレクトリを表します
    ;extension=php_mbstring.dll extension=php_mbstring.dll mbstring エクステンションを有効にします
    ;mbstring.language = Japanese mbstring.language = Japanese mbstring エクステンションの言語を表します
    ;mbstring.detect_order = auto mbstring.detect_order = auto mbstring エクステンションのエンコーディング検出方法を表します
    ;mbstring.internal_encoding = EUC-JP mbstring.internal_encoding = EUC-JP mbstring エクステンションの内部エンコーディングを表します

    php.ini に以下の行を追加します。

    備考
    extension = php_pdo.dll PDO エクステンションを有効にします
    extension = php_pdo_mysql.dll MySQL 用の PDO ドライバを有効にします

    補足

    • デバッグ時の問題を判り易くする為の設定です。運用時には Off にする事が強く推奨されています。
    • php.ini の雛形。php-recommanded.ini は推奨される設定であり、php-dest.ini は過去の PHP との互換性に重点を置いた物です。
    • これらのファイルが extension_dir で示したディレクトリにない場合、 PECL から別途入手する必要があります。
  3. PHP パッケージ内の php5ts.dll を、OS のシステム フォルダにコピーします。

    OS システム フォルダ
    (OS が C ドライブにインストールされている場合)

    Microsoft® Windows® XP

    C:\WINDOWS\system32
    Microsoft® Windows® 2000
    Microsoft® Windows® NT 4.0
    C:\WinNT\system32
    Microsoft® Windows® Me
    Microsoft® Windows® 98SE
    Microsoft® Windows® 98
    Microsoft® Windows® 95
    C:\WINDOWS\system
  4. Apache 2 の設定を行います。
    http.conf を開き、以下の文字列を追加します。

    LoadModule php5_module "C:\PHP\php5apache2.dll"
    <IfModule mod_php5.c>
    AddType application/x-httpd-php .php
    PHPIniDir "C:\PHP"
    </IfModule>
  5. 設定が完了したら Apache を再起動します。
    同時に設定が正しく反映されているか確認をします。
    phpinfo.php ファイルをドキュメントルート可に作成し、以下の内容を記入します。
    <?php echo phpinfo(); ?>

    ブラウザからアクセスして PHP クレジット他が表示されていれば正常です。

続く...

http://jp.php.net/manual/ja/configuration.changes.php 参照。

<IfModule mod_php5.c>
php_value include_path ".:/usr/local/lib/php"
php_admin_flag safe_mode on
</IfModule>
<IfModule mod_php4.c>
php_value include_path ".:/usr/local/lib/php"
php_admin_flag safe_mode on
</IfModule>
<IfModule mod_php3.c>
php3_include_path ".:/usr/local/lib/php"
php3_safe_mode on
</IfModule>

ただし Win32 と FreeBSD では以下の通り。

>>> for apache 2 under freebsd (not sure about other systems) it should be
>>> <IfModule sapi_apache2.c>
>>> ...
>>> </IfModule>

Same for Windows

PHP5 は mod_php5.c のままで良いのかしら?
Apache2.0.55 と PHP5.0.5 で確認したところ mod_php5.c できちんとディレクティブは有効になっていた。
IfModule ディレクティブで使用するモジュール名とは、モジュールのソースファイルと同じ。
参照 http://httpd.apache.org/docs/2.0/ja/mod/module-dict.html
この辺後で調べよう。

何の為に使うのか。IfModule ディレクティブ内に PHPIniDir を記入し、PHP4 と PHP5 で読みに行く設定ファイルを別々に持ちたい。自分で make してデフォルトの設定ファイルパスを変更する方法もあるみたい。

このモジュール名の例外が判らず、楽をしようとしたのに余計手間が掛かってしまった。説明書をちゃんと読んでいない証拠だ。反省。