如何让Phalcon将动作委托给其他控制器?

时间:2015-12-10 13:56:48

标签: php phalcon event-loop

问题

我希望能够编写类似控制器的类,并将它们作为供应商提供给最终应用程序。然后,最终应用程序控制器可以实例化我的类并调用它们的操作方法。

问题是我希望自动调用它们。我必须避免在结束控制器中重复每个动作方法。

第一种方法

我试过这个:

代码

class IndexController
{
    protected $vendorController;

    public function initialize()
    {
        $this->vendorController = new VendorController();
    }

    public function __call($name, $args)
    {
        call_user_func_args([$this->vendorController, $name], $args);
    }
}

VendorController类看起来像这样:

class VendorController 
{
    public function testAction()
    {
        die("this is test action");
    }
}

结果

但它给了我

Action 'test' was not found on handler 'index'

我假设 I've checked Phalcon内部做了一些检查,如下所示:

if (method_exists($controller, $actionName)) { // ...

导致未找到行动。

也许我可以以某种方式覆盖这种行为?我正在使用我自己的调度程序类来扩展Phalcon的本地调度程序,如果这有任何帮助的话。但我试图覆盖像getHandlerName()这样的方法而没有任何成功。

第二种方法

我也试过使用特征:

代码

trait TVendorController
{
    public function testAction()
    {
        die("this is test action");
    }
}

class IndexController
{
    use TVendorController;
}

这种方法有效,但有一个重大缺陷:

缺陷

如果IndexController扩展某种包含BaseControllerinitialize()等方法的beforeExecuteRoute()(通常就是这种情况),那么很容易忘记调用他们。这里的威胁是BaseController方法可以包含例如身份验证逻辑:

trait TVendorController
{
    public function beforeExecuteRoute()
    {
        // silently overrides the BaseController::beforeExecuteRoute()
    }

    public function testAction()
    {
        die("this is test action");
    }
}


abstract class BaseController
{
    public function beforeExecuteRoute()
    {
        // OOPS: this is never called
        $username = $this->session->get('username', null);
        if (!$username) {
            $this->response->redirect('index/login');
        }
    }
}


class IndexController extends BaseController
{
    use TVendorController;
}

问题

如果是第一种方法:如何告诉Phalcon的事件循环该操作实际存在于控制器中?

如果无法完成,那么:

  1. 如何编写我的供应商控制器?
  2. 如何将它们组合到最终应用程序控制器中,以便自动调用操作?

1 个答案:

答案 0 :(得分:3)

您可以尝试使用dispatch:beforeDispatch附件添加EventManager。在dispatch:beforeDispatch时刻,您知道操作名称,但在检查操作是否确实存在之前应该可以命​​中它。

beforeDispatch函数中,您可能需要检查,如果传递的操作名称在控制器内是可调用的,如果没有,您应该能够转发到execvendorAction方法的某个王。

编辑:

我做过这个事件管理:

$di->setShared('dispatcher', function() {

    $dispatcher = new Dispatcher();

    $eventsManager = new EventsManager();

    $eventsManager->attach("dispatch:beforeDispatch", function($event, $dispatcher) {

        $controllerName = $dispatcher->getControllerClass();
        $action = $dispatcher->getActionName();

        if(!method_exists($controllerName, $action . 'Action')) {

            $dispatcher->forward(array(
                // not delivering 'controller' param to make it stay
                // in current one
                'action' => 'vendor',
                'params' => array(
                    'name' => $action,
                    'params' => $dispatcher->getParams()
                )
            ));
        }
    });

    $dispatcher->setEventsManager($eventsManager);
    return $dispatcher;
});

然后,我在controllerBase添加了这样一个动作:

public function vendorAction($name, $arguments) {

    var_dump(array(
        'name' => $name,
        'arguments' => $arguments
    ));

    // $name .= 'Action';
    if(method_exists(array($this->vendorController, $name))) {
        call_user_func_args(array($this->vendorController, $name), $arguments);
    } else {
         // work it out
    }
}

让它运转起来。从controllerBase延伸的每个控制器都将运行您在其中传递的方法vendorController。您将不得不以自己的方式处理notFound异常,因为这是一个非常非标准的实现。

使用经典路由器/调度程序/ Phalcon上的所有内容进行测试。