オーバーロードされたプロパティと emtpy() 〜 nagoya.php vol.2 での話
http://blog.ohgaki.net/php-5-4-accessor-php にある trait を使ったアクセサのサンプルコード(微妙にいじってますが、ロジックは全く同じです)。
<?php // Example code how to eliminate getter and setter methods // with traits introduced from PHP 5.4 trait Accessors { public function __get($name) { if ($this->r_property[$name]) { return $this->$name; } else { trigger_error("Access to read protected property"); } } public function __set($name, $value) { if ($this->w_property[$name]) { $this->$name = $value; } else { trigger_error("Access to write protected property"); } } } class OrderLine { use Accessors; private $r_property = array('price' => 1, 'amount' => 1); private $w_property = array('price' => 0, 'amount' => 1); protected $price = 100; private $amount; private $tax = 1.15; // property without getter/setter public function getTotal() { return $this->price * $this->amount * $this->tax; } } $line = new OrderLine; //$line->price = 20; // Notice error $line->amount = 3; echo "Total cost: ".$line->getTotal(), PHP_EOL;
で、上記のようなコードで、オーバーロードされたプロパティを emtpy() したら true が返ってハマったというのが、nagoya.php での話でした。
var_dump(empty($line->amount)); // true
これは、PHP の仕様です。
PHP マニュアルの「注意」
ここで、以下のような注意が PHP にマニュアルにありました。
注意:
オーバーロードされたプロパティを、 isset() 以外の言語構造の中で使うことはできません。 つまり、オーバーロードされたプロパティに対して empty() がコールされたとしても、オーバーロードされたメソッドはコールされないということです。
この制約を回避するには、オーバーロードされたプロパティを そのスコープのローカル変数にコピーしてから empty() に渡さなければなりません。
http://www.php.net/manual/ja/language.oop5.overloading.php
これ、何なんでしょう?
「オーバーロードされたプロパティを、 isset() 以外の言語構造の中で使うことはできません」→ isset() なら使えるの?使えないですよね。
var_dump(isset($line->amount)); // false
empty() と isset() どちらも同じようにオーバーロードされたプロパティでは機能しません。
ということで、この「注意」はちょっとよくわかりません。ただの間違い?
あと、「この制約を回避するには、オーバーロードされたプロパティを そのスコープのローカル変数にコピーしてから empty() に渡さなければなりません」→ つまり、empty() 使いたかったら、こうしろと。
$amount = $line->amount; var_dump(empty($amount)); // false
しかし、そんなことを強制するのは、正直、無理っぽいですよね。
どうすればいいのか?
__isset() を定義すればいいと思うよ。
<?php // Example code how to eliminate getter and setter methods // with traits introduced from PHP 5.4 trait Accessors { public function __get($name) { echo '__get():'; if ($this->r_property[$name]) { return $this->$name; } else { trigger_error("Access to read protected property"); } } public function __set($name, $value) { echo '__set():'; if ($this->w_property[$name]) { $this->$name = $value; } else { trigger_error("Access to write protected property"); } } public function __isset($name) // これです { echo '__isset():'; if ($this->r_property[$name] === 1) { return true; } else { return false; } } } class OrderLine { use Accessors; private $r_property = array('price' => 1, 'amount' => 1); private $w_property = array('price' => 0, 'amount' => 1); protected $price = 100; private $amount; private $tax = 1.15; // property without getter/setter public function getTotal() { return $this->price * $this->amount * $this->tax; } } $line = new OrderLine; //$line->price = 20; // Notice error $line->amount = 3; echo "Total cost: ".$line->getTotal(), PHP_EOL; var_dump(isset($line->amount)); var_dump(empty($line->amount));
そうすると、結果は、以下のようになります。
__set():Total cost: 345 __isset():bool(true) __isset():__get():bool(false)
ちなみに、この話、trait は関係ありません。