Discussion of the idea: dynamic viewing script switching in the implementation of zend MVC

This is basically "Am I doing it right?" question.

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

Primary requirements:

  • switch the whole application with little encoded
  • integrates into zend MVC workflow, do not overwrite it
  • return to default
  • keep standard functionality controllers
  • don't have to know about changes

Here is my pseudo holper

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 to fine-tune the assistant, it doesn’t matter, and this part missed

, :
$ → _ setBasePaths(); // / /views/admin/
$ → _ setVrPathSpecs(); ': module/: controller/: action.: suffix'

foo-baz-bar
1. /views/admin/scripts/foo/baz/bar.phtml
2. application/views/default/scripts/foo/baz/bar.phtml
view script , ViewRenderer:
3. /modules/foo/views/scripts/baz/bar.phtml

, -


Update: after some research, I decided to use the action assistant to view autoregister scriptPaths based on the specification for the inflector and the given variables. I also modified the partial helpers to register scriptPaths if the partial request is from another module.

This is a crude but working version of the 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;
        }
    }
}
+3
source share
1 answer

How do I usually deal with this:

  • I am registering the Layout plugin by extending Zend_Layout_Controller_Plugin_Layout
  • I use the preDispatch hook to determine which module, controller, action I find in
  • I switch between layouts and views depending on context

This is the easiest way for me.

Gj

+1
source

Source: https://habr.com/ru/post/1796633/


All Articles