【composer】アノテーションでfriendとかpackage-privateとかをエミュレートするライブラリを作った
- 【composer】アノテーションでfriendとかpackage-privateとかをエミュレートするライブラリを作った
- WandTa/Annotation-Visibility
- Objective: 非標準のアクセス制御を使いたい
- Solution: アノテーションでオレオレアクセス制御子を定義する
- 使いどころ
- Laravelでつかう
【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
- 自分のクラス以外にアクセスを許そうとしたら、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
オブジェクト利用するときは、サービスコンテナから取り出すようにする- テスト環境でのみ「特定のクラス以外から呼び出されたらエラーを吐く」メソッドを利用可能になる