In this example, we illustrate the workflow of an MVC application. We trace the controller actions. We also trace dispatch methods executed before and after a helper or a plugin.
Components used in this example
- Zend_Application
- Zend_Application_Bootstrap_Bootstrap
- Zend_Controller_Action
- Zend_Controller_Action_HelperBroker
- Zend_Controller_Action_Helper_Abstract
- Zend_Controller_Front
- Zend_Controller_Plugin_Abstract
- Zend_Controller_Plugin_ErrorHandler
- Zend_Controller_Request_Abstract
Initialization of the application
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
Registration of my name space
protected function _initMyNamespace()
{
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace('My_');
}
Adding my helper if requested
protected function _initMyHelper()
{
empty($_COOKIE['My_Helper']) or
$helper = new My_Helper() and
Zend_Controller_Action_HelperBroker::addHelper($helper);
}
Registering my plugin if requested
protected function _initMyPlugin()
{
empty($_COOKIE['My_Plugin']) or
$front = Zend_Controller_Front::getInstance() and
$front->registerPlugin(new My_Plugin());
}
}
[production]
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
includePaths.library = APPLICATION_PATH "/../library"
bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
bootstrap.class = "Bootstrap"
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
resources.layout.layoutpath = APPLICATION_PATH "/layouts"
resources.layout.layout = workflow
[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
Edition controller
class EditionController extends Zend_Controller_Action
{
public function init()
{
My_Tracer::add('Connecting to edition tool...');
// [ some code to initialize the controller here ... ]
}
public function indexAction()
{
My_Tracer::add('Listing edition actions...');
// [ some code to process the action here ... ]
}
public function createAction()
{
My_Tracer::add('Creating document...');
// [ some code to process the action here ... ]
}
public function updateAction()
{
My_Tracer::add('Getting document...');
// [ some code to process the action here ... ]
$this->_forward('retrieve', 'storage');
}
public function deleteAction()
{
My_Tracer::add('Deleting document...');
// [ some code to process the action here ... ]
$this->_forward('remove', 'storage');
}
public function preDispatch()
{
$action = $this->_getParam('action');
$action != 'index' and My_Tracer::add('Edition action logged: ' . $action);
// [ some code to execute before any action here ... ]
}
}
Encryption controller
class EncryptionController extends Zend_Controller_Action
{
public function init()
{
My_Tracer::add('Connecting to encryption tool...');
// [ some code to initialize the controller here ... ]
}
public function indexAction()
{
My_Tracer::add('Listing encryptions...');
// [ some code to process the action here ... ]
}
public function selectAction()
{
My_Tracer::add('Selecting encryption...');
// [ some code to process the action here ... ]
}
public function encryptAction()
{
Zend_Controller_Action_HelperBroker::hasHelper('helper') and
$helper = $this->_helper->getHelper('Helper') and
$key = $helper->getUserKey();
My_Tracer::add('Encrypting document...');
// [ some code to process the action here ... ]
$this->_forward('store', 'storage');
}
public function decryptAction()
{
Zend_Controller_Action_HelperBroker::hasHelper('helper') and
$helper = $this->_helper->getHelper('Helper') and
$key = $helper->getUserKey();
My_Tracer::add('Decrypting document...');
// [ some code to process the action here ... ]
}
}
Error controller
class ErrorController extends Zend_Controller_Action
{
Processing the error- 404 error -- controller or action not found
- application error
public function errorAction()
{
$errors = $this->_getParam('error_handler');
switch ($errors->type) {
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
// 404 error -- controller or action not found
$this->getResponse()->setHttpResponseCode(404);
$this->view->message = 'Page not found';
break;
default:
// application error
$this->getResponse()->setHttpResponseCode(500);
$this->view->message = 'Application error';
break;
}
$this->view->exception = $errors->exception;
$this->view->request = $errors->request;
}
}
Main controller
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
My_Tracer::add('Listing controllers...');
// [ some code to process the action here ... ]
}
}
Storage controller
class StorageController extends Zend_Controller_Action
{
public function init()
{
My_Tracer::add('Connecting to data store...');
// [ some code to initialize the controller here ... ]
}
public function indexAction()
{
My_Tracer::add('Listing data stores...');
// [ some code to process the action here ... ]
}
public function selectAction()
{
My_Tracer::add('Selecting data store...');
// [ some code to process the action here ... ]
}
public function storeAction()
{
My_Tracer::add('Storing data in container...');
// [ some code to process the action here ... ]
}
public function retrieveAction()
{
My_Tracer::add('Retrieving data from container...');
$this->_forward('decrypt', 'encryption');
// [ some code to process the action here ... ]
}
public function removeAction()
{
My_Tracer::add('Removing container...');
// [ some code to process the action here ... ]
}
public function preDispatch()
{
$this->_getParam('action') != 'index' and
$this->_getParam('action') != 'select' and
My_Tracer::add('Opening container');
// [ some code to execute before any action here ... ]
}
public function postDispatch()
{
$this->_getParam('action') != 'index' and
$this->_getParam('action') != 'select' and
$this->_getParam('action') != 'delete' and
My_Tracer::add('Closing container');
// [ some code to execute after any action here ... ]
}
}
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title><?php My_Html::printTitle();?></title>
<style type="text/css">
body, td {
font-family: arial, sans-serif;
font-size: 0.9em;
}
.number {
text-align: center;
}
table {
border-spacing: 1em .5em;
}
.header {
font-weight: bold;
}
</style>
</head>
<body>
<p>EXAMPLE <?php My_Html::printTitle();?></p>
<hr />
<?php echo $this->layout()->content;?>
<p>
<a href="<?php echo $this->url(array('controller' => 'index', 'action'=>'index'));?>">
Home</a>
</p>
<input type="checkbox" onclick="setCheckbox('My_Helper', this.checked)"
<?php My_Html::printChecked(!empty($_COOKIE['My_Helper']));?> />
Enable My_Helper to display Helpers pre/post dispatch calls trace
<br />
<input type="checkbox" onclick="setCheckbox('My_Plugin', this.checked)"
<?php My_Html::printChecked(!empty($_COOKIE['My_Plugin']));?> />
Enable My_Plugin to display Plugins pre/post dispatch calls trace
<br /> <br />
<span style="font-size:75%">Change ZF Version</span>
<select style="font-size:75%"
onchange="setSelectOption('mvc-zf-version', this)">
<?php global $zfDir;
global $zfVersion;
foreach(array_map('basename', glob("$zfDir/*", GLOB_ONLYDIR)) as $dir) :?>
<option <?php My_Html::printSelected($zfVersion, $dir);?>>
<?php echo $dir;?>
</option>
<?php endforeach;?>
</select>
<hr />
<p>WORKFLOW / CALL TRACE</p>
<table>
<tr class="header">
<td class="number">#</td>
<td>Description</td>
<td>Class</td>
<td>Function</td>
<td>File</td>
</tr>
<?php foreach(My_Tracer::read() as $idx => $trace) :?>
<tr>
<td class="number"><?php echo ($idx + 1);?></td>
<td><?php echo $trace['comment'];?></td>
<td><?php echo $trace['class'];?></td>
<td><?php echo $trace['function'];?></td>
<td><?php echo $trace['file'];?></td>
</tr>
<?php endforeach;?>
</table>
<script type="text/javascript">
function setcookie (name, value, expires, path, domain, secure)
{
// [from phpjs.org]
return this.setrawcookie(name, encodeURIComponent(value), expires, path, domain, secure);
}
function setrawcookie (name, value, expires, path, domain, secure)
{
// [from phpjs.org]
if (expires instanceof Date) {
expires = expires.toGMTString();
} else if (typeof(expires) == 'number') {
expires = (new Date(+(new Date()) + expires * 1e3)).toGMTString();
}
var r = [name + "=" + value], s={}, i='';
s = {expires: expires, path: path, domain: domain};
for (i in s){
s[i] && r.push(i + "=" + s[i]);
}
return secure && r.push("secure"), this.window.document.cookie = r.join(";"), true;
}
function setCheckbox(name, checked)
{
// [stores checkbox status in cookie]
setcookie(name, Number(checked), 3600, '<?php echo COOKIE_PATH;?>');
location.reload();
}
function setSelectOption(name, select)
{
// [stores select option in cookie]
setcookie(name, select.options[select.selectedIndex].value, 3600,
'<?php echo COOKIE_PATH;?>');
}
</script>
</body>
</html>
<p>Create a new document</p>
<textarea rows="5" cols="30">Enter some text...</textarea><br/>
<p>
<a href="<?php echo $this->url(array('controller' => 'encryption', 'action'=>'encrypt'));?>">Save document</a><br />
<a href="<?php echo $this->url(array('action' => 'index'));?>">Back to edition</a><br />
</p>
<p><i>(Note: in a real application you would actually save the document.)</i></p>
<?php My_Tracer::add('Blank document loaded in editor'); ?>
<p>List of documents</p>
<ul>
<li>Zend Framework Manual</li>
<li>PHP Manual</li>
<li>...</li>
</ul>
<p>
<a href="<?php echo $this->url(array('action'=>'update'));?>">Read/update document</a><br />
<a href="<?php echo $this->url(array('action'=>'delete'));?>">Delete document</a><br />
<a href="<?php echo $this->url(array('action'=>'create'));?>">Create new document</a><br />
</p>
<p><i>(Note: in a real application you would actually be able
to select a document to read, update or delete.)</i></p>
<?php My_Tracer::add('Edition actions and documents listed'); ?>
<p>Read/update document</p>
<textarea rows="5" cols="30">Once upon a time ... and they all lived happily ever after.</textarea><br/>
<p>
<a href="<?php echo $this->url(array('controller' => 'encryption', 'action'=>'encrypt'));?>">Save document</a><br />
<a href="<?php echo $this->url(array('controller' => 'edition', 'action' => 'index'));?>">Back to edition</a><br />
</p>
<p><i>(Note: in a real application you would actually get the document you would have selected.)</i></p>
<?php My_Tracer::add('Document loaded in editor'); ?>
<p>List of encryptions</p>
<ul>
<li>Rijndael</li>
<li>Twofish</li>
<li>...</li>
</ul>
<p>
<a href="<?php echo $this->url(array('action'=>'select'));?>">Select algorithm</a><br />
</p>
<p><i>(Note: in a real application you would actually be able to select the algorithm.)</i></p>
<?php My_Tracer::add('Encryptions listed'); ?>
<p>Encryption selected</p>
<p>
<a href="<?php echo $this->url(array('action' => 'index'));?>">Back to encryption</a><br />
</p>
<p><i>(Note: in a real application the selected encryption would actually be saved.)</i></p>
<?php My_Tracer::add('Encryption selected'); ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Zend Framework Default Application</title>
</head>
<body>
<h1>An error occurred</h1>
<h2><?php echo $this->message ?></h2>
<?php if ('development' == APPLICATION_ENV): ?>
<h3>Exception information:</h3>
<p>
<b>Message:</b> <?php echo $this->exception->getMessage() ?>
</p>
<h3>Stack trace:</h3>
<pre><?php echo $this->exception->getTraceAsString() ?>
</pre>
<h3>Request Parameters:</h3>
<pre><?php echo var_export($this->request->getParams(), true) ?>
</pre>
<?php endif ?>
</body>
</html>
<p>Menu</p>
<p>
<a href="<?php echo $this->url(array('controller'=>'edition'));?>">Edition</a><br />
<a href="<?php echo $this->url(array('controller'=>'encryption'));?>">Encryption</a><br />
<a href="<?php echo $this->url(array('controller'=>'storage'));?>">Storage</a><br />
</p>
<p><i>
(Note: this is not a real application. You can navigate the menu
but actions are not actually performed.
However, calls are traced to illustrate the MVC workflow.)
</i></p>
<?php My_Tracer::add('Controllers listed'); ?>
<p>List of data stores</p>
<ul>
<li>Disk</li>
<li>Tape</li>
<li>Memory</li>
<li>...</li>
</ul>
<p>
<a href="<?php echo $this->url(array('action'=>'select'));?>">Select data store</a><br />
</p>
<p><i>(Note: in a real application you would actually be able to select the data store.)</i></p>
<?php My_Tracer::add('Data stores listed'); ?>
<p>Document deleted</p>
<p>
<a href="<?php echo $this->url(array('controller' => 'edition', 'action' => 'index'));?>">Back to edition</a>
</p>
<p><i>(Note: in a real application the document would actually be deleted.)</i></p>
<?php My_Tracer::add('Document deleted'); ?>
<p>Data store selected</p>
<p>
<a href="<?php echo $this->url(array('action' => 'index'));?>">Back to data store</a><br />
</p>
<p><i>(Note: in a real application the selected data store would actually be saved.)</i></p>
<?php My_Tracer::add('Data store selected'); ?>
<p>Document encrypted and saved</p>
<p>
<a href="<?php echo $this->url(array('controller' => 'edition', 'action' => 'index'));?>">Back to edition</a>
</p>
<p><i>(Note: in a real application the document would actually be saved.)</i></p>
<?php My_Tracer::add('Document saved'); ?>
An example of helper
class My_Helper extends Zend_Controller_Action_Helper_Abstract
{
Getting the user key
public function getUserKey()
{
My_Tracer::add('User key retrieved');
return 'The user key';
}
Logging user access after each action
public function postDispatch()
{
My_Tracer::add('User access logged');
// [ some code to execute after any action here ... ]
}
}
Displaying items
class My_Html
{
Displaying the title of the page based on the file name
public static function printTitle()
{
$trace = debug_backtrace();
$basename = basename($trace[0]['file'], '.phtml');
$title = ucwords(str_replace('-' , ' ', $basename));
$zfVersion = Zend_Version::VERSION;
$phpVersion = phpversion();
echo "ZfEx $title (ZF/$zfVersion PHP/$phpVersion)";
}
Displaying a check mark
public static function printChecked($value)
{
empty($value) or print 'checked="checked"';
}
Displaying the selected option
public static function printSelected($value, $target)
{
$value == $target and print 'selected="selected"';
}
}
An example of plugin
class My_Plugin extends Zend_Controller_Plugin_Abstract
{
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
My_Tracer::add('Verification user authenticated');
// [ some code to execute here ... ]
}
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
My_Tracer::add('Request logged for auditing');
// [ some code to execute here ... ]
}
public function dispatchLoopStartup(
Zend_Controller_Request_Abstract $request)
{
My_Tracer::add('Verification secure server available');
// [ some code to execute here ... ]
}
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
My_Tracer::add('Verification user authorized');
// [ some code to execute here ... ]
}
public function postDispatch(Zend_Controller_Request_Abstract $request)
{
My_Tracer::add('Action reported to publishing');
// [ some code to execute here ... ]
}
public function dispatchLoopShutdown()
{
My_Tracer::add('Access request email to user');
// [ some code to execute here ... ]
}
}
Tracing method calls
class My_Tracer
{
private static $_trace = array();
Adding the method to the list
public static function add($comment = '', $level = 2)
{
self::$_trace[] = $trace = self::get($comment, $level);
return $trace;
}
Getting the method details
public static function get($comment = '', $level = 1)
{
$trace = debug_backtrace();
$class = empty($trace[$level]['class'])? '' : "{$trace[$level]['class']}";
$function = empty($trace[$level]['function'])? '' : "{$trace[$level]['function']}()";
$path = dirname(APPLICATION_PATH);
$file = str_replace($path, '', $trace[$level - 1]['file']);
$file = str_replace('\\', '/', $file);
$file = substr($file, 1);
return array(
'comment' => $comment, 'class' => $class,
'function' => $function, 'file' => $file);
}
Reading the trace
public static function read()
{
return self::$_trace;
}
}
SetEnv APPLICATION_ENV development
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ /zfex/tryit/mvc-workflow/public/index.php [NC,L]
Entry point
- We define the path to the application directory.
- We define the application environment.
- We set the ZF version.
- We add the Zend Framework and specific resources to the include path.
- We define the application pathname for the cookie.
- We instantiate the application and we run the application.
// We define the path to the application directory.
defined('APPLICATION_PATH') or
define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));
// We define the application environment.
defined('APPLICATION_ENV') or
define('APPLICATION_ENV',
(getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production'));
// We set the ZF version.
$zfVersion = '1.9.7'; // tested with this version
$zfDir = '../../../ZendFramework';
isset($_COOKIE['mvc-zf-version']) and is_dir("$zfDir/{$_COOKIE['mvc-zf-version']}") and
$zfVersion = $_COOKIE['mvc-zf-version'];
// We add the Zend Framework and specific resources to the include path.
set_include_path(implode(PATH_SEPARATOR, array(
realpath(APPLICATION_PATH . '/../library'),
realpath("$zfDir/$zfVersion/library"),
get_include_path(),
)));
// We define the application pathname for the cookie.
$path = str_replace('\\', '/', realpath(dirname(__FILE__)));
preg_match('~www(/.+?/)public~', $path, $match) or die('Cannot set COOKIE_PATH!');
define('COOKIE_PATH', $match[1]);
require_once 'Zend/Application.php';
// We instantiate the application and we run the application.
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini'
);
$application->bootstrap()->run();
An interesting flowchart of the MVC can be found at http://www.kitpages.fr/cms/site/tutoriaux/sequence_globale.jpg.
ReplyDelete