EventSubscriberの活用事例


## はじめに

業務システムなどを開発していると、どうしてもお客様ごとに処理を分岐せざるをえない状況になることがあると思います。
今回はお客様ごとのEventSubscriberを作ることで、疎結合なままお客様ごとの要件に対応した、という事例を紹介します。

## EventDispatcher component
まずは、symfony.comの[EventDispatcher component](http://symfony.com/doc/current/components/event_dispatcher/index.html)を一通り読んでおきます。

その上で、今回やりたいのは、

- CustomなEventSubscriberを生成し、サービスコンテナに登録
- Eventオブジェクトを生成、Eventをdispatch

ですので、順を追って紹介していきます。

## CustomなEventSubscriberを生成し、サービスコンテナに登録

顧客環境ごとに名前空間が分かれていて、その配下に'EventSubscriber'というクラスが存在すれば、'custom_event_subscriber'サービスとして登録します。


```php
// Silexの場合
// CustomEventServiceProvider
public function register(Application $app) {
    
    $app['custom_event_subscriber'] = $app->share(function($app){
        
        $subscriber = null;
        $customerId = 'someCustomer';
        $className = '\\(namespace)\\'. ucfirst($customerId). '\\EventSubscriber';
        if (class_exists($className)) {
            $subscriber = new $className($app);
        }
        
        return $subscriber;
    });
}
```

## Eventオブジェクトを生成、Eventをdispatch

顧客ごとの独自なバリデーションを実装する場合、共通のバリデーション処理の後に、下記のようにeventをdispatchします。


```php
if ($app['custom_event_subscriber'] instanceof EventSubscriberInterface) {
    $subscriber = $app['custom_event_subscriber'];
    if (isset($subscriber->getSubscribedEvents()['form.validation'])) {
        $event = new ValidationEvent($request);
        $dispacher = new EventDispatcher();
        $dispacher->addSubscriber($subscriber);
        $dispacher->dispatch('form.validation', $event);
        // カスタムバリデーションにてエラーがあれば、共通のエラーオブジェクトにマージする
        if (!$event->getIsValid()) {
            $errors = array_merge($errors, $event->getErrors());
        }
    }
}
```


```php
// ValidationEvent.php
use Symfony\Component\EventDispatcher\Event;

class ValidationEvent extends Event {

    private $postData;
    private $isValid;
    private $errors;
    
    public function __construct($postData) {
        
        $this->setPostData($postData);
        $this->setIsValid(false);
    }
    
    public function getPostData() {
        return $this->postData;
    }
    
    public function setPostData($postData) {
        $this->postData = $postData;
    }

    public function getIsValid() {
        return $this->isValid;
    }
    
    public function setIsValid($isValid) {
        $this->isValid = $isValid;
    }

    public function getErrors() {
        return $this->errors;
    }

    public function setErrors($errors) {
        $this->errors = $errors;
    }
}
```


```php
// EventSubscriber.php
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class EventSubscriber extends EventSubscriberInterface {
    
    public static function getSubscribedEvents() {
        
        return [
            'form.validation' => [
                ['onIsValid', 0],
            ]
        ];
    }

    public function onIsValid(ValidationEvent $event) {

        $posts = $event->getPostData();

        $errors = $this->validate($posts);
        $isValid = empty($errors);
        if (!$isValid) {
            $event->setErrors($errors);
        }
        $event->setIsValid($isValid);
    }

    private function validate($posts) {
        // do something...
        return $errors;
    }
}
```

## 参考までに
あらためて、本家のドキュメントやソースコードを見てみると、理解が深まります。
[The HttpKernel Component - symfony.com](http://symfony.com/doc/current/components/http_kernel/introduction.html)
[KernelEvents -api.symfony.com](http://api.symfony.com/2.6/Symfony/Component/HttpKernel/KernelEvents.html)


## おわりに
顧客ごとのEventSubscriberを導入してみて良かったなと思うことは、

- 顧客ごとの独自な要件が出てきたときの指針が出来たので、エンジニアが迷わなくて済む

- レアケースのために、余計な設定や分岐が増えなくて済む

などが挙げられます。
他にこんないい方法があるよという方は、コメントいただけると幸いです。

最近は、顧客ごとにBundleを作るぐらいガッツリやったほうがいいのか、Actionクラスを作って、細かく分けてRouting含めて組み合わせられるようにしたほうがいいのか、など、どうやったら顧客ごとのわがままに応えられるか日々悩んでおります。