Zend Framework中的通用,全能行动......可以做到吗?

时间:2009-01-28 01:36:13

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

这种情况源于想要在他们的网站上创建自己的“页面”而不必创建相应操作的人。

所以说他们有像mysite.com/index/books这样的网址...他们希望能够创建mysite.com/index/booksmore或mysite.com/index/pancakes但不必创建任何操作索引控制器。他们(可以做简单html的非技术人员)基本上想要创建一个简单的静态页面,而不必使用动作。

就像在索引控制器中会有一些通用操作来处理对不存在的操作的请求。你是怎么做到的,甚至是可能的?

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

9 个答案:

答案 0 :(得分:10)

使用魔术__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);
    }
}

答案 1 :(得分:3)

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

我认为您可以指定一个通配符来捕获特定模块上的每个操作(默认值为减少网址)并定义一个操作,该操作将根据网址(甚至是调用的操作)来呈现视图< / p>

new Zend_Controller_Router_Route('index/*', 
array('controller' => 'index', 'action' => 'custom', 'module'=>'index') 
你在customAction函数中的

只检索参数并显示正确的块。 我没有尝试过,你可能不得不破解代码

答案 2 :(得分:2)

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

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

答案 3 :(得分:2)

我需要让现有的模块/控制器/操作在Zend Framework应用程序中正常工作,但是然后有一个catchall路由发送任何未知的PageController,可以从数据库表中选择用户指定的URL并显示页面。我不希望在用户指定的URL前面有一个控制器名称。我希望/ my / custom / url不是/ page / my / custom / url来通过PageController。所以上述解决方案都不适合我。

我最终扩展了Zend_Controller_Router_Route_Module:使用了几乎所有的默认行为,只是略微调整控制器名称,如果控制器文件存在,我们正常路由到它。如果它不存在那么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:它捕获现有控制器上的无效操作。

答案 4 :(得分:2)

我通过重写dispatch方法并处理未找到操作时抛出的异常来实现了catch-all:

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');
    }
}

答案 5 :(得分:1)

您可以使用魔术__call()功能。例如:

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

答案 6 :(得分:1)

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

  1. 获取路由器的实例(一切都在你的bootstrap文件中,顺便说一下):

    $ router = $ frontController-&gt; getRouter();

  2. 创建自定义路线:

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

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

    $ frontController-&GT; setRouter($路由器);

  4. 我没有使用gabriel的__call方法(只要您不需要视图文件,它就可以用于丢失方法),因为这仍然会丢失有关丢失的相应视图文件的错误。

答案 7 :(得分:1)

供将来参考,建立在gabriel1836&amp; 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();
}

答案 8 :(得分:0)

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