関数, メソッドを動的に実行する

C の関数ポインタや、Java のリフレクションのように任意の関数、メソッドを呼び出すには次のようにします。

<?php
function greet(){ echo “Hello world!”;}
 
// 変数に () を付ける記法(関数のみ可)
echo “0: “;
$func=”greet”;
$func();
 
// call_user_func() を使った関数よびだし
echo “\n1: “;
$func=”greet”;
call_user_func($func);
 
// call_user_func() を使った静的メソッドよびだし
echo “\n2: “;
class Foo{
   function doit(){
      echo “static method called”;
   }
}
$method=array(“Foo”, “doit”);
call_user_func($method);
 
// call_user_func() を使った非静的メソッドよびだし
echo “\n3: “;
class Foo2{
  var $name;
  function Foo2($name){
    $this->name = $name;
  }
  function doit(){
    echo “non static method called (“.$this->name.”)”;
  }
}
$o=new Foo2(“abc”);
$method=array($o, “doit”);
call_user_func($method);
 
echo “\n”;
?>

上のコードを実行すると結果は次のようになります。

0: Hello world!
1: Hello world!
2: static method called
3: non static method called (abc)

参考:
call_user_func()

PEAR – DB から MDB2 への移行

PEAR :: Package :: DB

This package been superseded by MDB2 but is still maintained for bugs and security fixes
(意訳: このパッケージの代わりに MDB2 が使われるようになりましたが、バグ、セキュリティの修正はまだ続けられています)

とあったので、PEAR::DB から PEAR::MDB2 への移行について書かれているサイトをメモしておきます。
 
DB to MDB2 migration
 
参考/情報元:
PEAR::MDB2
MDB2 Homepage

PHP で HTTP 認証情報を取得する

HTTP 認証に成功した上でスクリプトにアクセスした場合、

$_SERVER[‘PHP_AUTH_USER’]

に HTTP 認証した時のログインユーザ名が格納されます。
認証に失敗した、あるいは認証が行われていない場合は空になるため、
この変数を調べることでログイン状態を調べることが可能です。
 
参考:
PHP による HTTP 認証(公式マニュアル)

PHP_Error とエラー処理

PEAR を利用しているとよく

$res = $conn->query(“SELECT no_such_field FROM a_table”);
if(PEAR::isError($res)){ // DB::isError($res) も同じ
   die($res->getMessage()); // エラー表示して終了
}

という表記をしますが、全ての PEAR パッケージについて統一的にエラー処理したい場合は PEAR::setErrorHandling() というメソッドが有用です。
 
たとえば、次のコードは上のものと同様の結果になります。

// PEAR_ERROR_DIE: PEAR_Error がインスタンス化されたら getMessage() を出力して終了するようにする。
PEAR::setErrorHandling(PEAR_ERROR_DIE);
// あとは普通に実行
$res = $conn->query(“SELECT no_such_field FROM a_table”);
// ※ここでエラー処理は不要!

 
詳細なエラー処理を行いたい場合は PEAR_ERROR_CALLBACK を利用します。

// 例1:
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, “var_dump”);
// エラー発生時に
// if(PEAR::isError($res)){ var_dump($res); }
// とするのと等価
 
// 例2:
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, “error_exit”);
function error_exit($pear_err){
  die($pear_err->getMessage().”:”.$pear_getDebugInfo());
}
// エラー発生時にユーザ定義関数 error_exit を呼び出すのと等価

 
static に呼ばずインスタンスメソッドとして setErrroHandling をコールした場合、そのインスタンスについてのみ、ハンドリングします。
 
参考:
PEAR 基底クラス – はじめに

GD でマスク処理

ふと、ImageLayerEffect() という関数を見付けたのですが、情報がなかったため、使い方を書いておきます。
 
User Contributed Notes より..

bool imagelayereffect ( resource image, int effect )
 
この関数は全ての描画関数の処理に影響を与えるという点で、既存の関数 ImageAlphaBlending() と似ています。(以下に示す)エフェクトモードを切替えることで既存の関数の機能を拡張できます。
 
effect には以下のうちいずれか一つを指定します。
 
IMG_EFFECT_REPLACE
ピクセル置換を利用します(ImageAlphaBlending(FALSE) と同等)
 
IMG_EFFECT_NORMAL
通常のピクセルブレンディングを利用します(ImageAlphaBlending(TRUE) と同等)
 
IMG_EFFECT_OVERLAY
 オーバーレイルーチンを利用します。オーバーレイは、黒い下地のピクセルは黒のまま、白い下地のピクセルは白のままですが、グレーの下地のピクセルに対しては上に乗るピクセルの色をとります。

 
当然気になるのは、IMG_EFFECT_OVERLAY です。
説明にある下地のピクセル(background pixels)というのは、たとえば imagecopy() を使う時の、コピー先の画像のそれぞれのピクセル、上に乗るピクセル(foreground pixels)というのはコピー元の画像のそれそれのピクセルにあたります。
 
下地がグレー(0x7F7F7F)の時は上に乗る色、
RGB のそれぞれが 0 に近付くほど、上に乗る色のそれぞれの色を暗くし、0になると元の色によらず 0 になります。つまり全てが0になると真黒(0x000000)になります。
同様に FF に近付くほど、元の色が明るくなり全てが FF になると真っ白(0xFFFFFF)になるようです。
 
例: 画像の任意の部分を黒に塗りつぶす

$im = imagecreatefromjpeg(“sample.jpg”); // 任意の元画像
// 画像サイズ
$w = imagesx($im);
$h = imagesy($im);
// 非透過
$c_visible = 0x007F7F7F;
// 背景(黒)
$c_bg = 0x00000000;
// 中央に円のあるマスクを作成
$im_mask = imagecreatetruecolor($w,$h);
imageFilledRectangle($im_mask, 0,0, $w,$h, $c_bg);
imageFilledEllipse ($im_mask, $w/2,$h/2, $w,$h, $c_visible);
 
// コピー先のエフェクトモードを変更(デフォルトは IMG_EFFECT_NORMAL)
imagelayereffect($im_mask, IMG_EFFECT_OVERLAY);
 
// 元画像にマスクを適用(結果はマスクに上書き)
imagecopy($im_mask, $im, 0,0, 0,0, $w,$h);
imagedestroy($im);
 
// マスク結果を出力
imagepng($im_mask);
imagedestroy($im_mask);

 
非透過とある部分を、 たとえば 0x00FF007F にすると、非透過部分のそれぞれのピクセルに対し、R 要素を FF, G 要素を 0, B 要素をそのままで出力します。
 
また、α値も組合せることが可能です。
 
例:画像の任意の部分を透過する

$im = imagecreatefromjpeg(“sample.jpg”); // 任意の元画像
// 画像サイズ
$w = imagesx($im);
$h = imagesy($im);
// 非透過
$c_visible = 0x007F7F7F;
// 背景(透過)
$c_bg = 0x7F7F7F7F;
// 中央に円のあるマスクを作成
$im_mask = imagecreatetruecolor($w,$h);
imagelayereffect($im_mask, IMG_EFFECT_REPLACE); // 透過色塗りのため上書きモードに変更。
imageFilledRectangle($im_mask, 0,0, $w,$h, $c_bg);
imageFilledEllipse ($im_mask, $w/2,$h/2, $w,$h, $c_visible);
imagelayereffect($im_mask, IMG_EFFECT_NORMAL); // 透過色を透過するため通常モードに戻す。
 
// コピー先のエフェクトモードを変更(デフォルトは IMG_EFFECT_NORMAL)
imagelayereffect($im_mask, IMG_EFFECT_OVERLAY);
 
// 元画像にマスクを適用(結果はマスクに上書き)
imagecopy($im_mask, $im, 0,0, 0,0, $w,$h);
// 透過色を保存する設定
imagesavealpha($im_mask, true);
 
imagepng($im_mask);
imagedestroy($im);

imagerotate の小数から整数への変換仕様

imagerotate の結果を計算で出したいなと思い次のコードで検証してみました。

<?php
// 対象の画像のサイズ。
$sw = 1000;
$sh = 2000;
// 実測用に画像生成
$im = imagecreatetruecolor($sw,$sh);
for($deg = -360; $deg <= 360; $deg +=15){
   $rad = deg2rad($deg); // 三角関数はラジアンを取るので角度をラジアンに変換
   // 回転後のサイズ計算(四捨五入)
   $w = round(abs($sw * cos($rad)) + abs($sh * sin($rad)));
   $h = round(abs($sw * sin($rad)) + abs($sh * cos($rad)));
   // 実際に回転させた時のサイズ
   $im_t =imagerotate($im, $deg, -1);
   $rw = imagesx($im_t);
   $rh = imagesy($im_t);
   imagedestroy($im_t);
   // 比較
   if($w != $rw || $h != $rh){
     echo “$deg: calc($w, $h) != real($rw, $rh) “.”\n”;
   }
}
?>

結果:

-330: calc(1866, 2232) != real(1867, 2233)
-315: calc(2121, 2121) != real(2122, 2122)
-300: calc(2232, 1866) != real(2233, 1867)
-240: calc(2232, 1866) != real(2233, 1867)
-225: calc(2121, 2121) != real(2122, 2122)
-210: calc(1866, 2232) != real(1867, 2233)
-150: calc(1866, 2232) != real(1867, 2233)
-135: calc(2121, 2121) != real(2122, 2122)
-120: calc(2232, 1866) != real(2233, 1867)
-60: calc(2232, 1866) != real(2233, 1867)
-45: calc(2121, 2121) != real(2122, 2122)
-30: calc(1866, 2232) != real(1867, 2233)
30: calc(1866, 2232) != real(1867, 2233)
45: calc(2121, 2121) != real(2122, 2122)
60: calc(2232, 1866) != real(2233, 1867)
120: calc(2232, 1866) != real(2233, 1867)
135: calc(2121, 2121) != real(2122, 2122)
150: calc(1866, 2232) != real(1867, 2233)
210: calc(1866, 2232) != real(1867, 2233)
225: calc(2121, 2121) != real(2122, 2122)
240: calc(2232, 1866) != real(2233, 1867)
300: calc(2232, 1866) != real(2233, 1867)
315: calc(2121, 2121) != real(2122, 2122)
330: calc(1866, 2232) != real(1867, 2233)

この結果から判断して、
imagerotate では内部的に ceil で繰り上げにしているようなので
単純に round -> ceil にかえてみたところ、
結果は次のようになりました。

-360: calc(1001, 2001) != real(1000, 2000)
-270: calc(2001, 1001) != real(2000, 1000)
-180: calc(1001, 2001) != real(1000, 2000)
-90: calc(2000, 1001) != real(2000, 1000)

PHP の float の特性(*1)が悪さをしているのか、切り上げ処理で GD の結果と食い違いができてしまったようです。
ということで次のようにすれば完全に一致しました。

<?php
// 回転後のサイズ計算(imagerotate 互換の切り上げ)
$fw = abs($sw * cos($rad)) + abs($sh * sin($rad));
$w = ceil(“$fw”); // 一度文字列にする
$fh = abs($sw * sin($rad)) + abs($sh * cos($rad));
$h = ceil(“$fh”); // 同上

 
 *1: PHP の float は有効桁数を超えると表示上その部分が見えなくなります。

$f = 1 + 1E-13;
echo “$f\n”; // 1 が表示される
if($f > 1){
    echo “$f > 1\n”; // 1 > 1 が表示される。
}
echo sprintf(“%.15f”, $f).”\n”; // 1.000000000000100 が表示される

Yahoo API の簡単な利用法 – serialize

http://developer.yahoo.co.jp/
で公開されている API は全て XML でレスポンスが返りますが、
output=php をパラメータに追加すると、PHP の serialize() 形式でシリアル化されたデータが返るという記事を見付けました。
 
注:ライブラリを利用することで、他の言語(C#, perl など)でも PHP serialize は読み込むことが可能なため、用途は PHP に限りません。これに付いてはリンク先の最後を参照してください。
 
Using Serialized PHP with Yahoo! Web Services
 
ここに書かれている、
http://api.search.yahoo.com/ImageSearchService/V1/imageSearch?appid=YahooDemo&query=Madonna&results=1&output=php
にアクセスしてみるとサンプル出力が得られます。
 
以下、上記 API を利用した PHP のサンプルコードです。

<?php
$url = “http://api.search.yahoo.com/ImageSearchService/V1/” .
        “imageSearch?appid=YahooDemo&query=fruit+cake&results=1&output=php”;
ob_start();
readfile($url);
$result =unserialize(ob_get_contents()); // 結果セットを取得
ob_end_clean();
 
// var_dump($result); // どういう内容が格納されているか確認したい場合コメントアウト。
 
 $thumbnail_url = $result[“ResultSet”][“Result”][0][“Thumbnail”][“Url”];
?>
<html>
<head>
  <title>サムネイル</titte>
</head>
<body>
  <?php foreach($result[“ResultSet”][“Result”] as $record){ ?>
     □<?=$record[‘Title’]?>
       (<?=$record[‘FileSize’]?> バイト)<br>
     <p>説明:<?=$record[‘Summary’]?></p>
     <a href=”<?=$record[‘ClickUrl’]?>”><img src=”<?=$record[‘Thumbnail’][‘Url’]?>”></a><br>
     <hr>
  <?php } ?>
</body>
</html>

 
参考:
Yahoo曰く、XMLじゃなくてPHPの変数をそのままシリアライズして返せばいいじゃん

静的にも動的にも呼び出し可能なメソッドを定義する

次のように、メソッド内で $this の存在を empty() ないし isset でチェックすることで、エラーになることなく static かインスタンスメソッド呼び出しかで動作を分けることができます。
(PHP4.4 / 5.1 にて動作確認済)

<?php
class Foo{
  var $mes;
  // コンストラクタ
  function Foo($mes){
    $this->mes = $mes;
  }
  // クラスメソッド兼インスタンスメソッド
  function greet($mes = false){
    if(!$mes && !empty($this) ){
      // インスタンスが存在する。
      $mes = $this->mes;
    }else{
      // static 呼び出し。
    }
    echo ($mes?$mes:’empty’).”\n”;
  }
}
error_reporting(E_ALL);
$o = new Foo(‘hello’);
$o->greet(); // 結果: hello
 
Foo::greet(‘static hello’); // 結果: static hello
Foo::greet(); // 結果 empty
 
?>

変数代入とそのメモリ使用量についての検証

大したことではありませんが、ふと気づいたのでメモしておきます。
 
検証環境: PHP 4.4.2
時間, メモリ量のリミットなし
なお、都合により memory_get_usage() ではなく top コマンドでメモリ消費を確認しています。
 

$n = 5; // sleep する秒数
 
echo “Started..\n”;
$a = str_repeat(“a”, 1024 * 1024 * 10); // 10MB
sleep($n); // #使用量 13MB
echo “Cloning \$a into \$b.\n”;
$b = $a; // コピー
sleep($n); // #使用量..変化なし ←この時点では参照($b=&$a)と同等
echo “Modifing \$a.\n”;
$a{0} = ‘b’; // $a の 0 文字目を ‘b’ に変更
sleep($n); // #使用量..23MB ←一方が変更されて始めてバッファが複製される
echo “Freeing \$a.\n”;
$a = null; // 解放
sleep($n); // #使用量..13MB
echo “Referring \$b for \$c \n”;
$c =& $b; // 参照
sleep($n); // #使用量..変化無し
echo “Modifing \$b.\n”;
$b{0} = ‘c’; // $b の 0 文字目を ‘c’ に変更
sleep($n); // #使用量..変化無し
echo “Freeing memory for \$b (and also \$c)\n”;
$b = null; // 解放
sleep($n); // #使用量..3MB

 
この検証でわかるのは、単純な代入の場合は、代入元、代入先いずれかが変更されるまでは参照と同等に処理する(つまりメモリ確保や strcpy をしない)ということです。
 
上の例のように変数の値を捨てたり unset したりして明示的にメモリを解放してやらなくても、変数が関数の終了でスコープアウトした時点で自動的に解放されます。
 
ただし、Chain of Repository Pattern など、メソッド、関数の呼び出しが連鎖する場合は、呼び出し先の関数が終了するまで、呼び出し元の関数内の変数も存在し続ける事に注意が必要です。
 

$a = str_repeat(“a”, 1024 * 1024 * 10);
// 3 段の連鎖(※1)
$o = new Chain(1,
  new Chain(2,
    new Chain(3, null)
  )
);
$o->handle($a); // 連鎖実行。
// ↑ここだけで最終的に Chain の段数 x strlen($a) バイトのメモリを消費する!!
 
echo “Finished\n”;sleep(6);
echo substr($a, 0, 100).”\n”;
exit;
// ——————————–
// Chain Of Repository パターンの簡単な実装
class Chain{
   var $v;
   var $_next;
   function Chain($v, $next = null){
       $this->v = $v;
       $this->_next = $next;
   }
   function next(&$buf){
       if(!empty($this->_next)) $this->_next->handle($buf);
   }
   function handle(&$buf){
       echo “Step:{$this->v}\n”;
       // $buf に使って何らかの処理をし、結果を $res に格納。
       $res = $this->v.$buf;
       $buf = $res; // $buf を処理結果に置き換え。
       sleep(5); // (※2)
       $this->next($buf); // 次の処理へ($res が生きている)。
   }
}

 
この Chain クラスでは(※1)を重ねれば重ねるほどメモリ利用量が増え続け、handle が全て完了するまで解放されないという一種のメモリリークが発生しています。
これを解消するには(※2) の箇所で $res = null などで解放を明示してやる必要があります。