マジで?って思ったので
皆さんこんにちは
PHPで実装するとき、当然のようにメソッドにアクセス権をつけるわけです。
公開するpublic
, 自身、および継承先の中でのみ使用できるprotected
, 自身の中でしか使えないprivate
...。
今更何いってんだって思うかもしれんのですが、このアクセス権がいい感じに作用しないケースがあったので、簡単にまとめます。
名前のセンスとか、気にしちゃだめよ?
バージョン
PHP 7.2.4 です。
trait
traitはコードを再利用する仕組みの一つです。
個人的なイメージは「特定の処理のまとまりをお手軽に導入する仕組み」となっています。
使い方は簡単で
<?php
namespace Niisan;
trait Operation
{
public function go() :string
{
return 'go';
}
}
というtraitを作ると
<?php
namespace Niisan;
class User
{
use Operation;
}
みたいなクラスを作ったときに
<?php
require(__DIR__ . '/../vendor/autoload.php');
$user = new Niisan\User;
echo isOk($user->go() === 'go');
function isOk($state)
{
if ($state) return "0K\n";
return "NG\n";
}
を動かすと
$ php test.php
OK
のように実行できます。
つまり、Operation
trait にあったコードがそのままUser
クラスで使用できるということです。
trait のアクセス権検証
外部からの使用
次に、traitにアクセス権を設定してみましょう。
<?php
namespace Niisan;
trait Operation
{
public function go() :string
{
return 'go';
}
protected function wait() : string
{
return 'wait';
}
private function stop() :string
{
return 'stop';
}
}
こいつを普通に使ってみます。
echo isOk($user->wait() === 'wait');
でも、これはアクセス権により弾かれます。
Uncaught Error: Call to protected method Niisan\User::wait()
当たり前ですね。
当然、private メソッドでも同じです。
echo isOk($user->stop() === 'stop');
Uncaught Error: Call to private method Niisan\User::stop()
アクセス権は効いているようです。
内部からの使用
念の為内部から使うことを考えてみます。
まず、以下のようなメソッドを定義して、内部から使用する仕組みを作ります。
<?php
namespace Niisan;
class User
{
use Operation;
public function operate(int $num)
{
if ($num == 1) return $this->go();
if ($num == 2) return $this->wait();
return $this->stop();
}
}
テストはこんな感じで作ります。
echo isOk($user->operate(1) == 'go');
echo isOk($user->operate(2) == 'wait');
echo isOk($user->operate(3) == 'stop');
すると、こんな感じになります。
# php test.php
0K
0K
0K
いや、待って、privateで落ちてくれないの?
とりあえず、trait で private にしたメソッドは、そのtraitを使用しているクラス内では、普通に参照できるっぽいです。
継承先での使用
継承先で使用することを考えてみます。
<?php
namespace Niisan;
class Boss extends User
{
public function operate(int $num)
{
if ($num == 1) return $this->go();
if ($num == 2) return $this->wait();
return $this->stop();
}
}
テストはこんな感じ
$boss = new Niisan\Boss;
echo isOk($boss->operate(1) == 'go');
echo isOk($boss->operate(2) == 'wait');
echo isOk($boss->operate(3) == 'stop');
# php test.php
0K
0K
Fatal error: Uncaught Error: Call to private method Niisan\User::stop() from context 'Niisan\Boss' in /var/www/src/Boss.php:10
エラー出ました。
結論
trait は単純に継承の代わりをしているわけではなく、
「trait の中にある内容を、クラス定義に書き込んでいるのと同等」
であるということらしいです。
なので、trait のなかで private 定義しても、使用しているクラス上では普通に使用できます。
まとめ
というわけで、trait のアクセス権の挙動について、少し調べました。
いや、コードレビューのときに、trait の中のprivate メソッド使っているのテスト通っていたので、なんでやろうなぁ?って思っただけなんです。