[ZF3] 自作コントローラープラグインの追加方法

コントローラープラグインを利用するとコントローラークラスに簡単に機能を追加することが出来ます。ZendFramework3のコントローラーには最初からプラグインが複数登録されており、よく使う$this->params()や、$this->redirect()もコントローラープラグインです。

今回はコントローラープラグインを作成し、コントローラーに追加してみます。

プラグインクラスの作成

まずはプラグインクラスを作成します。今回は下記の様にログを出力するクラスを作成しました。プラグインクラスは「Zend\Mvc\Controller\Plugin\AbstractPlugin」を継承する必要があります。

<?php
namespace Application\Controller\Plugin;

use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\Log\Logger;
use Zend\Log\Writer;

class Log extends AbstractPlugin
{
    protected $logger = null;

    public function __construct()
    {
        // ロガーを設定
        $this->logger = new Logger;
        $writer = new Writer\Stream('php://output');
        $this->logger->addWriter($writer);
    }

    public function __invoke()
    {
        return $this->logger;
    }
}

このクラスでは__invokeメソッドで、Zend\Log\Loggerのインスタンスを返しています。このようにするとコントローラーから__callメソッドを通してプラグインを呼び出した場合にZend\Log\Loggerの機能を利用できるようになります。

__invokeメソッドを実装していない場合、コントローラーはプラグインクラス自体のインスタンスを返すので、プラグインクラス自体に機能を実装して使用することもできます。(というかこっちがスタンダードな使い方だと思う)

コントローラーに登録

作成したプラグインクラスを登録するにはコントローラー内で下記の様にすれば登録して、使用できるようになります。

<?php
namespace Application\Controller;

use \Zend\Mvc\Controller\AbstractActionController;

class IndexController extends AbstractActionController
{
    public function indexAction()
    {
        // プラグインの登録
        $this->plugins->setInvokableClass('log', Plugin\Log::class);
        
        // 登録したプラグインを使用する(コントローラーの__callメソッドを通す)
        $this->log()->info('informational messages');
        $this->log()->err('error conditions');
        
        // pluginメソッドを使用してプラグインを呼び出すこともできるが
        // プラグイン自体のインスタンスが返ってくるので、今回の例ではエラーになる
        $this->plugin('log')->info('informational messages');
        $this->plugin('log')->err('error conditions');
    }
}

コントローラーのpuluginメソッドを使用してプラグインを使用することも出来ますが、この場合はプラグインクラスの__invokeの有無にかかわらず、プラグインクラス自体のインスタンスが返ってきます。

まあ、「プラグインが返すインスタンスとプラグイン自体のインスタンスを使い分けたい」って場合はほとんど無いと思いますので、コントローラーの__callメソッドを通す呼び出し方に統一したほうが良いでしょう。

この登録例のように、プラグインを使用したい場合に毎回コントローラー内でプラグインを登録するのはナンセンス(だと思う)なので、どのコントローラーでも登録せずに共通で使用できるように、事前に登録しておくように変更します。

事前にプラグインを登録するにはイベントマネージャーを使用します。

<?php

namespace Application;

use Zend\Mvc\MvcEvent;
use Zend\Mvc\Controller\AbstractActionController;

class Module
{
    public function onBootstrap(MvcEvent $event)
    {
        // イベントマネージャー取得
        $eventManager = $event->getApplication()->getEventManager();
        // ディスパッチ時にonDispatchメソッドを実行
        $sharedEventManager = $eventManager->getSharedManager();
        $sharedEventManager->attach(
            AbstractActionController::class,
            MvcEvent::EVENT_DISPATCH,
            [$this, 'onDispatch'],
            100
        );
    }

    public function onDispatch(MvcEvent $event)
    {
        // コントローラーを取得
        $controller = $event->getTarget();
        // プラグインマネージャーを取得
        $pluginManager = $controller->getPluginManager();
        // プラグインを登録
        $pluginManager->setInvokableClass('log', Controller\Plugin\Log::class);
    }
}

この様に事前に登録しておくことで、コントローラー内でいちいち登録しなくても、プラグインが使用できるようになります。

diコンテナを使用したプラグインの作成

プラグイン自体にもdiコンテナを使用して、他の機能を使用したい場合があると思います。そのような場合は、ZendFrameworkの通例に従ってFactoryクラスを作成します。

<?php
namespace Application\Controller\Plugin\Factory;

use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;

class LogFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $name, array $options = null)
    {
        // ロガーの取得
        $logger = $container->get('MyLogger');
        // プラグインクラスの作成
        return new $name($logger);
    }
}

このファクトリークラスではdiコンテナを使用してZend\Log\Loggerを取得するようにしています。

次にプラグインクラス自体を下記の様に変更します。

<?php
namespace Application\Controller\Plugin;

use Zend\Mvc\Controller\Plugin\AbstractPlugin;

class Log extends AbstractPlugin
{
    protected $logger = null;

    public function __construct(Logger $logger)
    {
        // コンストラクタの引数からLoggerを取得するように変更
        $this->logger = $logger;
    }

    public function __invoke()
    {
        return $this->logger;
    }
}

Factoryクラスを使用したコントローラープラグインを登録するにはプラグインマネージャーのsetFactoryメソッドとsetAliasメソッドを使用します。具体的には下記の様にします。

<?php

namespace Application;

use Zend\Mvc\MvcEvent;
use Zend\Mvc\Controller\AbstractActionController;

class Module
{
    public function onBootstrap(MvcEvent $event)
    {
        // イベントマネージャー取得
        $eventManager = $event->getApplication()->getEventManager();
        // ディスパッチ時にonDispatchメソッドを実行
        $sharedEventManager = $eventManager->getSharedManager();
        $sharedEventManager->attach(
            AbstractActionController::class,
            MvcEvent::EVENT_DISPATCH,
            [$this, 'onDispatch'],
            100
        );
    }

    public function onDispatch(MvcEvent $event)
    {
        // コントローラーを取得
        $controller = $event->getTarget();
        // プラグインマネージャーを取得
        $pluginManager = $controller->getPluginManager();
        // プラグインにFactoryクラスを登録
        $pluginManager->setFactory(Controller\Plugin\Log::class, Controller\Plugin\Factory\LogFactory::class);
        // エイリアスを設定
        $pluginManager->setAlias('log', Controller\Plugin\Log::class);
    }
}

この様にすることで、setAliasメソッドで設定した名前で、コントローラープラグインが使用できるようになります。

プラグインクラス内で別のプラグインを使用する方法

プラグインクラス内で別のプラグインを使用したい場合は、setControllerメソッドをプラグインクラスに実装して、コントローラーを取得するようにします。

プラグインマネージャーはプラグイン使用時に、setControllerメソッドを通してプラグインにコントローラーをセットする 様になっています。

<?php
namespace Application\Controller\Plugin;

use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\Stdlib\DispatchableInterface as Dispatchable;

class Log extends AbstractPlugin
{
    protected $logger = null;
    protected $controller = null;

    public function __construct(Logger $logger)
    {
        $this->logger = $logger;
    }

    public function __invoke()
    {
        // 現在のURLをデバッグ出力
        $url = $this->controller->url()->fromRoute();
        $this->logger->debug('[URL]' . $url);
        return $this->logger;
    }

    public function setController(Dispatchable $controller)
    {
        // コントローラーを取得
        $this->controller = $controller;
    }
}
 

setControllerメソッドでコントローラーを取得し、urlプラグインを使用して、このプラグインが呼び出される度に現在のURLがデバッグ出力されるようにしました。

コメントを残す