Hello,
How could I process an action before anything else, including the FC's
current request, from within my subclassed zend_controller_action?
Like, supposing I'm creating a tabbed interface (which I'm actually doing,
with dojo), and the current action would be /module2/index/index, how to
prepend /default/index/index to the "list of tabs"?
My idea was to use the view object's action() helper before dispatch(), but
this doesn't work:
$this->view->action('index','index','default');
Instead, I'm using the actionStack, but that pushes it to the stack.
Please also note that I really need it to be done in My_Controller_Action,
the base class for my "tabbed actions", so I think the router or anything
"global" is not what I want, since that would enforce a behaviour for all of
my controllers, which obviously is not what I want.
Here's the code, as I use it now:
<?php
require_once 'Zend/Controller/Action.php';
class My_Controller_Action extends Zend_Controller_Action {
protected $_isDirectCall = FALSE;//module/controller/action only if it
has not already been done
protected $_isStartAction = FALSE;//is the /default/index/index action
/*
* 2D array containing three keys, each in the format 0 => module 1 =>
controller 2 => action
* 'default' : what the front controller returns as "default
homepage"
* 'this' : this very object/request
* 'url' : what is actually in the url
*/
private $_mvcs = NULL;
private static $instances=array();
public function preDispatch() {
$logger = Zend_Registry::get('logger');
$mvcs = $this->getMVCs();
if($mvcs['default'] == $mvcs['this']) {//startpage
$this->_isStartAction = TRUE;
}
if($mvcs['this'] == $mvcs['url']) {//direct call
$this->_isDirectCall = TRUE;
}
//TODO: do this based on Zend_Registry::get('config')
if($this->_isDirectCall && !$this->_isStartAction) {
//XXX: how to put the default tab before $mvcs['this'] ?
//I'd do this, but it doesn't work:
//$this->view->action('index','index','default');
//instead:
$this->_helper->actionStack('index','index','default');
//which adds the "default tab" *AFTER* the current one
}
$this->view->MVCs = $this->_mvcs;
$this->view->isDirectCall = $this->_isDirectCall;
$this->view->isDefaultAction = $this->_isStartAction;
if(!$this->_isDirectCall) {
//XXX: this fixes the previous problem of using the wrong path
from zend_view_abstract::$_path[]
//when rendering the view script of the 'navpane' action (each
module has got one of them)
$this->view->addBasePath(Zend_Registry::get('config')->modulesPath.'/'.$mvcs['this'][0].'/views');
}
}
public function dispatch($action) {
$mvcs = $this->getMVCs();
$this_act = '/'.implode('/',$mvcs['this']);
Zend_Wildfire_Plugin_FirePhp::group($this_act);
Zend_Wildfire_Plugin_FirePhp::send($this->getViewScript(),'view
script');
Zend_Wildfire_Plugin_FirePhp::send($this->_helper->viewRenderer->view->getScriptPaths(),'all
script paths');
Zend_Wildfire_Plugin_FirePhp::send($mvcs,'MVCs');
Zend_Wildfire_Plugin_FirePhp::groupEnd();
/**
* if this is a direct call to this action, then activate the tab
generated
*/
if($this->_isDirectCall) {
$mvc = $this->getMVCs();
$id = $mvc['this'][0].'_'.$mvc['this'][1].'_'.$mvc['this'][2];
$this->view->placeholder('dojo-onload')->captureStart();
echo 'if(dijit.byId("'.$id.'")) {
dijit.byId("default_ui_mainTabs").selectChild(dijit.byId("'.$id.'"));
}';
$this->view->placeholder('dojo-onload')->captureEnd();
}
//XXX HACK unclean work-around:
/**
* as written now, it always tries to dispatch twice the first
action on the stack, eg
* for the URL http://localhost/module1/index/index, the first two
dispatched actions are
* /module1/index/index, whereby Module1_IndexController extends
My_Action_Controller (self)
*/
if(!in_array($this_act,self::$instances,TRUE)) {
self::$instances[] = $this_act;
parent::dispatch($action);
}
else {
$this->getRequest()->setDispatched(TRUE);
}
}
public function postDispatch() {
$this->_isDirectCall = FALSE;
$this->_isStartAction = FALSE;
}
public function getMVCs() {
$fc = Zend_Controller_Front::getInstance();
//$this->getFrontController();
if(!$this->_mvcs) {
$this->_mvcs = array();
$this->_mvcs['default'] = array($fc->getDefaultModule(),
$fc->getDefaultControllerName(),
$fc->getDefaultAction()); // usually /default/index/index
//$t = $this->getRequest();
$t = $this->_request->getParams();
$this->_mvcs['url'] =
array($t['module'],$t['controller'],$t['action']);//this is what you see in
the URL
// $this->_mvcs['url'] =
array($t->getModuleName(),$t->getControllerName(),$t->getActionName());
}
$t = $fc->getRequest();
$this->_mvcs['this'] =
array($t->getModuleName(),$t->getControllerName(),$t->getActionName());//the
module/controller/action of this object
return $this->_mvcs;
}
}
Also note the hack "XXX HACK" comment in the code. The cause is certainly
not what I'm pushing on the action stack in my FC plugin, since I'm pushing
only "navbar" actions for every module's index controller. What causes it?
Could it be related to the section in the code "XXX: this fixes the previous
problem"?
If I wasnt't to do that, it would have rendered the wrong "navpane.phtml"
from the first module. So now it behaves similar, but for index.phtml, yet I
don't know why.
Just in case, here's my FC plugin:
<?php
require_once 'Zend/Controller/Plugin/Abstract.php';
/**
* Sets up a working environment
*
* Steps: 1. __construct, 2. initConfig() 3. routeStartup()
* 4. restartLogging() 5. initView() 6. initLayout() 7. initModules()
* @author flav
*
*/
class My_Plugin_Initialize extends Zend_Controller_Plugin_Abstract {
private $env;
public $logWriter;
public $logger;
public $config;
private $view;
public function __construct($env) {
$this->env = $env;
switch($env) {
case 'development':
$this->logWriter = new Zend_Log_Writer_Mock;// then firebug
in restartLogging
break;
case 'testing':
//TODO, ideally mock, then SQL in restartLogging()
break;
case 'production':
$this->logWriter = new Zend_Log_Writer_Null;
break;
default:
throw new Exception('Unknown environment');
}
$this->logger = new Zend_Log($this->logWriter);
$this->logger->info(__METHOD__);
$this->initConfig();
}
protected function initConfig() {
global $config_path;
$this->config = new Zend_Config_Ini($config_path.'/application.ini',
$this->env);
Zend_Registry::set('config',$this->config);
$this->logger->INFO(__METHOD__);
}
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
$this->restartLogging();
$router = Zend_Controller_Front::getInstance()->getRouter();
if('development' === APPLICATION_ENV) {
$router->addRoute('debugging',
new Zend_Controller_Router_Route_Regex(
'yap-project/application/modules/([a-zA-Z]+)/controllers/([a-zA-Z]+)Controller.php',
array(
'action' => 'index'),
array(
'module' => 1,
'controller' => 2
)
)
);
}
Zend_Controller_Front::getInstance()->getResponse()->renderExceptions(TRUE);
$this->initView()
->initLayout()
->initModules()
;
}
public function restartLogging() {
$this->logger->info('bootstrapping logger');
$events = $this->logWriter->events;
unset($this->logger,$this->logWriter);
switch($this->config->logWriter) {
case FALSE:
$this->logWriter = new Zend_Log_Writer_Null;
break;
case 'firebug':
$this->logWriter = new Zend_Log_Writer_Firebug;
break;
case 'stream':
$this->logWriter = new
Zend_Log_Writer_Stream($this->config->logStream);
break;
default:
$this->logWriter = new Zend_Log_Writer_Null;
throw new Zend_Log_Exception('unknown log writer');
}
$this->logger = new Zend_Log($this->logWriter);
$this->logger->addPriority('TABLE',8);
//TODO: delete filter
/**
$filter = new Zend_Log_Filter_Message('/---.+/i');
$this->logger->addFilter($filter);
/**/
Zend_Registry::set('logger',$this->logger);
foreach($events as $event) {
$this->logWriter->write($event);
}
}
public function initView() {
global $library_path; // as defined in bootstrap.php
$this->view = new Zend_View;
Zend_Dojo::enableView($this->view);
Zend_Dojo_View_Helper_Dojo::setUseDeclarative();
$this->view->addHelperPath($library_path .
'/My/View/Helper','My_View_Helper');
$this->view->baseUrl = rtrim($this->getRequest()->getBaseUrl(),'/');
$this->view->docType = $this->config->docType;
$this->view->headTitle()->setSeparator($this->config->titleSeparator)
->append($this->config->siteName);
$this->view->headMeta()->appendHttpEquiv('Content-Type',$this->config->contentType);
$renderer =
Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer');
$this->view->dojo()
->setDjConfig($this->config->dojo->djConfig->toArray())
//TODO switch between remote CDN and local based on $config
->setLocalPath($this->config->dojo->localPath)
->addStylesheetModule($this->config->dojo->stylesheetModule)
//->setCdnBase(Zend_Dojo::CDN_BASE_GOOGLE)
//->setCdnDojoPath(Zend_Dojo::CDN_DOJO_PATH_GOOGLE)
->enable();
return $this;
}
public function initLayout() {
$this->logger->info(__METHOD__);
$viewRenderer =
Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer');
$viewRenderer->setView($this->view);
$layout = Zend_Layout::startMvc(array(
'layoutPath' => $this->config->layoutPath
));
return $this;
}
public function initModules() {
Zend_Controller_Front::getInstance()->addModuleDirectory($this->config->modulesPath);
$actionstack =
Zend_Controller_Action_HelperBroker::getStaticHelper('ActionStack');
/**
* Instead of doing it here, do it in
My_Controller_Action::preDispatch().
* This way, 1. we can have Controller_Actions without showing the
* "homepage"; 2. we can show the "homepage" as the first tab
*/
//$actionstack->actionToStack('index','index','default');
$modules = $this->config->module->toArray();
//reverse it in order to achieve the same stack pop order as in .ini
$moduleOrder = array_reverse(explode('
',$this->config->moduleOrder));
foreach($moduleOrder as $module) {
$settings = $modules[$module];
if($settings['active'] && $settings['navbar']) {
//TODO: load each module's FC plugin if enabled (could be
used for per-module routing, etc)
$actionstack->actionToStack('navpane','index',$module);
}
}
return $this;
}
}
Thanks in advance,
--
Flavius