【问题标题】:A Generic, catch-all action in Zend Framework... can it be done?Zend 框架中的一个通用的、包罗万象的动作……可以做到吗?
【发布时间】:2013-02-10 22:24:01
【问题描述】:

这种情况是因为有人想在他们的网站上创建自己的“页面”,而不必创建相应的操作。

假设他们有一个类似 mysite.com/index/books 的 URL...他们希望能够创建 mysite.com/index/booksmore 或 mysite.com/index/pancakes 但不必在其中创建任何操作索引控制器。他们(可以做简单 html 的非技术人员)基本上想要创建一个简单的静态页面,而无需使用操作。

就像在索引控制器中会有一些通用操作来处理对不存在操作的请求。您如何做到这一点,甚至有可能吗?

编辑: 使用 __call 的一个问题是缺少视图文件。缺少动作变得没有意义,但现在您必须处理丢失的视图文件。如果找不到异常,框架将抛出​​异常(尽管如果有办法让它重定向到丢失视图文件上的 404 __call 是可行的。)

【问题讨论】:

    标签: php zend-framework zend-framework-mvc


    【解决方案1】:

    使用神奇的__call 方法可以正常工作,您所要做的就是检查视图文件是否存在,如果不存在则抛出正确的异常(或执行其他操作)。

    public function __call($methodName, $params)
    {
        // An action method is called
        if ('Action' == substr($methodName, -6)) {
            $action = substr($methodName, 0, -6);
    
            // We want to render scripts in the index directory, right?
            $script = 'index/' . $action . '.' . $this->viewSuffix;
    
            // Script file does not exist, throw exception that will render /error/error.phtml in 404 context
            if (false === $this->view->getScriptPath($script)) {
                require_once 'Zend/Controller/Action/Exception.php';
                throw new Zend_Controller_Action_Exception(
                    sprintf('Page "%s" does not exist.', $action), 404);
            }
    
            $this->renderScript($script);
        }
    
        // no action is called? Let the parent __call handle things.
        else {
            parent::__call($methodName, $params);
        }
    }
    

    【讨论】:

    • 这种方法对我来说非常有效,没有使用文件验证,但捕获了我需要的一切,谢谢分享! :)
    • 只是要补充一点,这对我来说也可以作为创建全局或通配符操作的一种方式。
    【解决方案2】:

    你必须玩路由器 http://framework.zend.com/manual/en/zend.controller.router.html

    我认为您可以指定一个通配符来捕获特定模块上的每个操作(默认为减少 url)并定义一个操作来根据 url 渲染视图(甚至调用操作)

    new Zend_Controller_Router_Route('index/*', 
    array('controller' => 'index', 'action' => 'custom', 'module'=>'index') 
    

    在您的 customAction 函数中,只需检索参数并显示正确的块。 我还没有尝试过,所以你可能需要稍微破解一下代码

    【讨论】:

    • 这解决了我写的问题。不过,我搞砸了,因为实际上存在正在使用的现有操作,并且此通配符方法强制所有操作使用“自定义”操作。清楚吗?
    【解决方案3】:

    如果你想使用 gabriel1836 的 _call() 方法,你应该能够禁用布局和视图,然后渲染任何你想要的。

    $this->_helper->layout()->disableLayout();
    $this->_helper->viewRenderer->setNoRender(true);
    

    【讨论】:

      【解决方案4】:

      我需要让现有的模块/控制器/动作在 Zend Framework 应用程序中正常工作,但随后有一个包罗万象的路由将任何未知的内容发送到 PageController 可以从数据库表中挑选用户指定的 url 并显示页面.我不想在用户指定的 url 前面有一个控制器名称。我希望 /my/custom/url 而不是 /page/my/custom/url 通过 PageController。所以上述解决方案都不适合我。

      我最终扩展了 Zend_Controller_Router_Route_Module:使用几乎所有的默认行为,并且只是稍微调整了控制器名称,因此如果控制器文件存在,我们将正常路由到它。如果它不存在,那么 url 一定是一个奇怪的自定义 URL,所以它被发送到 PageController,整个 url 作为参数保持不变。

      class UDC_Controller_Router_Route_Catchall extends Zend_Controller_Router_Route_Module
      {
          private $_catchallController = 'page';
          private $_catchallAction     = 'index';
          private $_paramName          = 'name';
      
          //-------------------------------------------------------------------------
          /*! \brief takes most of the default behaviour from Zend_Controller_Router_Route_Module
              with the following changes:
              - if the path includes a valid module, then use it
              - if the path includes a valid controller (file_exists) then use that
                  - otherwise use the catchall
          */
          public function match($path, $partial = false)
          {
              $this->_setRequestKeys();
      
              $values = array();
              $params = array();
      
              if (!$partial) {
                  $path = trim($path, self::URI_DELIMITER);
              } else {
                  $matchedPath = $path;
              }
      
              if ($path != '') {
                  $path = explode(self::URI_DELIMITER, $path);
      
                  if ($this->_dispatcher && $this->_dispatcher->isValidModule($path[0])) {
                      $values[$this->_moduleKey] = array_shift($path);
                      $this->_moduleValid = true;
                  }
      
                  if (count($path) && !empty($path[0])) {
                      $module = $this->_moduleValid ? $values[$this->_moduleKey] : $this->_defaults[$this->_moduleKey];
                      $file = $this->_dispatcher->getControllerDirectory( $module ) . '/' . $this->_dispatcher->formatControllerName( $path[0] ) . '.php'; 
                      if (file_exists( $file ))
                      {
                          $values[$this->_controllerKey] = array_shift($path);
                      }
                      else
                      {
                          $values[$this->_controllerKey] = $this->_catchallController;
                          $values[$this->_actionKey] = $this->_catchallAction;
                          $params[$this->_paramName] = join( self::URI_DELIMITER, $path );
                          $path = array();
                      }
                  }
      
                  if (count($path) && !empty($path[0])) {
                      $values[$this->_actionKey] = array_shift($path);
                  }
      
                  if ($numSegs = count($path)) {
                      for ($i = 0; $i < $numSegs; $i = $i + 2) {
                          $key = urldecode($path[$i]);
                          $val = isset($path[$i + 1]) ? urldecode($path[$i + 1]) : null;
                          $params[$key] = (isset($params[$key]) ? (array_merge((array) $params[$key], array($val))): $val);
                      }
                  }
              }
      
              if ($partial) {
                  $this->setMatchedPath($matchedPath);
              }
      
              $this->_values = $values + $params;
      
              return $this->_values + $this->_defaults;
          }
      }
      

      所以我的 MemberController 可以作为 /member/login、/member/preferences 等正常工作,并且可以随意添加其他控制器。仍然需要 ErrorController:它会捕获现有控制器上的无效操作。

      【讨论】:

      • 这个解决方案对我来说很棒。我只需要复制 getInstance() 方法,因为没有在新实例上设置对调度程序的所需引用。用法:$catchAll = UDC_Controller_Router_Route_Catchall::getInstance(Zend_Registry::get('Config')); $router->addRoute('default',$catchAll);
      【解决方案5】:

      我通过重写 dispatch 方法并处理未找到操作时引发的异常来实现一个包罗万象:

      public function dispatch($action)
      {
          try {
              parent::dispatch($action);
          }
          catch (Zend_Controller_Action_Exception $e) {
              $uristub = $this->getRequest()->getActionName();
              $this->getRequest()->setActionName('index');
              $this->getRequest()->setParam('uristub', $uristub);
              parent::dispatch('indexAction');
          }
      }
      

      【讨论】:

        【解决方案6】:

        您可以使用神奇的__call() 函数。例如:

        public function __call($name, $arguments) 
        {
             // Render Simple HTML View
        }
        

        【讨论】:

        • 这个解决方案的一个问题是每个动作都需要一个对应的视图文件。因此,如果有人试图访问一个不存在且没有视图文件的操作,则会出现错误(而不是 404)。
        【解决方案7】:

        stunti 的建议是我采用的方式。我的特定解决方案如下(这使用您指定的任何控制器的 indexAction()。在我的情况下,每个操作都使用 indexAction 并根据 url 从数据库中提取内容):

        1. 获取路由器的实例(一切都在您的引导文件中,顺便说一句):

          $router = $frontController->getRouter();

        2. 创建自定义路由:

          $router->addRoute('controllername', new Zend_Controller_Router_Route('controllername/*', array('controller'=>'controllername')));

        3. 将新路由传递给前端控制器:

          $frontController->setRouter($router);

        我没有使用 gabriel 的 __call 方法(只要您不需要视图文件,它就适用于缺少的方法),因为这仍然会引发有关缺少相应视图文件的错误。

        【讨论】:

          【解决方案8】:

          为了将来参考,基于 gabriel1836 和 ejunker 的想法,我挖掘了一个更切中要害的选项(并支持 MVC 范式)。此外,“使用专门的视图”比“不使用任何视图”更有意义。

          
          // 1. Catch & process overloaded actions.
          public function __call($name, $arguments)
          {
              // 2. Provide an appropriate renderer.
              $this->_helper->viewRenderer->setRender('overload');
              // 3. Bonus: give your view script a clue about what "action" was requested.
              $this->view->action = $this->getFrontController()->getRequest()->getActionName();
          }
          

          【讨论】:

            【解决方案9】:

            @Steve 如上所述 - 您的解决方案对我来说听起来很理想,但我不确定您是如何在引导程序中实现它的?

            【讨论】:

              猜你喜欢
              • 2011-05-03
              • 2023-01-19
              • 1970-01-01
              • 1970-01-01
              • 2014-08-11
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多