Laravelの依存ライブラリでdoctrine/commonがあり、
annotationも一緒についてくるので
遊び半分でLaravelでアノテーションを使えるように実装してみました。
もしvendor下に無ければ、
"doctrine/annotations": "~1.0"
を追記してください。
今回はdoctrine/annotationsの使用方法で、
海外のannotation実装のサンプルをそのままやってみます
/**
* @AnnotatedDescription(value="hello annotation")
*/
public function __construct()
{
}
ちょっとあれですがこんな感じになります。
シンプルな感じで、下記のようにしました
<?php
namespace Acme\Annotations;
/**
* @Annotation
* @Target("ALL")
*/
class AnnotatedDescription implements AnnotationInterface
{
public $value;
public $type;
public $desc;
}
@Tragetはdoctrine/annotationsの指定方法で、
この場合はCLASSでもMETHODでもプロパティでもどこでも使えるアノテーションだよ!という指定になります。
詳しくはリファレンスやsymfony等を覗いてみるといいかもしれません。
doctrine/annotations
作ったアノテーションを実際に使用できるようにします。
namespace Acme\Annotations;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
class AnnotationManager
{
/** @var AnnotationReader */
protected $reader;
/**
* @param AnnotationReader $reader
*/
public function __construct(AnnotationReader $reader)
{
$this->reader = $reader;
}
/**
* @return void
*/
public function reader()
{
$loader = require base_path().'/vendor/autoload.php';
AnnotationRegistry::registerLoader([$loader, 'loadClass']);
$reflectionClass = new \ReflectionClass('Acme\Controllers\HomeController');
$annotate = $this->reader->getClassAnnotations($reflectionClass);
var_dump($annotate);
}
annotation実装時にディレクトリ等がどこにあっても登録できるようにしました。
Acme\Controllers\HomeController 固定にして動作確認します。
リフレクションを使って調べます。そんなに難しい仕組みは特にありません。
namespace Acme\Providers;
use Illuminate\Support\ServiceProvider;
class AnnotationServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
* @return void
*/
public function register()
{
$this->app->make('Acme\Annotations\AnnotationManager',
[
$this->app->make('Doctrine\Common\Annotations\AnnotationReader')
])->reader();
がっちり実装する場合や、パッケージしたいときはinterfaceを作ってあげるといいかもしれません。
php6くらいでもphpにもannotationが実装されるかもしれません。
Acme\Providers\AnnotationServiceProvider を config/app.phpに追記して早速使ってみます。
namespace Acme\Controllers;
use Acme\Annotations\AnnotatedDescription;
/**
* Class HomeController
* @package Acme\Controllers
* @AnnotatedDescription("hello")
*/
class HomeController extends \Acme\Controllers\BaseController
{
/**
* @AnnotatedDescription(value="hello method", type="text", desc="hoge")
*/
public function getIndex()
{
}
}
まだメソッドには実装してませんが、後でメソッドにも実装するため先に書いてしまいました。
早速コントローラーにアクセスしてみましょう。
AnnotationManager内でdumpすると、クラスで指定したannotationが取得できているのがわかると思います。
メソッドでも使用できるように実装します。
/**
* @return void
*/
public function reader()
{
$loader = require base_path().'/vendor/autoload.php';
AnnotationRegistry::registerLoader([$loader, 'loadClass']);
$reflectionClass = new \ReflectionClass('Acme\Controllers\HomeController');
$annotate = $this->reader->getClassAnnotations($reflectionClass);
var_dump($annotate);
foreach($reflectionClass->getMethods() as $reflectionMethod)
{
$reflectionMethod = new \ReflectionMethod($reflectionClass->name, $reflectionMethod->name);
$methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
var_dump($methodAnnotations);
}
'Acme\Controllers\HomeController'クラス内のメソッドをすべて調べて、取得するようにしました。
再度コントローラーにアクセスするとメソッドに記述したannotationも取得できているはずです。
これでクラス、メソッドの値が取得できるようになりました。
同じようにプロパティにも指定できますので、同じように追記していきます。
クラス決めうちで動作確認できたので、今度はプロジェクト全体で使用できるようにします。
これについてはもうちょっといい方法があるのかもしれませんが、
今回はライブラリを使って実装します。
"require": {
"laravel/framework": "4.1.*",
"andrewsville/php-token-reflection": "1.*",
},
php-token-reflectionを使って
プロジェクトのディレクトリをすべて調べリフレクションで取得するようにしてみました。
実装方法はいくつかあると思いますので、
ソースコードやリファレンスを見ながら実装してみると良いかもしれません。
名前空間を対象にしたりして調べることができます。
とりあえずAnnotationManager.phpに追記しました。
namespace Acme\Providers;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use TokenReflection\Broker;
class AnnotationManager
{
/** @var AnnotationReader */
protected $reader;
/**
* @param AnnotationReader $reader
*/
public function __construct(AnnotationReader $reader)
{
$this->reader = $reader;
}
/**
* @return void
*/
public function reader()
{
$loader = require base_path().'/vendor/autoload.php';
AnnotationRegistry::registerLoader([$loader, 'loadClass']);
$broker = new Broker(new Broker\Backend\Memory());
$broker->processDirectory(app_path());
$files = $broker->getFiles();
foreach($files as $file)
{
$namespaces = $file->getNamespaces();
foreach($namespaces as $namespace)
{
// namespaceで取得してリフレクションでそれぞれを調べる処理
}
}
}
app_path()でapp/のパスを取得できるので、
app/ 配下を対象にファイルを取得して、その中身を調べるようにしました。
実装方法は色々あると思いますので、他にもっと良いものがあればそれを使って実装すると良いと思います!
PHP-Token-Reflectionを使ってapp/配下すべてを対象にしたので、
どこに記載されていようとすべて取得することができるようになりました。
あとはそれぞれ欲しい機能を実装すると良いかもしれません。
多分もうちょっと良い実装方法がありそうなので、
もっといい方法があれば是非パッケージとして作ってもらえればなと思います(他力本願)
今回はannotationを実装するところまでですので、
もうちょっと押し進めれば、フィールドインジェクション等実装できるはずです。
是非どうぞ