static property

仕事の時に見てたコードで、おや?と思ったものがあったので調べた。

 

クラス継承時に、親の持っている static property は子から参照しても同じ値が入っています。(static_property.php)
しかし、子クラスで同名の static property を宣言すると、親と子で別々の値が持てるようになります。(static_property_self.php)

理由はわりと単純で、子クラスで self::$foo と参照したときに、まず自分のクラスで定義を探す。なければ、 parent へ探しに行く。
なので、子クラスからも必ず同じ static property を参照したい場合は、self ではなく parent を指定しておくと良さそう。(static_property_parent.php)

 

コード見た方が早いと思うので、サンプル貼っておきますね。

object が同一かどうかはspl_object_hashで見ているhttp://php.ne ...

メソッドの呼び出し方

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: ビット演算子 - Manual

どうしてこうなるんだよ!!!!!!

じゃあ、文字列同士じゃない場合は?

試してみた。

<?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のアソシエイト辺りは、こういうのを駆使してるのかなー?