思路讨论:zend MVC实现中的动态视图脚本切换

发布于 2024-10-20 06:49:53 字数 11007 浏览 0 评论 0原文

这基本上就是“我做得对吗?”问题。

我知道如何在运行时透明地切换默认/移动版本/管理区域的视图。我想知道您认为这种方法有哪些优点和缺点。

主要要求是:

  1. 切换整个应用程序 很少甚至没有编码
  2. 集成到 zend MVC 工作流程中, 不覆盖它
  3. 回退到默认
  4. 保留标准功能
  5. 控制器不应该知道 更改

这是我的伪帮助程序

class Xrks_Controller_Action_Helper_VrExtension extends Zend_Controller_Action_Helper_Abstract
{
    public function postDispatch()
    {
        if(!$this->_shouldRender()) {
            return; //just skip
        }
        try {
            $vr = $this->_getViewRenderer();
            $backupView = clone $vr->view;
            $this->_setBasePaths(); //set base path(s) through ViewRenderer::initView($path)
            $oldSpecArray = $this->_setVrPathSpecs(); //set VR view script path specs
            $vr->render();
            $vr->setNoRender(true); //disable renderer

        } catch(Zend_View_Exception $e) { //fallback to default viewscripts if view script file not found 
            $vr->setView($backupView); //restore view on error

        } catch(Exception $e) {
            $vr->setView($backupView); //restore view on error
            $this->_setVrPathSpecs($oldSpecArray); //restore script path spec
            throw $e;
        }
        $this->_setVrPathSpecs($oldSpecArray);//restore script path spec
    }

    /**
     * Same functionality as ViewRenderer helper _shouldRender method
     * @return boolean
     */
    protected function _shouldRender();

    /**
     * @return Zend_Controller_Action_Helper_ViewRenderer
     */
    protected function _getViewRenderer();

    /**
     * Sets viewRenderer path specifications
     *
     * @param array $spec if NULL uses $this->_viewRendererPathSpecs
     * @return array old path spec (0 => pathSpec, 1 => pathNoControllerSpec)
     */
    protected function _setVrPathSpecs(array $spec = NULL);
}

应该如何配置帮助程序并不重要,并且跳过该部分

以下是它应该如何工作的示例:
$this->_setBasePaths();将视图基本路径设置为 application/views/default/application/views/admin/
$this->_setVrPathSpecs();将路径规范设置为“:module/:controller/:action.:suffix”,

因此对于 foo-baz-bar 它将搜索
1. application/views/admin/scripts/foo/baz/bar.phtml
2. application/views/default/scripts/foo/baz/bar.phtml
如果未找到视图脚本,则回退到默认 ViewRenderer:
3. application/modules/foo/views/scripts/baz/bar.phtml

如果我错过了什么,请提问


Upd: After some research i decided to use action helper to autoregister view scriptPaths based on specification for inflector and specified variables. I also modified partial helpers to register scriptPaths if partial from other module requested.

这是动作助手的粗略但有效的版本:

class Xrks_Controller_Action_Helper_ViewRendererPathstack extends Zend_Controller_Action_Helper_Abstract
{
    const PATH_APPEND  = 'append';
    const PATH_PREPEND = 'prepend';

    protected $_enabled = FALSE;
    protected $_viewScriptPaths = array();
    /**
     * By default following vars available: baseDir, area, theme, module
     * @var string
     */
    protected $_viewScriptPathSpec = ':baseDir/:area/:module';
    protected $_defaults = array(
        'area'       => 'frontend',
        'theme'      => 'default',
    );
    protected $_vars = array();
    protected $_inflector;
    protected $_viewRenderer;

    public function __construct($baseDir = NULL)
    {
        if($baseDir == NULL) {
            $baseDir = APPLICATION_PATH . DS . 'views';
        }
        $this->setDefaultVar('baseDir', $baseDir);
        $this->addPath(array());
    }

    /**
     * Enter description here ...
     * @return Zend_Controller_Action_Helper_ViewRenderer
     */
    protected function _getViewRenderer()
    {
        if(!$this->_viewRenderer) {
            $this->_viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
        }
        return $this->_viewRenderer;
    }

    /**
     * Should the ViewRenderer render a view script?
     *
     * @return boolean
     */
    protected function _shouldRender()
    {
        $vR = $this->_getViewRenderer();
        return (!$this->getFrontController()->getParam('noViewRenderer')
            && !$vR->getNeverRender()
            && !$vR->getNoRender()
            && (null !== $vR->getActionController())
            && $vR->getRequest()->isDispatched()
            && !$vR->getResponse()->isRedirect()
        );
    }

    public function generatePaths(array $vars = array())
    {
        $this->_registerVarsWithInflector();
        $vars = array_merge($this->_defaults, $this->_vars, $vars);
        $inflector = $this->getInflector();
        $generatedPaths = array();
        foreach($this->_viewScriptPaths as $path) {
            $pathVars = array_merge($vars, $path);
            $generatedPaths[] = $inflector->filter($pathVars);
        }
        return array_reverse(array_unique(array_reverse($generatedPaths)));//last occurence more important than first
        // array('test', 'test2', 'test') => array('test2', 'test')
        // @todo rethink this code piece later. must be better solution
    }

    protected function _registerVarsWithInflector()
    {
        $vars = array_merge($this->_defaults, $this->_vars);
        $inflector = $this->getInflector();
        $unregistered = array_keys(array_diff_key($vars, $inflector->getRules()));
        sort($unregistered, SORT_DESC);//more specific first (moduleDir prior to module key)
        foreach($unregistered as $var) {
            $inflector->addFilterRule($var, array('Word_CamelCaseToDash', 'StringToLower'));
        }
    }

    protected function _viewAddScriptPaths(Zend_View_Abstract $view, $paths)
    {
        foreach ($paths as $path) {
            $view->addScriptPath($path);
        }
    }

    /**
     * Get inflector
     *
     * @return Zend_Filter_Inflector
     */
    public function getInflector()
    {
        if (null === $this->_inflector) {
            $this->_inflector = new Zend_Filter_Inflector();
            $this->_inflector->setThrowTargetExceptionsOn(true);
            //setup default rules
            $this->_inflector->addRules(array(
                     ':baseDir' => array(),
                 ))
                 ->setTargetReference($this->_viewScriptPathSpec);
        }
        return $this->_inflector;
    }

    /**
     *
     * @return array
     */
    public function getPaths()
    {
        return $this->_basePaths;
    }

    public function getEnabled()
    {
        return $this->_enabled;
    }

    public function setEnabled($flag = TRUE)
    {
        $this->_enabled = (bool)$flag;
        return $this;
    }

    /**
     *
     * @todo add check for $pathVars keys and values validity
     * @param array $pathVars associative array
     * @param string $placement either append or prepend
     * @return Xrks_Controller_Action_Helper_ViewRendererPathstack
     */
    public function addPath(array $pathVars, $placement = self::PATH_APPEND)
    {
        if($placement == self::PATH_PREPEND) {
            array_unshift($this->_viewScriptPaths, $pathVars);
        } else {
            $this->_viewScriptPaths[] = $pathVars;
        }
        return $this;
    }

    /**
     *
     * @param array|Zend_Config $paths
     * @param string $placement either append or prepend
     * @return Xrks_Controller_Action_Helper_ViewRendererPathstack
     * @throws Xrks_Exception
     */
    public function addPaths($paths, $placement = self::PATH_APPEND)
    {
        if($paths instanceof Zend_Config) {
            $paths = $paths->toArray();
        } elseif (!is_array($paths)) {
            throw new Xrks_Exception('$paths should be either array or instance of Zend_Config');
        }

        if($placement == self::PATH_PREPEND) {
            $paths = array_reverse($paths);
        }
        foreach($paths as $path) {
            $this->addPath((array)$path, $placement);
        }
        return $this;
    }

    /**
     *
     * @param array $pathVars associative array
     * @return Xrks_Controller_Action_Helper_ViewRendererPathstack
     */
    public function setPath(array $pathVars)
    {
        $this->_basePaths = array();
        $this->addPath($pathVars);
        return $this;
    }

    /**
     *
     * @param array|Zend_Config $paths
     * @return Xrks_Controller_Action_Helper_ViewRendererPathstack
     * @throws Xrks_Exception
     */
    public function setPaths($paths)
    {
        $this->_basePaths = array();
        $this->addPaths($paths);
        return $this;
    }

    /**
     *
     * @param string $varName
     * @return string |NULL
     */
    public function getDefaultVar($varName)
    {
        if(key_exists($varName, $this->_defaults)) {
            return $this->_defaults[$varName];
        }
        return NULL;
    }

    /**
     * @param string $varName
     * @param string $value
     * @return Xrks_Controller_Action_Helper_ViewRendererPathstack Provides fluent interface
     */
    public function setDefaultVar($varName, $value)
    {
        $this->_defaults[$varName] = (string)$value;
        return $this;
    }

    /**
     *
     * @param string $name
     * @return string |NULL
     */
    public function getVar($name, $defaults = false)
    {
        if(key_exists($name, $this->_vars)) {
            return $this->_vars[$name];
        }
        return $defaults ? $this->getDefaultVar($name) : NULL;
    }

    /**
     * @param string $varName
     * @param string $value
     * @return Xrks_Controller_Action_Helper_ViewRendererPathstack Provides fluent interface
     */
    public function setVar($varName, $value)
    {
        $this->_vars[$varName] = $value;
        return $this;
    }

    public function unsetVar($name)
    {
        if(key_exists($name, $this->_vars)) {
            unset($this->_vars[$name]);
        }
        return $this;
    }


    public function postDispatch()
    {
        if(!$this->getEnabled() || !$this->_shouldRender()) {
            return; //just skip
        }
        try {
            $vr = $this->_getViewRenderer();
            $this->setVar('module', $vr->getModule());
            $paths = $this->generatePaths();
            $this->_viewAddScriptPaths($vr->view, $paths);
            if(Zend_Registry::isRegistered('Zend_Log')) {
                Zend_Registry::get('Zend_Log')
                    ->log($paths, Zend_Log::DEBUG);
            }
        } catch(Exception $e) {
            if(Zend_Registry::isRegistered('Zend_Log')) {
                Zend_Registry::get('Zend_Log')
                    ->log($e, Zend_Log::WARN);
            }
            throw $e;
        }
    }
}

This is basically the "Am i doing it right?" question.

I have an idea how i can transparently switch views for default/mobile version/admin areas at run time. And I would like to know what pros and cons you see in this approach.

Main requirements are:

  1. switch entire application with
    little to no coding
  2. integrate into zend MVC workflow,
    not overwrite it
  3. fallback to default
  4. preserve standard functionality
  5. controllers shouldn't be aware of
    changes

Here is my pseudohelper

class Xrks_Controller_Action_Helper_VrExtension extends Zend_Controller_Action_Helper_Abstract
{
    public function postDispatch()
    {
        if(!$this->_shouldRender()) {
            return; //just skip
        }
        try {
            $vr = $this->_getViewRenderer();
            $backupView = clone $vr->view;
            $this->_setBasePaths(); //set base path(s) through ViewRenderer::initView($path)
            $oldSpecArray = $this->_setVrPathSpecs(); //set VR view script path specs
            $vr->render();
            $vr->setNoRender(true); //disable renderer

        } catch(Zend_View_Exception $e) { //fallback to default viewscripts if view script file not found 
            $vr->setView($backupView); //restore view on error

        } catch(Exception $e) {
            $vr->setView($backupView); //restore view on error
            $this->_setVrPathSpecs($oldSpecArray); //restore script path spec
            throw $e;
        }
        $this->_setVrPathSpecs($oldSpecArray);//restore script path spec
    }

    /**
     * Same functionality as ViewRenderer helper _shouldRender method
     * @return boolean
     */
    protected function _shouldRender();

    /**
     * @return Zend_Controller_Action_Helper_ViewRenderer
     */
    protected function _getViewRenderer();

    /**
     * Sets viewRenderer path specifications
     *
     * @param array $spec if NULL uses $this->_viewRendererPathSpecs
     * @return array old path spec (0 => pathSpec, 1 => pathNoControllerSpec)
     */
    protected function _setVrPathSpecs(array $spec = NULL);
}

How exactly helper should be configured is not important and that part skipped

Here is example how it supposed to work:
$this->_setBasePaths(); sets view base paths to application/views/default/ and application/views/admin/
$this->_setVrPathSpecs(); set path specification to ':module/:controller/:action.:suffix'

so for foo-baz-bar it will search at
1. application/views/admin/scripts/foo/baz/bar.phtml
2. application/views/default/scripts/foo/baz/bar.phtml
if view script not found fall back to default ViewRenderer:
3. application/modules/foo/views/scripts/baz/bar.phtml

Ask questions if I missed something


Upd: After some research i decided to use action helper to autoregister view scriptPaths based on specification for inflector and specified variables. I also modified partial helpers to register scriptPaths if partial from other module requested.

This is crude but working version of action helper:

class Xrks_Controller_Action_Helper_ViewRendererPathstack extends Zend_Controller_Action_Helper_Abstract
{
    const PATH_APPEND  = 'append';
    const PATH_PREPEND = 'prepend';

    protected $_enabled = FALSE;
    protected $_viewScriptPaths = array();
    /**
     * By default following vars available: baseDir, area, theme, module
     * @var string
     */
    protected $_viewScriptPathSpec = ':baseDir/:area/:module';
    protected $_defaults = array(
        'area'       => 'frontend',
        'theme'      => 'default',
    );
    protected $_vars = array();
    protected $_inflector;
    protected $_viewRenderer;

    public function __construct($baseDir = NULL)
    {
        if($baseDir == NULL) {
            $baseDir = APPLICATION_PATH . DS . 'views';
        }
        $this->setDefaultVar('baseDir', $baseDir);
        $this->addPath(array());
    }

    /**
     * Enter description here ...
     * @return Zend_Controller_Action_Helper_ViewRenderer
     */
    protected function _getViewRenderer()
    {
        if(!$this->_viewRenderer) {
            $this->_viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
        }
        return $this->_viewRenderer;
    }

    /**
     * Should the ViewRenderer render a view script?
     *
     * @return boolean
     */
    protected function _shouldRender()
    {
        $vR = $this->_getViewRenderer();
        return (!$this->getFrontController()->getParam('noViewRenderer')
            && !$vR->getNeverRender()
            && !$vR->getNoRender()
            && (null !== $vR->getActionController())
            && $vR->getRequest()->isDispatched()
            && !$vR->getResponse()->isRedirect()
        );
    }

    public function generatePaths(array $vars = array())
    {
        $this->_registerVarsWithInflector();
        $vars = array_merge($this->_defaults, $this->_vars, $vars);
        $inflector = $this->getInflector();
        $generatedPaths = array();
        foreach($this->_viewScriptPaths as $path) {
            $pathVars = array_merge($vars, $path);
            $generatedPaths[] = $inflector->filter($pathVars);
        }
        return array_reverse(array_unique(array_reverse($generatedPaths)));//last occurence more important than first
        // array('test', 'test2', 'test') => array('test2', 'test')
        // @todo rethink this code piece later. must be better solution
    }

    protected function _registerVarsWithInflector()
    {
        $vars = array_merge($this->_defaults, $this->_vars);
        $inflector = $this->getInflector();
        $unregistered = array_keys(array_diff_key($vars, $inflector->getRules()));
        sort($unregistered, SORT_DESC);//more specific first (moduleDir prior to module key)
        foreach($unregistered as $var) {
            $inflector->addFilterRule($var, array('Word_CamelCaseToDash', 'StringToLower'));
        }
    }

    protected function _viewAddScriptPaths(Zend_View_Abstract $view, $paths)
    {
        foreach ($paths as $path) {
            $view->addScriptPath($path);
        }
    }

    /**
     * Get inflector
     *
     * @return Zend_Filter_Inflector
     */
    public function getInflector()
    {
        if (null === $this->_inflector) {
            $this->_inflector = new Zend_Filter_Inflector();
            $this->_inflector->setThrowTargetExceptionsOn(true);
            //setup default rules
            $this->_inflector->addRules(array(
                     ':baseDir' => array(),
                 ))
                 ->setTargetReference($this->_viewScriptPathSpec);
        }
        return $this->_inflector;
    }

    /**
     *
     * @return array
     */
    public function getPaths()
    {
        return $this->_basePaths;
    }

    public function getEnabled()
    {
        return $this->_enabled;
    }

    public function setEnabled($flag = TRUE)
    {
        $this->_enabled = (bool)$flag;
        return $this;
    }

    /**
     *
     * @todo add check for $pathVars keys and values validity
     * @param array $pathVars associative array
     * @param string $placement either append or prepend
     * @return Xrks_Controller_Action_Helper_ViewRendererPathstack
     */
    public function addPath(array $pathVars, $placement = self::PATH_APPEND)
    {
        if($placement == self::PATH_PREPEND) {
            array_unshift($this->_viewScriptPaths, $pathVars);
        } else {
            $this->_viewScriptPaths[] = $pathVars;
        }
        return $this;
    }

    /**
     *
     * @param array|Zend_Config $paths
     * @param string $placement either append or prepend
     * @return Xrks_Controller_Action_Helper_ViewRendererPathstack
     * @throws Xrks_Exception
     */
    public function addPaths($paths, $placement = self::PATH_APPEND)
    {
        if($paths instanceof Zend_Config) {
            $paths = $paths->toArray();
        } elseif (!is_array($paths)) {
            throw new Xrks_Exception('$paths should be either array or instance of Zend_Config');
        }

        if($placement == self::PATH_PREPEND) {
            $paths = array_reverse($paths);
        }
        foreach($paths as $path) {
            $this->addPath((array)$path, $placement);
        }
        return $this;
    }

    /**
     *
     * @param array $pathVars associative array
     * @return Xrks_Controller_Action_Helper_ViewRendererPathstack
     */
    public function setPath(array $pathVars)
    {
        $this->_basePaths = array();
        $this->addPath($pathVars);
        return $this;
    }

    /**
     *
     * @param array|Zend_Config $paths
     * @return Xrks_Controller_Action_Helper_ViewRendererPathstack
     * @throws Xrks_Exception
     */
    public function setPaths($paths)
    {
        $this->_basePaths = array();
        $this->addPaths($paths);
        return $this;
    }

    /**
     *
     * @param string $varName
     * @return string |NULL
     */
    public function getDefaultVar($varName)
    {
        if(key_exists($varName, $this->_defaults)) {
            return $this->_defaults[$varName];
        }
        return NULL;
    }

    /**
     * @param string $varName
     * @param string $value
     * @return Xrks_Controller_Action_Helper_ViewRendererPathstack Provides fluent interface
     */
    public function setDefaultVar($varName, $value)
    {
        $this->_defaults[$varName] = (string)$value;
        return $this;
    }

    /**
     *
     * @param string $name
     * @return string |NULL
     */
    public function getVar($name, $defaults = false)
    {
        if(key_exists($name, $this->_vars)) {
            return $this->_vars[$name];
        }
        return $defaults ? $this->getDefaultVar($name) : NULL;
    }

    /**
     * @param string $varName
     * @param string $value
     * @return Xrks_Controller_Action_Helper_ViewRendererPathstack Provides fluent interface
     */
    public function setVar($varName, $value)
    {
        $this->_vars[$varName] = $value;
        return $this;
    }

    public function unsetVar($name)
    {
        if(key_exists($name, $this->_vars)) {
            unset($this->_vars[$name]);
        }
        return $this;
    }


    public function postDispatch()
    {
        if(!$this->getEnabled() || !$this->_shouldRender()) {
            return; //just skip
        }
        try {
            $vr = $this->_getViewRenderer();
            $this->setVar('module', $vr->getModule());
            $paths = $this->generatePaths();
            $this->_viewAddScriptPaths($vr->view, $paths);
            if(Zend_Registry::isRegistered('Zend_Log')) {
                Zend_Registry::get('Zend_Log')
                    ->log($paths, Zend_Log::DEBUG);
            }
        } catch(Exception $e) {
            if(Zend_Registry::isRegistered('Zend_Log')) {
                Zend_Registry::get('Zend_Log')
                    ->log($e, Zend_Log::WARN);
            }
            throw $e;
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

看海 2024-10-27 06:49:53

我通常处理这个问题的方式:

  • 我注册一个布局插件,扩展 Zend_Layout_Controller_Plugin_Layout
  • 我使用 preDispatch 钩子来确定我所在的模块、控制器、操作 我
  • 根据上下文在布局和视图之间切换

对我来说,这是迄今为止最简单的方法。

国杰

The way I usually handle this:

  • I register a Layout Plugin, extending Zend_Layout_Controller_Plugin_Layout
  • I use the preDispatch hook to determine what module, controller, action I am in
  • I switch between layouts and views depending on the context

For me, that's by far the easiest method.

GJ

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文