static property
仕事の時に見てたコードで、おや?と思ったものがあったので調べた。
クラス継承時に、親の持っている static property は子から参照しても同じ値が入っています。(static_property.php)
しかし、子クラスで同名の static property を宣言すると、親と子で別々の値が持てるようになります。(static_property_self.php)
理由はわりと単純で、子クラスで self::$foo と参照したときに、まず自分のクラスで定義を探す。なければ、 parent へ探しに行く。
なので、子クラスからも必ず同じ static property を参照したい場合は、self ではなく parent を指定しておくと良さそう。(static_property_parent.php)
コード見た方が早いと思うので、サンプル貼っておきますね。
メソッドの呼び出し方
PHPでは、static宣言なしでもメソッドのstatic呼び出しができてしまう。
で、どっちが速いの?という話。
メモリの使用量も比べてみたかったけど、速さのみ。
<?php require_once 'Benchmark/Timer.php'; class MyTest { public function myMethod() { return; } public static function staticMethod() { return; } } $max = 100000; $timer = new Benchmark_Timer(); $t = new MyTest(); $timer->start(); for($i = 0; $i < $max; $i++) { MyTest::staticMethod(); } $timer->setMarker('static'); for($i = 0; $i < $max; $i++) { MyTest::myMethod(); } $timer->setMarker('static2'); for($i = 0; $i < $max; $i++) { $t->myMethod(); } $timer->setMarker('instance'); $timer->stop(); $timer->display(); /* ------------------------------------------------------ marker time index ex time perct ------------------------------------------------------ Start 1293934691.81032700 - 0.00% ------------------------------------------------------ static 1293934691.85720200 0.046875 21.74% ------------------------------------------------------ static2 1293934691.99593900 0.138737 64.35% ------------------------------------------------------ instance 1293934692.02592700 0.029988 13.91% ------------------------------------------------------ Stop 1293934692.02593700 0.000010 0.00% ------------------------------------------------------ total - 0.215610 100.00% ------------------------------------------------------ */
$ php --version PHP 5.3.3 (cli) (built: Aug 22 2010 19:41:55)
static宣言なしのメソッドをstaticに呼ぶのは遅いってことかしら。
ちなみにrubyだとこうなった。
static methodは存在しないので、class method。このへんはうろ覚えなので、ちょっと自信ない・・・
require 'benchmark' class MyTest def myMethod 1 + 2 end def self.classMethod 1 + 2 end end module MyModule def moduleMethod 1 + 2 end end module MyModule2 def moduleClassMethod 1 + 2 end end max = 100000; Benchmark.bmbm(10) do |x| t = MyTest.new x.report("instance: ") { max.times do t.myMethod end } x.report("class : ") { max.times do MyTest::classMethod end } MyTest.class_eval { include MyModule } t = MyTest.new x.report("module : ") { max.times do t.moduleMethod end } MyTest.extend MyModule2 x.report("module class : ") { max.times do MyTest::moduleClassMethod end } end
Rehearsal --------------------------------------------------- instance: 0.040000 0.000000 0.040000 ( 0.043314) class : 0.050000 0.000000 0.050000 ( 0.049375) module : 0.040000 0.000000 0.040000 ( 0.042887) module class : 0.050000 0.000000 0.050000 ( 0.045320) ------------------------------------------ total: 0.180000sec user system total real instance: 0.040000 0.000000 0.040000 ( 0.042020) class : 0.040000 0.000000 0.040000 ( 0.046069) module : 0.040000 0.000000 0.040000 ( 0.041350) module class : 0.040000 0.000000 0.040000 ( 0.045474)
$ ruby --version ruby 1.8.7 (2010-08-16 patchlevel 302) [i686-darwin10]
ビット演算子の恐怖
PHPは暗黙の型変換をしてくれます。
余計なお世話なことが多いですが、適当プログラマには便利な場面もあります。
さて、ビット演算。フラグを複数設定したい場合なんかにたまに使いますが、こいつがまた余計なことをしてくれます。
なんと、文字列がビット演算できる!
データ型の変換に注意しましょう。両辺のパラメータが文字列の場合、 ビット演算子は文字の ASCII コードで演算を行います。
どうしてこうなるんだよ!!!!!!
じゃあ、文字列同士じゃない場合は?
試してみた。
<?php $a = '100'; $b = '20'; $c = 20; $d = 'foo'; var_dump($a | $b); // 数値へ変換可能な文字列同士 var_dump($a | $c); // Intと数値へ変換可能な文字列 var_dump($a | $d); // 数値へ変換可能な文字列と変換できない文字列 var_dump($c | $d); // Intと数値へ変換できない文字列 /* Output: string(3) "300" int(116) string(3) "w" int(20) */
- 文字列同士だと、asciiコードによる演算を行う
- 文字列同士じゃない場合は、Intへ暗黙の型変換されてるっぽい
もっとも考え得るはまりポイントとしては・・・「数値文字列同士をビット演算して、意図とは全く異なる結果が返ってくる。」ですかね。
いつも型を意識していない場合や、DBやテキストから数字を取得した時なんかは特に要注意。
おまけ
本当に内部でIntに型変換 しているらしい。あほか。
<?php var_dump(true | 20); // true と Int var_dump(false | 20); // false と Int var_dump(2.5 | 3.3); // float同士 /* Output: int(21) int(20) int(3) */
というわけで
PHPは本当に余計なおせっかい焼きのkuso
素晴らしい言語ですね!!!!
ReflectionClassでメソッドを動的に呼ぶ&ベンチマーク
以前に変数を使って、クラスに対して動的にプロパティを追加してみた。
Objectに動的にプロパティを追加する - うっかりプログラミング日誌
今度は動的にメソッドを呼んでみる。*1
<?php class Test1 { public function hoge() { return 1 + 1; } } $test = new Test1(); $method = 'hoge'; $test->$method(); //=> 2
簡単!
ReflectionClassを使う
とはいえ、この方法だと引数が固定数しか取れない。
そこでReflectionMethod#invokeArgsを使う。
<?php class Test2 { public function hoge($a, $b) { return $a + $b; } } $test = new Test2(); $method = 'hoge'; $args = array(1, 3); $ref = new ReflectionClass(get_class($test)); $met = $ref->getMethod($method); // ReflectionMethodクラスが返される $met->invokeArgs($test, $args); //=> 4
このReflectionClassが結構便利。
複数のクラスを受け取れるメソッドを用意しておいて、
ReflectionClass#hasMethod()と組み合わせたりすると良い。
ちなみにReflectionMethod#invoke()だと上記の変数呼び出しと同じ使い方になる。
で、遅くないの?
実はここが本題。
動的にメソッドを呼び出すのはいいけど、一体どっちが早いのか?
メソッド呼び出しを100万回くらいやれば速度差が出るかと思い、やってみた。
PEAR::Benchmarkを使っています。
<?php class Test1 { public function hoge() { return 1 + 1; } } require_once('Benchmark/Timer.php'); $timer = new Benchmark_Timer(); $test = new Test1(); $method = 'hoge'; $max = 1000000; $timer->start(); // 変数呼び出し for($i=0;$i < $max; $i++) { $test->$method(); } $timer->setMarker('Variable'); // ReflectionClass for($i=0;$i < $max; $i++) { $ref = new ReflectionClass(get_class($test)); if ($ref->hasMethod($method)) $met = $ref->getMethod($method); $met->invoke($test); } $timer->setMarker('Reflection'); $timer->stop(); $timer->display(); /* result --------------------------------------------------------- marker time index ex time perct --------------------------------------------------------- Start 1251959593.35938300 - 0.00% --------------------------------------------------------- Variable 1251959594.44166300 1.082280 5.83% --------------------------------------------------------- Reflection 1251959611.91642100 17.474758 94.17% --------------------------------------------------------- Stop 1251959611.91648500 0.000064 0.00% --------------------------------------------------------- total - 18.557102 100.00% --------------------------------------------------------- */
newを含めると、なんと17倍の差が。
純粋にinvokeとの速度差を調べてみました。
<?php class Test1 { public function hoge() { return 1 + 1; } } require_once('Benchmark/Timer.php'); $timer = new Benchmark_Timer(); $test = new Test1(); $method = 'hoge'; $max = 1000000; $timer->start(); for($i=0;$i < $max; $i++) { $test->$method(); } $timer->setMarker('Variable'); $ref = new ReflectionClass(get_class($test)); if ($ref->hasMethod($method)) $met = $ref->getMethod($method); for($i=0;$i < $max; $i++) { $met->invoke($test); } $timer->setMarker('Reflection'); $timer->stop(); $timer->display(); /* --------------------------------------------------------- marker time index ex time perct --------------------------------------------------------- Start 1251959636.48438300 - 0.00% --------------------------------------------------------- Variable 1251959637.56774800 1.083365 33.69% --------------------------------------------------------- Reflection 1251959639.69969100 2.131943 66.30% --------------------------------------------------------- Stop 1251959639.69976100 0.000070 0.00% --------------------------------------------------------- total - 3.215378 100.00% --------------------------------------------------------- */
それでもReflectionの方が倍くらいの時間がかかる。
- クラスやメソッドを解析して、厳密にやるならReflection。
- チェックとか特に必要なく呼び出せるなら変数。
という感じで使い分けると良さそう。
*1:これの呼び方がよくわからない
array_mapでstaticメソッドを呼び出す時に、selfが使えない
ここのところPHPばかりやっているので、小ネタ。
PHP 5.2.8 for Windows での話。
array_mapで関数名を指定する時、通常は文字列で指定する。
<?php function _add3($a) { return $a + 3; } $array = array(1,2,3); print_r(array_map('_add3', $array));
クラス内の関数の場合はarrayで指定する。
<?php class Test { public function _add3($a) { return $a + 3; } public function hoge($array) { return array_map(array($this, '_add3'), $array); } } $test = new Test(); $array = array(1,2,3); print_r($test->hoge($array));
インスタンスメソッドが$thisで指定できるなら、staticメソッドはselfでいける!と思ったら・・・
<?php class Test { public static function _add3($a) { return $a + 3; } public function hoge($array) { return array_map(array(self, '_add3'), $array); } } $test = new Test(); $array = array(1,2,3); print_r($test->hoge($array));
なぜかselfが定義されていない定数というNoticeが出る。
Notice: Use of undefined constant self - assumed 'self' in test.php on line 6
s/self/get_class($this)/
s/self/__CLASS__/
でNoticeは出なくなる。selfの実体は'self'という文字列なので、クラス名の文字列を渡す必要があるコールバック関数では使えないってことかしら。
まあ、E_STRICTを切ればいいんだけど・・・ねえ。
参考
静的なクラスメソッドの場合、 0 番目の要素としてオブジェクトを渡す代わりにクラス名を渡すことにより、 オブジェクトのインスタンスを作成せずに渡すことができます。
create_functionはクロージャにはなれない
Rubyだとこんな感じで書ける
def counter i = 0 lambda{|n| i += n} end count = counter puts count.call(1) #=> 1 puts count.call(2) #=> 3 puts count.call(3) #=> 6
PHPの場合も同じ結果を期待したけど、クロージャとしては動いてくれないらしい。
<?php function counter() { $i = 0; return create_function('$n',"$i = $i + $n;"); } $func = counter(); echo $func(1); // Parse error: syntax error, unexpected '=' echo $func(3); echo $func(5);
文字列をダブルクォーテーションで囲っているため、$iは文字列内で変数として評価されてしまう。
そのため、create_function内では以下のような式が入ることになる。
0 = 0 + 1
そりゃ動かないわな。
ちなみにシングルクォーテーションにすると、function内のローカル変数扱いになるため、外の変数は参照されません。
5.3のλ導入までは使えないってことですかね。
PHPのオーバーロードで動的にメソッドやメンバーを作成する
宣言していないメンバーやメソッドにアクセスしようとすると、マジックメソッドが呼ばれる。
そのマジックメソッドの中でごにょごにょしてあげれば良い。
それを使うと、メソッドチェインみたいなこともできる。
<?php class Child { } class Hoge { private $values = array(); public function __get($name) { if (isset($this->values[$name]) === false) { $this->values[$name] = new Child(); } return $this->values[$name]; } } $hoge = new Hoge(); $hoge->foo->bar = 'baz'; print_r($hoge); /* Hoge Object ( [values:private] => Array ( [foo] => Child Object ( [bar] => baz ) ) ) */
Cakeのアソシエイト辺りは、こういうのを駆使してるのかなー?