🐘
[PHP] privateなプロパティをオーバーライドするときはgetter/setterも子クラスに書かないとハマるという話
PHP歴8年にもなるのに今さらこんなのでめっちゃハマってしまったので反省文です😵
OKパターン
まずはこちらをご覧ください。
<?php
// test.php
class Base
{
private $privateProperty = 'base';
protected $protectedProperty = 'base';
public $publicProperty = 'base';
private function privateMethod() { return 'base'; }
protected function protectedMethod() { return 'base'; }
public function publicMethod() { return 'base'; }
}
class Child extends Base
{
private $privateProperty = 'child';
protected $protectedProperty = 'child';
public $publicProperty = 'child';
private function privateMethod() { return 'child'; }
protected function protectedMethod() { return 'child'; }
public function publicMethod() { return 'child'; }
public function __get($name) { return $this->$name; }
public function __call($name, $arguments) { return $this->$name(); }
}
$child = new Child();
echo $child->privateProperty.PHP_EOL;
echo $child->protectedProperty.PHP_EOL;
echo $child->publicProperty.PHP_EOL;
echo '---'.PHP_EOL;
echo $child->privateMethod().PHP_EOL;
echo $child->protectedMethod().PHP_EOL;
echo $child->publicMethod().PHP_EOL;
このコードの実行結果は以下のようになります。
$ php test.php
child
child
child
---
child
child
child
プロパティもメソッドもすべて子クラスでオーバーライドしているので、当然の結果ですね。
NGパターン
では今度は __get()
__call()
マジックメソッドを親に移動させてみましょう。
<?php
// test.php
class Base
{
private $privateProperty = 'base';
protected $protectedProperty = 'base';
public $publicProperty = 'base';
private function privateMethod() { return 'base'; }
protected function protectedMethod() { return 'base'; }
public function publicMethod() { return 'base'; }
+
+ public function __get($name) { return $this->$name; }
+ public function __call($name, $arguments) { return $this->$name(); }
}
class Child extends Base
{
private $privateProperty = 'child';
protected $protectedProperty = 'child';
public $publicProperty = 'child';
private function privateMethod() { return 'child'; }
protected function protectedMethod() { return 'child'; }
public function publicMethod() { return 'child'; }
-
- public function __get($name) { return $this->$name; }
- public function __call($name, $arguments) { return $this->$name(); }
}
$child = new Child();
echo $child->privateProperty.PHP_EOL;
echo $child->protectedProperty.PHP_EOL;
echo $child->publicProperty.PHP_EOL;
echo '---'.PHP_EOL;
echo $child->privateMethod().PHP_EOL;
echo $child->protectedMethod().PHP_EOL;
echo $child->publicMethod().PHP_EOL;
実行結果はこうなります。
$ php test.php
base
child
child
---
base
child
child
privateプロパティとprivateメソッドだけ、親の持つ値が出力されました😵
原因(ものすごく当たり前の話ですが)
__get()
が親にある場合、例えば $child->privateProperty
にアクセスしたときの処理の流れは以下のようになります。
まったく厳密ではありません。あくまでイメージです🙏
-
Child::privateProperty
は存在するけどprivateなので、__get()
が探される -
Child::__get()
は存在しないのでBase::__get()
が呼ばれる -
Base::__get()
からChild::privateProperty
は取得できない -
Base::privateProperty
の値が取得される
一方、 $child->protectedProperty
にアクセスしたときはというと、
-
Child::protectedProperty
は存在するけどprotectedなので、__get()
が探される -
Child::__get()
は存在しないのでBase::__get()
が呼ばれる -
Base::__get()
からChild::protectedProperty
が取得できる(protected以上なので) -
Child::protectedProperty
の値が取得される
となります。
これが原因です。メソッドアクセスの場合もまったく同じ理屈ですね。
実務でハマりそうなケース
- 複数の派生クラスがあり
- privateプロパティのオーバーライドを使っていて
- 面倒なのでgetter/setterを基底クラスに書いちゃう
ということをするとこの問題が発生します。
NGパターン
class Base
{
private $name = 'base';
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}
class Child1
{
private $name = 'child1';
}
class Child2
{
private $name = 'child2';
}
ついやっちゃいそうじゃないですか?😓
OKパターン1
privateのままオーバーライドするなら、getter/setterはちゃんと子クラスに移しましょう。
class Base
{
private $name = 'base';
}
class Child1
{
private $name = 'child1';
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}
class Child2
{
private $name = 'child2';
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}
OKパターン2
あるいは、getter/setterを親に持たせておきたいなら、privateではなくprotectedにしましょう。
class Base
{
protected $name = 'base';
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}
class Child1
{
protected $name = 'child1';
}
class Child2
{
protected $name = 'child2';
}
まとめ
当たり前ですが、privateプロパティやprivateメソッドには親自身からしかアクセスできないということを改めて脳に刻み込んでおきましょう…😓
Discussion