他にも何とでもやりようはありそうだけど、とりあえず思いついた方法で実装してみた。
ちなみにここでパッケージプライベートというのは同じ名前空間からのみアクセスできるという意味で言っている。
参考にしたのは以下の記事である。
<?php namespace { class PackagePrivateImplementor { private $nameSpaceName; public function __construct($nameSpaceName) { $this->nameSpaceName = $nameSpaceName; } function __call($name, $arguments) { if (!method_exists($this, '_' . $name)) { throw new \Exception('No such method: ' . $name); } $backTrace = debug_backtrace(); if (count($backTrace) < 3) { // main からの呼び出し throw new \Exception('Cannot invoke package private method from main!'); } else { $callerTrace = $backTrace[2]; } $name = '_' . $name; if (array_key_exists('class', $callerTrace)) { $caller = new \ReflectionClass($callerTrace['class']); } else if (array_key_exists('function', $callerTrace)) { $caller = new \ReflectionFunction($callerTrace['function']); } else { return; // ここに到達することはないはず... } $callerNamespaceName = $caller->getNamespaceName(); if ($callerNamespaceName != $this->nameSpaceName) { // 呼び出し元が呼び出し先と同じ名前空間の場合のみ呼べる throw new \Exception('Cannot invoke package private method from other package method!'); } $calleeMethod = new \ReflectionMethod($this, $name); $calleeMethod->setAccessible(true); $calleeMethod->invokeArgs($this, $arguments); $calleeMethod->setAccessible(false); } } } namespace foo\bar { class Loneliness extends \PackagePrivateImplementor { public function __construct() { parent::__construct(__NAMESPACE__); } private function _packagePrivateMethod($arg1, $arg2) { echo sprintf('This is package private method : arg1=%s, arg2=%s', $arg1, $arg2) . PHP_EOL; } } class Friend { static function invokePackagePrivateMethod($arg1, $arg2) { $loneliness = new Loneliness(); $loneliness->packagePrivateMethod($arg1, $arg2); } } function invokePackagePrivateMethod($arg1, $arg2) { $loneliness = new Loneliness(); $loneliness->packagePrivateMethod($arg1, $arg2); } } namespace foo\baz { class NotFriend { static function invokePackagePrivateMethod($arg1, $arg2) { $loneliness = new \foo\bar\Loneliness(); $loneliness->packagePrivateMethod($arg1, $arg2); } } function invokePackagePrivateMethod($arg1, $arg2) { $loneliness = new \foo\bar\Loneliness(); $loneliness->packagePrivateMethod($arg1, $arg2); } } namespace { // 同じ名前空間の別のメソッドから呼ぶ foo\bar\Friend::invokePackagePrivateMethod(11, 12); // 同じ名前空間の別の関数から呼ぶ foo\bar\invokePackagePrivateMethod(21, 22); // 別の名前空間の別のメソッドから呼ぶ try { foo\baz\NotFriend::invokePackagePrivateMethod(31, 32); } catch (Exception $e) { echo $e->getMessage() . PHP_EOL; } // 別の名前空間の別の関数から呼ぶ try { foo\baz\invokePackagePrivateMethod(41, 42); } catch (Exception $e) { echo $e->getMessage() . PHP_EOL; } // main から呼ぶ try { $loneliness = new \foo\bar\Loneliness(); $loneliness->packagePrivateMethod(51, 52); } catch (Exception $e) { echo $e->getMessage() . PHP_EOL; } }
実行結果は以下のとおりである。
This is package private method : arg1=11, arg2=12 This is package private method : arg1=21, arg2=22 Cannot invoke package private method from other package method! Cannot invoke package private method from other package method! Cannot invoke package private method from main!
動作の説明と考察みたいなものを以下にまとめる。
- 今回の例ではアンスコつきのメソッド名でパッケージプライベートにしたいメソッドを定義し、呼び出される時はアンスコなしのメソッド名で呼ぶというルールにしている。
public function packagePrivateMethod($arg1, $arg2) { $this->verifyAndInvoke(func_get_args()); }
- debug_backtrace() からコールスタックを取得する。
- コールスタックはざっくり言うと以下のようになっている。
添字 | コンテキスト |
---|---|
0 | __call |
1 | packagePrivateMethod |
2 | 呼び出し元 |