Hey ZF-Community,

first i have to say, good work guys! ZF became my first choice because of
its scalability and elegance of the api's, but there is a pitfall in my
application framework....

in short terms: i work on widget system to get completly encapsulated
internal requests, like PHP virutal() do and i would like to know if there
is a way to let the helper brooker not just instanciating helper objects
once. my helpers can hold internal properties and when i make a new front
controller dispatch during runtime, they are instancated again.

in long temrs: I am developing on a web application framework based on ZF
what works straight good until the point i was thinking about views and how
to implement a layout mechanism for all our possible coming modules alias
applications in our envirenment.

These modules should use one user/authorization base and allowed to be
rendered into a global design layout or optionaly something by there own. so
far so good, we implemented the proof of ACLs in a front controller plugin
and wanted now to render views from different modules. some views are like
widgets, they are clearly capsulated like a calendar with their own data and
own view. some views we wanted to render into our own views are more
complicated, they should bring their own data and own view, but we wanted to
overwrite some data, for example a html head with the title.

so i was searching around what is allready existing and i found a good
tutorial about complex views from Pádraic
Brady(http://blog.astrumfutura.com/search/views/P1.html). Also i found a
short note here about
widgets(http://www.nabble.com/Implementing-complex-Views-with-ZF-tf3608385s16154.html#a10083091).
I was searching for something that is very is to use and extremly flexible.
I also tried to bring PHP virtual() function to work in a view. For those
who dont know virtual() make an apache internal subrequest. In a View it
looks like:

<html>
<head></head>
<body>
  <?php virtual("/other-module/controller/action/var1/value1"); ?>
</body>
</html>

This would cause a completely capsulated internal sub call of a controller
of a different module. The Front Controller and the Plugins would be
generated and processing the request. Fortunately it didnt worked, i guess
the session hangs the php out.

Then i extended ZF to my needs: i start the bootstrap front controller which
handles the root request fairly well. During runtime i wanted to create new
front controllers as often as i want to make internal subrequests. The
advantadge is to have all the proccesses like ACL checking by default in
these subreqeusts. The disadvantage is of course a little overhead of
generating the needed objects again and again, but at this point we can live
with.

What i did was:

o extending Zend_Controller_Front with possibily of creating new instances
o chaining Zend_Controller_Front instances
o getting allways the right instance of Zend_Controller_Front in the current
environment
o creating a view helper called Widget which creates a new font controller
and gives him a new request and giving the response back
o a layout helper for action controller which let us define a layout (action
of a controller)
o give the layout various parameters to inject/overwrite them automated in
the layout view
o recursive usable

this is the code i came up:

/**
 * View Helper class which creates new instance of the front controller and
calls the dispatch to make a virtual subrequest during runtime.
 */
class RF_View_Helper_Widget extends RF_View_Helper
{
        public static $passThroughParamsRecursive = true;

        /**
         * This method returns the response of an virtual subrequest.
         * 
         * @author Markus Siebeneicher <[EMAIL PROTECTED]>
         * 
         * proxies to [EMAIL PROTECTED] widgetUri()}
         * 
         * @param string $action
         * @param string $controller
         * @param string $module
         * @param array $params URI Parameter aka Query Params
         * @param array $passThroughParams params for the front controller
         * @return Zend_Controller_Response_Http
         */
    public static function widget($action, $controller = NULL, $module =
NULL, $params = array(), $passThroughParams = array())
    {
                return self::widgetUri(RF_Helper::makeZendUri($action, 
$controller,
$module, $params, $passThroughParams));
    }
    /**
         * This method returns the response of an virtual subrequest.
         * 
         * @author Markus Siebeneicher <[EMAIL PROTECTED]>
         * 
         * @todo widget output/errors handeln
         * 
         * @param string|Zend_Uri $uri
         * @param array $passThroughParams params for the front controller
         * @return Zend_Controller_Response_Http
         */
    public static function widgetUri($uri, $passThroughParams = array()) {
                $frontParent = RF_Controller_Front::getInstance();

                if(self::$passThroughParamsRecursive === true)
                        if(count((array) 
$frontParent->getParam("widgetPassThroughParams")) > 0)
                                $passThroughParams = 
self::appendPassThroughParams(
                                        $passThroughParams,
                                        
$frontParent->getParam("widgetPassThroughParams")
                                );

                //print_r($passThroughParams);
                                
        $front = RF_Controller_Front::initFrontController($frontParent);

                $front->setParam("widgetPassThroughParams", $passThroughParams);

        $request = new Zend_Controller_Request_Http($uri);
                $response = new Zend_Controller_Response_Http();
                         
        ob_start();
        
        $t1 = microtime(true);
        $front->dispatch($request, $response);
        $t2 = microtime(true);

        Zend_Registry::getInstance()->rfLogger->log("Time used to dispatch
widget(". $front->getRequest()->getRequestUri() ."): ". ($t2-$t1),
Zend_Log::DEBUG);

        //$front->getResponse()->setBody(ob_get_contents());
                ob_end_clean();

                RF_Controller_Front::destroyLastInstance();
                //unset($front);

                return $response;
    }
    /**
         * hier werden zwei arrays gemerged, die elemente werden dem major vom
minor angehangen,
         * wenn der key im major noch nicht existiert
         * 
         * @author Markus Siebeneicher <[EMAIL PROTECTED]>
         * 
         * @param array $majorParams
         * @param array $minorParams
         * @return array 
         */
    private static function appendPassThroughParams($majorParams,
$minorParams)
    {
        //print_r($minorParams);
        //print_r($majorParams);
        
        foreach($minorParams AS $_key => $_value) {
                if(isset($majorParams[$_key])) {
                        if(is_array($majorParams[$_key]) && 
is_array($minorParams[$_key])) {
                                $majorParams[$_key] =
self::appendPassThroughParams($majorParams[$_key], $minorParams[$_key]);
                        }
                } else {
                        $majorParams[$_key] = $_value;
                }
        }

        return $majorParams;
    }
}


/**
 * This Class extends the Front Controller with the capability to make more
than one instance per runtime, the instance chain is also handled
 */
class RF_Controller_Front extends Zend_Controller_Front
{
        protected $_instance_parent;

        static protected $_instance_chain = array();

        /**
         * Sets the parent front controller object.
         * 
         * @author Markus Siebeneicher <[EMAIL PROTECTED]>
         * 
         * @param Zend_Controller_Front
         */
        public function setInstanceParent(Zend_Controller_Front $front)
        {
                $this->_instance_parent = $front;
        }
        
    /**
     * Singleton instance
     *
     * @author Markus Siebeneicher <[EMAIL PROTECTED]>
     * 
     * @return RF_Controller_Front
     */
    public static function getInstance()
    {
        if (null === self::$_instance) {
            self::$_instance = new self();
            self::$_instance_chain[] = self::$_instance;
        }
        return self::$_instance;
    }
    
    /**
         * Creates new instance and moves previous back in the chain.
         * 
         * @author Markus Siebeneicher <[EMAIL PROTECTED]>
         * 
         * @return RF_Controller_Front
         */
        public static function createInstance()
        {
                self::$_instance = null;
                return self::getInstance();
        }

        /**
         * Destroys last RF_Controller_Front instance.
         * 
         * @author Markus Siebeneicher <[EMAIL PROTECTED]>
         * 
         * @return void
         */
        public static function destroyLastInstance()
        {
                if(self::$_instance_chain[count(self::$_instance_chain) - 1] !==
self::$_instance) {
                        throw new Exception("Last entry of \$_instance_chain is 
not equal with
\$_instance, in ". __METHOD__);
                }

                if(is_array(self::$_instance_chain) && 
count(self::$_instance_chain) > 0)
{
                        array_pop(self::$_instance_chain);
                        if(count(self::$_instance_chain) > 0) {
                                self::$_instance = 
self::$_instance_chain[count(self::$_instance_chain)
- 1];
                        }
                }
        }

    /**
     * Resets all object properties of the singleton instance
     *
     * Primarily used for testing; could be used to chain front controllers.
     *
     * @author Markus Siebeneicher <[EMAIL PROTECTED]>
     * 
     * @return void
     */
    public function resetInstance()
    {
        $reflection = new ReflectionObject($this);
        foreach ($reflection->getProperties() as $property) {
            $name = $property->getName();
            switch ($name) {
                case '_instance':
                    break;
                case '_instance_chain':              // <-
                    break;
                case '_controllerDir':
                case '_invokeParams':
                    $this->{$name} = array();
                    break;
                case '_plugins':
                    $this->{$name} = new Zend_Controller_Plugin_Broker();
                    break;
                case '_throwExceptions':
                case '_returnResponse':
                    $this->{$name} = false;
                    break;
                case '_moduleControllerDirectoryName':
                    $this->{$name} = 'controllers';
                    break;
                default:
                    $this->{$name} = null;
                    break;
            }
        }

        if (!Zend_Controller_Action_HelperBroker::hasHelper('viewRenderer'))
{
            Zend_Controller_Action_HelperBroker::addHelper(new
Zend_Controller_Action_Helper_ViewRenderer());
        }
    }
    

        static private function _initPluginErrorHandler()
        {
                $errorPlugin = new Zend_Controller_Plugin_ErrorHandler();
                $errorPlugin->setErrorHandlerModule('rf-frontpage')
                                ->setErrorHandlerController('error')
                                ->setErrorHandlerAction('error');
                return $errorPlugin;
        }
        /**
         * This function creates a new instance of the RF_Controller_Front 
class.
         * 
         * @author Markus Siebeneicher <[EMAIL PROTECTED]>
         * 
         * @param Zend_Controller_Front $parentFrontController
         * @return RF_Controller_Front
         */
        static public function initFrontController(Zend_Controller_Front
$parentFrontController = null)
        {
                // Der Broker instanziert einmal einen helper und nimmt ihn für 
jeden
neuen controller.
                // Leider auch bei neuen FrontController Instanzen, drum 
instanzieren wir
die notwendigen
                // Helper neu.
                // XXXMSi: ersetzt das den vorherigen helper global, also auch 
im
controlle und ist nicht mehr
                // verfügbar?
                Zend_Controller_Action_HelperBroker::addHelper(new
RF_Controller_Action_Helper_ViewRenderer());
                Zend_Controller_Action_HelperBroker::addHelper(new
RF_Controller_Action_Helper_Layout());
        
                // Register Controller Action Helper
        
Zend_Controller_Action_HelperBroker::addPath('./libraries/RF/Controller/Action/Helper',
'RF_Controller_Action_Helper');

                // create new instance of RF_Controller_Front
                $frontController = RF_Controller_Front::createInstance();
                
                // adds controller directories to the front controller
                
$frontController->addControllerDirectories(RF_Module::getModules());

                // Shall an error be printed out?
                
$frontController->throwExceptions(RF_APPLICATION_THROW_EXCEPTION);

                
$frontController->setDefaultModule(RF_APPLICATION_DEFAULT_MODULE)
                         
->setDefaultControllerName(RF_APPLICATION_DEFAULT_CONTROLLER)
                         ->setDefaultAction(RF_APPLICATION_DEFAULT_ACTION)
                         ->setRouter(new Zend_Controller_Router_Rewrite())
                         ->registerPlugin(self::_initPluginErrorHandler())
                         ->registerPlugin(new RF_Acl_Plugin_Init())
                         //->registerPlugin(new RF_Controller_Plugin_Logger())
                         ->registerPlugin(new RF_Controller_Plugin_Init())
                         ->registerPlugin(new RF_Language_Plugin_Init());
                         //->registerPlugin(new 
RF_Controller_Plugin_Frontend());

                if($parentFrontController instanceof Zend_Controller_Front) {
                        
$frontController->setInstanceParent($parentFrontController);
                }
                         
                return $frontController;
        }
}


so i hope this was not banging you too much, in a view we now make something
like this:

<div id="calender">
  <?php echo $this->widget("calendar", "utils", "frontpage"); ?>
</div>

or we make this:

  <?php echo $this->getHelper("Widget")->widgetUri($this->contentUri,
array("title", "my pretty page")); ?>


the widget() and widgetUri() functions are creating a new instance of the
front controller, dispatching a new request and giving the response back to
the view.

my problem is now, that our view and action controller helpers are behaving
like static and are not initialized per each front controller instance. The
helper brooker applies the helper objects in a singleton way to the new view
and controller object of the new front controller. i worked around with
setting new helper objects like:

Zend_Controller_Action_HelperBroker::addHelper(new
RF_Controller_Action_Helper_ViewRenderer());
Zend_Controller_Action_HelperBroker::addHelper(new
RF_Controller_Action_Helper_Layout());

These objects are using internal objects, and they have the  wrong
references when i dont create them new. but this also destroys my old
instances of my helpers, what could bring me earlier or later into trouble.

i am sorry that this post is so long, i hope for comments about the brooker,
if its possible to extend them with my concerns in mind? thanks to those who
followed me until here...

bye markus


By the way: i could/should also post the layout and viewrenderer subclasses
for those who wish(they make use simple layout system based on the widgets
and recursive subrequests with passing params into the view), i guess this
would explode this "little" post... but here it is:





class RF_Controller_Action_Helper_ViewRenderer extends
Zend_Controller_Action_Helper_ViewRenderer
{
    /**
     * Render a view script (optionally to a named response segment)
     *
     * Sets the noRender flag to true when called.
     * 
     * Reporting-Framework: Renders into a layout widget if $_noLayout is
false and $_layout is present.
     * The response of the widget is overwritten by the response of the
view.
     * The body of this view is injected into the view of the widget.
     * Inject Params are given to the widget.
     * 
     * Reporting-Framework: On the other side of the medaile we render here
the layout itself.
     * 
     *
     * @param  string $script
     * @param  string $name
     * @return void
     */
    public function renderScript($script, $name = null)
    {
        if (null === $name) {
            $name = $this->getResponseSegment();
        }

        // if this is a subrequest/subfrontcontroller by widget go on...
        $invokeArgs = $this->getActionController()->getInvokeArgs();
        if(isset($invokeArgs["widgetPassThroughParams"])) {
                        $widgetPassThroughParams = 
$invokeArgs["widgetPassThroughParams"];

                        //echo $this->getRequest()->getActionName();
                        //var_dump($widgetPassThroughParams);
                        
                // inject(set and/or replace) the params in the view object
                if(isset($widgetPassThroughParams["layoutInjectParams"]))
                                
foreach($widgetPassThroughParams["layoutInjectParams"] AS $_key => $_a)
                                        if(!isset($this->view->$_key) || 
$_a['replace'])
                                                $this->view->$_key = 
$_a['value'];

        }

        // render view
        $this->getResponse()->appendBody(
            $this->view->render($script),
            $name
        );

        $this->setNoRender();

        $layoutHelper = $this->getActionController()->getHelper("Layout");

        // render the view and inject the body into the layout
        if($layoutHelper->getNoLayoutRender() === true)
                return;

        if(!$layoutHelper->getLayoutWidget()) {
                Zend_Registry::getInstance()->rfLogger->log("Can not render 
layout,
no layout defined in Controller, in ". __METHOD__, Zend_Log::ERROR);
                return;
        }

        $layoutHelper->setLayoutInjectParam("layoutInjectBody",
$this->getResponse()->getBody());
                $widgetPassThroughParams = array("layoutInjectParams" =>
$layoutHelper->getLayoutInjectParams());

        // get the widgets response
        $widgetResponse = RF_View_Helper_Widget::widgetUri(
                $layoutHelper->getLayoutWidget(),
                $widgetPassThroughParams
        );

        //var_dump($layoutHelper);
        
        // set the new body 
                $this->getResponse()->setBody($widgetResponse->getBody());
                $layoutHelper->appendResponse($this->getResponse(), 
$widgetResponse);
    }
}



class RF_Controller_Action_Helper_Layout extends
Zend_Controller_Action_Helper_Abstract
{
        /**
         * Holds the layout uri
         * 
         * @var Zend_Uri|string
         */
        protected $_layout;
        /**
         * Holds the layout render status
         * 
         * @var bool
         */
        protected $_noLayoutRender = true;

        /**
         * This array holds the parameters which will be injected into the 
layout
view
         * 
         * @var array
         */
        protected $_layoutInjectParams = array();
        
        /**
         * Returns all inject parameters
         * 
         * @return array
         */
        public function getLayoutInjectParams()
        {
                return $this->_layoutInjectParams;
        }
        /**
         * Returns the inject parameter with the given name
         * 
         * @param string $name
         * @return array|null returns null if parameter does not exist
         */
        public function getLayoutInjectParam($name)
        {
                if(isset($this->_layoutInjectParams[$name]))
                        return $this->_layoutInjectParams[$name];
                return null;
        }
        /**
         * Adds a new inject parameter for the layout view
         * 
         * If the parameter with the given name allready exists, it will be
overwritten
         * 
         * @param string $name name of the parameter
         * @param mixed $value value of the parameter
         * @param bool $replace shall the value of the same parameter of the 
layout
view be overwritten
         * @return void
         */
        public function setLayoutInjectParam($name, $value, $replace = true)
        {
                $this->_layoutInjectParams[$name] = array("value" => $value, 
"replace" =>
(bool) $replace);
        }
        /**
         * Appends a new element at the end, only if the param is an array.
         * 
         * @param string $name name of the parameter
         * @param mixed $value value of the parameter
         * @param bool $replace shall the value of the same parameter of the 
layout
view be overwritten
         * @return void
         */
        public function appendLayoutInjectParam($name, $value, $replace = true)
        {
                if(isset($this->_layoutInjectParams[$name]) &&
!is_array($this->_layoutInjectParams[$name]['value']))
                        return;
        
                if(!isset($this->_layoutInjectParams[$name]) ||
!$this->_layoutInjectParams[$name]['value'])
                        $this->_layoutInjectParams[$name] = array('value' => 
array(), 'replace'
=> $replace);

                $this->_layoutInjectParams[$name]['value'][] = $value;
                $this->_layoutInjectParams[$name]['replace'] = $replace;
        }
        /**
         * Removes inject parameter
         * 
         * @param string $name
         */
        public function removeLayoutInjectParam($name)
        {
                if(isset($this->_layoutInjectParams[$name]))
                        unset($this->_layoutInjectParams[$name]);
        }

        /**
         * Returns the layout uri
         * 
         * @return Zend_Uri
         */
        public function getLayoutWidget()
        {
                return $this->_layout;
        }
        /**
         * Set the layout widget
         * 
         * @param string $action
         * @param string $controller
         * @param string $module
         * @param array $params URI params
         * @return void
         */
        public function setLayoutWidget($action, $controller = null, $module =
null, $params = array())
        {
                $this->setNoLayoutRender(false);
                $this->_layout = RF_Helper::makeZendUri($action, $controller, 
$module,
$params);
        }
        /**
         * Returns if a layout should be rendered
         * 
         * @return bool
         */
        public function getNoLayoutRender()
        {
                return (bool) $this->_noLayoutRender;
        }
        /**
         * Set if the layout should not be rendered
         * 
         * @param bool $noLayoutRender
         * @return void
         */
        public function setNoLayoutRender($noLayoutRender = true)
        {
                $this->_noLayoutRender = (bool) $noLayoutRender;
        }
        /**
         * Appends Response Objects
         * 
         * The major response is priorizied over the minor response. The major
inherits from the minor.
         * The minor never replaces the major.
         * 
         * @param Zend_Controller_Response_Abstract $majorResponse
         * @param Zend_Controller_Response_Abstract $minorResponse
         * @return void
         */
    public static function appendResponse($majorResponse, $minorResponse) {
        // merge headers
        foreach($minorResponse->getHeaders() AS $_key => $_value)
                $majorResponse->setHeader($_key, $_value, false);       // no 
replace

        // Exceptions verursachen automatisch in einem front controller einen
abbruch + ausgabe
        // ResponseCode bleibt beim majorResponse
    }
        /**
         * Insert css file
         * 
         * Uses Param: layoutInjectCssLinks
         * 
         * @param String $cssFile
         * @param String $moduleName
         */
        public function addCssLink($cssFile, $moduleName = NULL)
        {
                //Zend_Registry::getInstance()->rfLogger->log('Start addCss',
Zend_Log::DEBUG);

                if (!$moduleName) {
                        $moduleName = $this->getRequest()->getModuleName();
                }

                $cssFilePath = RF_BASE_URL .'/modules/'. $moduleName 
.'/public/css/'.
$cssFile;
                $this->appendLayoutInjectParam("layoutInjectCssLinks", 
$cssFilePath);

                //Zend_Registry::getInstance()->rfLogger->log('Finish addCss',
Zend_Log::DEBUG);
        }
        
        /**
         * Insert javascript file
         * 
         * Uses Param: layoutInjectJsScripts
         * 
         * @param String $JsFile
         * @param String $moduleName
         */
        public function addJsScript($jsFile, $moduleName = NULL)
        {
                //Zend_Registry::getInstance()->rfLogger->log('Start addJs',
Zend_Log::DEBUG);

                if (!$moduleName) {
                        $moduleName = $this->getRequest()->getModuleName();
                }
                
                $jsFilePath = RF_BASE_URL .'/modules/'. $moduleName 
.'/public/css/'.
$jsFile;
                $this->appendLayoutInjectParam("layoutInjectJsScripts", 
$jsFilePath);
                
                //Zend_Registry::getInstance()->rfLogger->log('Finish addJs',
Zend_Log::DEBUG);
        }
}

-- 
View this message in context: 
http://www.nabble.com/encapsulate-front-controller%2C-widget-layout-system-tf4592779s16154.html#a13111381
Sent from the Zend Framework mailing list archive at Nabble.com.

Reply via email to