【composer】アノテーションでfriendとかpackage-privateとかをエミュレートするライブラリを作った
- 業務でcomposerライブラリを作りそうだったので練習がてら
- まだまだ開発途上だが、とりあえずデモできる段階になった
WandTa/Annotation-Visibility
composer require wand/annotation-visibility
-
‘19/07/25現在、dev-master
- composer.jsonに
"minimum-stability": "dev"
が必要
- composer.jsonに
Objective: 非標準のアクセス制御を使いたい
-
デザインパターン等を学ぶと…
- 特定のクラスからのみ呼び出し可能なメソッドがほしい
- 同一パッケージのクラスからのみ呼び出し可能なメソッドがほしい
-
例
-
しかしPHPで使用可能なアクセス修飾子は下記の3つのみ
-
public
- フルオープン
-
protected
- 派生クラスにのみオープン
-
private
- 自分のクラスのみアクセス可能
-
-
自分のクラス以外にアクセスを許そうとしたら、publicにせざるをえない
- 「振る舞いに関するデザインパターン」が軒並み残念な感じに
Solution: アノテーションでオレオレアクセス制御子を定義する
Sample 1. Layered Architecture
Domain.php
<?php
namespace App;
use WandTa\Annotations\VisibleTo;
class Domain
{
/**
* @VisibleTo("App\Presentation");
*/
public function someLogic()
{ }
}
Presentation.php
<?php
namespace App;
use WandTa\Container;
class Presentation
{
public function callDomain()
{
$domain = (new Container)->make(Domain::class);
$domain->someLogic(); // OK
}
}
Infrastructure.php
<?php
namespace App;
use WandTa\Container;
class Infrastructure
{
public function callDomain()
{
$domain = (new Container)->make(Domain::class);
$domain->someLogic(); // NG
}
}
index.php
<?php
namespace App;
// OK
(new Presentation)->callDomain();
// Uncaught WandTa\Exceptions\AccessViolationException:
// someLogic can't be called by App\Infrastructure
(new Infrastructure)->callDomain();
Sample 2. Visitor Pattern
<?php
declare(strict_types=1);
namespace Tests\Feature\Sample\VisitorPattern;
use WandTa\Annotations\VisibleTo;
class HelloVisitor implements IVisitor
{
/** @var string */
private $greet;
/**
* @VisibleTo("Tests\Feature\Sample\VisitorPattern\AcceptorA")
* @param AcceptorA $acceptorA
*/
public function visitA(AcceptorA $acceptorA)
{
$this->greet = 'hello A';
}
/**
* @VisibleTo("Tests\Feature\Sample\VisitorPattern\AcceptorB")
* @param AcceptorB $acceptorB
*/
public function visitB(AcceptorB $acceptorB)
{
$this->greet = 'hello B';
}
/**
* get greeting message
* @return string
*/
public function getGreet(): string
{
return $this->greet;
}
}
VisitA
メソッドはTests\Feature\Sample\VisitorPattern\AcceptorA
クラスからのみ呼び出し可能VisitB
メソッドはTests\Feature\Sample\VisitorPattern\AcceptorB
クラスからのみ呼び出し可能getGreet
メソッドは任意のクラスから呼び出し可能(通常のpublic)
使いどころ
- 本番環境で実行時にエラーを出したいわけではない。あくまで開発用
- 静的にエラーが出るのが理想
- 次点で、CIの自動テストでエラーが出てくれることを目的とした
- クラスの依存の向きを取り締まることで秩序をもたらす
Laravelでつかう
- テスト用のサービスプロバイダを作る
php artisan make:Provider TestServiceProvider
app/TestServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Domain\HogeDomain;
use WandTa\Container;
class TestServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
app()->bind(HogeDomain::class, function ($app) {
return (new Container)->make(
HogeDomain::class
);
});
}
}
- テスト環境でのみ上記サービスプロバイダを読み込む
config/app.php
<?php
$config = [
...
// 本番用サービスプロバイダ
'providers' => [
...
],
...
];
// テスト環境でのみTestServiceProviderを読み込む
if (env('APP_ENV') === 'testing') {
$config['providers'][] = App\Providers\TestServiceProvider::class;
}
return $config;
HogeDomain
オブジェクト利用するときは、サービスコンテナから取り出すようにする- テスト環境でのみ「特定のクラス以外から呼び出されたらエラーを吐く」メソッドを利用可能になる