
Continuing with the tutorial series, we will see how to debug the application. Debugging include easy to use methods of printing data on the screen, including ZFDebug Toolbar in order to manage all errors and queries, using the logger to log messages to Firebug, using a simple debug function that will place debug messages in your ZFDebug Toolbar, in a special Debug panel [...]
Continuing with the tutorial series, we will see how to debug the application. Debugging includes easy to use methods of printing data on the screen, including ZFDebug Toolbar in order to manage all errors and queries, using the logger to log messages to Firebug, using a simple debug function that will place debug messages in your ZFDebug Toolbar, in a special Debug panel, using a redirect debug function in order to see what is happening during your requests, using redirect in a Controller plugin.
1. Utils.php
In your WWW-ROOT, in your index.php, add the following line after the require_once ‘Zend/Application.php’ :
... // Custom functions to help development require_once ( APP_LIBRARY_PATH . DS . 'Utils.php'); ...
Create a file called Utils.php in your application_root/library with the following contents:
function debug ($object, $label = '')
{
Custom_Controller_Plugin_Debug::debug($object, $label);
}
function logger($message, $type = Zend_Log::INFO)
{
Custom_Controller_Plugin_Debug::logger($message, $type);
}
function array_key_exists_recursive($needle,$haystack)
{
foreach($haystack as $key=>$val)
{
if(is_array($val))
{
if ( array_key_exists_recursive($needle,$val))
{
return TRUE;
}
}
elseif ($val == $needle)
{
return TRUE;
}
}
return FALSE;
}
function is_multidimensional_array($a)
{
$rv = array_filter($a,'is_array');
if (count($rv)>0)
{
return TRUE;
}
else
{
return FALSE;
}
}
function array_equal($a, $b)
{
return (is_array($a) && is_array($b) && array_diff($a, $b) === array_diff($b, $a));
}
function array_identical($a, $b)
{
return (is_array($a) && is_array($b) && array_diff_assoc($a, $b) === array_diff_assoc($b, $a));
}
function pr($val)
{
$debug_backtrace = debug_backtrace();
echo 'Debug called from ' . $debug_backtrace[1]['file'] . ' (line ' . $debug_backtrace[1]['line'] . ')';
echo "< pre>";
print_r($val);
echo "< /pre>";
}
function debug_redirect($url)
{
$debug_backtrace = debug_backtrace();
$file = '< strong> ' . $debug_backtrace[0]['file'] . '< /strong>';
$line = '< strong>' . $debug_backtrace[0]['line'] . '< /strong>';
echo '< div style="padding:15px 30px; margin:0px; text-align:center; font-size:16px; background-color:#ccc; ">Should redirect to: < a href="'. $url . '">' . $url . '< /a>< /div>';
echo '< div style="padding:15px 30px; margin:0px; text-align:center; font-size:16px; background-color:#ccc; ">Called from ' . $file . ', line ' . $line . '< /div>';
echo '< div style="background-color:yellow; border:1px solid red; padding:5px 10px; margin:20px 0px">';
echo '< pre>COOKIES:';
print_r($_COOKIES);
echo '< /pre>';
echo '< pre>SESSION:';
print_r($_SESSION);
echo '< /pre>';
echo '< pre>SERVER:';
print_r($_SERVER);
echo '< /pre>';
echo '< /div>';
exit();
}
Let us examine a bit what these functions do:
We will skip the debug and logger for the moment.
The function array_key_exists_recursive is a convenience method i had to write in order to check if a key exists in a multidimensional array.
The function is_multidimensional_array is a method to check if an array is uni dimensional or not.
The function pr is a short way to call print_r but without minding of the < pre> < /pre> tags and also points out where did you call the function from (remember those days where you had 99999 lines of debug on your screen and you were asking yourself were did you put all those echos/print_r’s
?)
The function debug_redirect takes an url as an argument, and simulates a redirect. By simulates I mean that, it doesn’t really redirect rather than print on the screen that in production mode, it would redirect and you can see where it would redirect. Moreover, it tells you all the information from your current session so that you can view exactly what happens just before the redirect was called. It also tells you where you called the redirect method.
3. The Bootstrap
In order to continue, we will need a way to tell the application we want the debugging to be enabled or not. But how do we do this?
Easiest way is to have a DEBUG constant set to TRUE or FALSE (or different levels if you wish). But what about in production mode? How can we debug there?
One way is to have an IP enabled DEBUG constant. An easier method is to enable DEBUG if you have a certain cookie with a VERY secret hash key.
Of course, the best practice is to not have debug enabled at all in production ENV, but if you really need it, then having at least the cookie is required. You can make it more secure by adding the IP check also.
Here is how this would look like in your Bootstrap.php
...
#initializes the DEBUG constant to true or false based on config. settings and/or cookie
#and stores a copy of the Zend_Logger in the Registry for future references
protected function _initDebug()
{
$config = Zend_Registry::get('config');
if (isset($config->settings->debug->enabled))
{
if ($config->settings->debug->enabled == TRUE)
{
define('DEBUG', TRUE);
}
else
{
if (isset($config->settings->debug->cookie))
{
$debug_cookie = $config->settings->debug->cookie;
if (array_key_exists($debug_cookie,$_COOKIE))
{
define('DEBUG', TRUE);
}
}
}
}
if (FALSE === defined('DEBUG'))
{
define('DEBUG', FALSE);
}
$logger = new Zend_Log();
$writer = new Zend_Log_Writer_Firebug();
$logger->addWriter($writer);
Zend_Registry::set('logger', $logger);
}
...
4. The application.ini
In order to use the method from the bootstrap, we need to do the required changes in the application.ini :
[bootstrap] ... settings.debug.cookie = "some_very_very_secret_hash_key_here_that_will_be_very_hard_to_add_in_the_cookie" ... [production : bootstrap] phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 settings.debug.enabled = false ... [development : testing] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 settings.debug.enabled = true
And that is about it.
5. Controller Plugins
In order for the debug_redirect function to work, you will need the following code. Remember the redirect helper method that was available to all your controllers (from the file Utils.php) ?
Here is a new code for it:
/**
* Helper method to redirect to a specific action or controller or url
*
* @param string $controller / $url which contains http in its composition
* @param string $action
* @param array $params
*/
public function redirect($controller = 'index', $action = 'index', $module = 'frontoffice', $params = array(), $route = null, $reset = true )
{
$this->_redirect = $this->_helper->getHelper('Redirector');
$current_controller = $this->_getParam('controller');
$current_action = $this->_getParam('action');
$current_module = $this->_getParam('module');
if (strstr($controller, 'http'))
{
if (DEBUG && (!$this->_request->isXmlHttpRequest() && !isset($_GET['ajax'])))
{
debug_redirect($controller);
}
else
{
return $this->_redirect($controller, array('code' => 301));
}
}
if (DEBUG && (!$this->_request->isXmlHttpRequest() && !isset($_GET['ajax'])))
{
if ($route !== null)
{
$url = 'http://' . $_SERVER['HTTP_HOST']
. $this->view->url(array_merge(array('controller' => $controller, 'action' => $action, 'module' => $module), $params), $route, $reset);
}
else
{
$url = 'http://' . $_SERVER['HTTP_HOST']
. $this->view->url(array_merge(array('controller' => $controller, 'action' => $action, 'module' => $module), $params));
}
debug_redirect($url);
}
else
{
if ($route !== null)
{
$params = array_merge(array('action' => $action,
'controller' => $controller,
'module' => null), $params);
return $this->_redirect->setCode(301)
->setExit(true)
->gotoRoute($params, $route, $reset);
}
return $this->_redirect->setCode(301)
->setExit(true)
->gotoSimpleAndExit($action,
$controller,
$module,
$params);
}
}
Basically, if you have DEBUG enabled and you are not requesting the page via AJAX (either real AJAX or fake via $_GET of “ajax”) then it will use the debug_redirect function rather than the Zend Redirect. Mind you that when doing Unit Testing, you should *not* have DEBUG enabled or at least not use use this function at all for obvious reasons.
6. ZFDebug
The biggest addition to our debugging arsenal now comes with ZFDebug (which you can download here: http://code.google.com/p/zfdebug/).
ZFDebug is a Symfony like debug toolbar which can be configured very easily and provides handy information’s about your application. You can read more about it on the code.google.com. But in the meantime, let us see how we can include it:
Firsts things are first: Add your ZFDebug in your library folder like this:
/library/Custom
/Library/ZFDebug
In your application.ini, add the following namespace to the autoloader:
... Autoloadernamespaces[] = "Custom_" Autoloadernamespaces[] = "ZFDebug_"
Finally, in your Bootstrap.php add the following method:
...
#initializses the ZFDebug if DEBUG is ON
protected function _initZFDebug()
{
if (!DEBUG)
{
return FALSE;
}
$options = array(
'plugins' => array('Variables',
'ZFDebug_Controller_Plugin_Debug_Plugin_Debug' => array('tab' => 'Debug',
'panel' => ''),
'ZFDebug_Controller_Plugin_Debug_Plugin_Auth',
'Database',
'Registry',
'Exception')
);
# Setup the cache plugin
if (Zend_Registry::isRegistered('cache'))
{
$cache = Zend_Registry::get('cache');
$options['plugins']['Cache']['backend'] = $cache->getBackend();
}
# Setup the databases
$resource = $this->getPluginResource('multidb');
$databases = Zend_Registry::get('config')->resources->multidb;
foreach ($databases as $name => $adapter)
{
$db_adapter = $resource->getDb($name);
$options['plugins']['Database']['adapter'][$name]= $db_adapter;
}
# Init the ZF Debug Plugin
$debug = new ZFDebug_Controller_Plugin_Debug($options);
$this->bootstrap('frontController');
$frontController = $this->getResource('frontController');
$frontController->registerPlugin($debug);
}
...
7. Plugins for ZFDebug
We added a custom tab to the ZFDebug. In order to make it work, go to the Debug.php from ZFDebug/Controller/Plugin/
and replace your dispatchLoopShutdown() method with the following:
/**
* Defined by Zend_Controller_Plugin_Abstract
*/
public function dispatchLoopShutdown()
{
$html = '';
if ($this->getRequest()->isXmlHttpRequest())
return;
/**
* Creating menu tab for all registered plugins
*/
foreach ($this->_plugins as $plugin)
{
if (strtolower($plugin->getTab()) == 'debug')
{
$debug_session = new Zend_Session_Namespace(Custom_Controller_Plugin_Debug::DEBUG_NAMESPACE);
if (isset($debug_session->debug))
{
$plugin->setPanel( $debug_session->debug);
}
}
$panel = $plugin->getPanel();
if ($panel == '') {
continue;
}
/* @var $plugin ZFDebug_Controller_Plugin_Debug_Plugin_Interface */
$html .= '< div id="ZFDebug_' . $plugin->getIdentifier()
. '" class="ZFDebug_panel">' . $panel . '< /div>';
}
$html .= '< div id="ZFDebug_info">';
/**
* Creating panel content for all registered plugins
*/
foreach ($this->_plugins as $plugin)
{
$tab = $plugin->getTab();
if ($tab == '') {
continue;
}
if (strtolower($plugin->getTab()) == 'debug' && $plugin->getPanel() != '')
{
$style = 'style="background-color:yellow; font-weight:bold;"';
Zend_Session::namespaceUnset(Custom_Controller_Plugin_Debug::DEBUG_NAMESPACE);
}
else
{
$style = '';
}
/* @var $plugin ZFDebug_Controller_Plugin_Debug_Plugin_Interface */
$html .= '< span ' .$style .' class="ZFDebug_span clickable" onclick="ZFDebugPanel(\'ZFDebug_' . $plugin->getIdentifier() . '\');">';
$html .= '< mg src="' . $this->_icon($plugin->getIdentifier()) . '" style="vertical-align:middle" alt="' . $plugin->getIdentifier() . '" title="' . $plugin->getIdentifier() . '" /> ';
$html .= $tab . '< /span>';
}
$html .= '< span class="ZFDebug_span ZFDebug_last clickable" id="ZFDebug_toggler" onclick="ZFDebugSlideBar()">«< /span>';
$html .= '< /div>';
$this->_output($html);
}
Go into your /ZFDebug/Controller/Plugin/Debug/Plugin/Auth.php and at replace the getTab() method with the following:
/**
* Gets menu tab for the Debugbar
*
* @return string
*/
public function getTab()
{
$username = 'Not Authed';
$role = 'Unknown Role';
if(!$this->_auth->hasIdentity())
{
return 'Not authorized';
}
$identity = $this->_auth->getIdentity();
if (is_object($identity))
{
$username = $identity->name;
}
else
{
$username = $identity['name'];
}
return ' ' . $username;
}
Create a new file called Debug.php in your /ZFDebug/Controller/Plugin/Debug/Plugin/ with the following contents:
class ZFDebug_Controller_Plugin_Debug_Plugin_Debug implements ZFDebug_Controller_Plugin_Debug_Plugin_Interface
{
/**
* @var string
*/
protected $_tab = '';
/**
* @var string
*/
protected $_panel = '';
/**
* Contains plugin identifier name
*
* @var string
*/
protected $_identifier = 'text';
/**
* Create ZFDebug_Controller_Plugin_Debug_Plugin_Debug
*
* @paran array $options
* @return void
*/
public function __construct(array $options = array())
{
if (isset($options['tab'])) {
$this->setTab($options['tab']);
}
if (isset($options['panel'])) {
$this->setPanel($options['panel']);
}
}
/**
* Gets identifier for this plugin
*
* @return string
*/
public function getIdentifier()
{
return $this->_identifier;
}
/**
* Sets identifier for this plugin
*
* @param string $name
* @return ZFDebug_Controller_Plugin_Debug_Plugin_Debug Provides a fluent interface
*/
public function setIdentifier($name)
{
$this->_identifier = $name;
return $this;
}
/**
* Gets menu tab for the Debugbar
*
* @return string
*/
public function getTab()
{
return $this->_tab;
}
/**
* Gets content panel for the Debugbar
*
* @return string
*/
public function getPanel()
{
return $this->_panel;
}
/**
* Sets tab content
*
* @param string $tab
* @return ZFDebug_Controller_Plugin_Debug_Plugin_Debug Provides a fluent interface
*/
public function setTab($tab)
{
$this->_tab = $tab;
return $this;
}
/**
* Sets panel content
*
* @param string $panel
* @return ZFDebug_Controller_Plugin_Debug_Plugin_Debug Provides a fluent interface
*/
public function setPanel($panel)
{
$this->_panel = $panel;
return $this;
}
}
However, this will not work until we create the debug controller plugin:
Go into your application_root/library/Custom/Controller/Plugin/ and create the file Debug.php with the following contents:
final class Custom_Controller_Plugin_Debug extends Zend_Controller_Plugin_Abstract
{
const DEBUG_NAMESPACE = 'DEBUG_Plugin';
public static function debug($object, $label = '')
{
if (FALSE === DEBUG)
{
return FALSE;
}
if (FALSE === Zend_Session::isStarted())
{
Zend_Session::start();
}
$debug_session = new Zend_Session_Namespace(self::DEBUG_NAMESPACE);
$debug_backtrace = debug_backtrace();
if ($object === FALSE) $object = 'FALSE';
if ($object === TRUE) $object = 'TRUE';
if ($label == '') $label = 'DEBUG';
$debug = '< div id="debug_wrapper" style="clear: both; text-align: left; width: 98%; margin:10px auto; background: #FFFFD7; border: 1px dotted #008200; font-family: Tahoma; font-size: 12px;">'; // Start of debug_wrapper
$debug .= '< div id="debug_content" style="padding: 10px 10px 0px 10px;">';
$debug .= '< div id="debug_location" style="font-weight: bold; color: #008200; border-bottom: 1px dotted #008200; padding-bottom: 10px;">'; // Start of debug_location
$debug .= '< span style="color:#FFF; background-color:green;padding:0px 10px;margin:0px 10px 0px 0px; border:1px solid green; -moz-border-radius: 5px; -webkit-border-radius: 5px;">'.strtoupper($label).'< /span>Debug called from ' . $debug_backtrace[1]['file'] . ' (line ' . $debug_backtrace[1]['line'] . ')';
$debug .= '< /div>'; // End of debug_location
$debug .= '< pre>';
$debug .= print_r($object, true);
$debug .= '< /pre>';
$debug .= '< /div>'; // End of debug_content
$debug .= '< /div>'; // End of debug_wrapper
Custom_Controller_Plugin_Debug::logger($object, null);
$debug_session->debug = isset($debug_session->debug) ? $debug_session->debug . $debug : $debug;
}
public static function logger($message, $type = Zend_Log::INFO, $extras = array())
{
if (Zend_Registry::isRegistered('logger') === TRUE && DEBUG === TRUE)
{
Zend_Registry::get('logger')->log($message, Zend_Log::INFO, $extras);
}
}
}
What does this method exactly do ?
Well, several things:
It provides 2 public static methods that can be called from anywhere in your application to debug or log to firebug a message.
These 2 public static methods are used in Utils.php for an easier way to access them:
debug() runs the Custom_Controller_Plugin_Debug::debug() and logger() runs Custom_Controller_Plugin_Debug::logger()
The debug method itself takes a message and/or an optional label, wraps it nicely in a colored DIV and puts it in the SESSION.
Once the ZFDebug output is shown on the screen, it removes the debug from the SESSION storage.
Why so complicated you may ask? The obvious reason is that, when you are doing redirects or errors happen (and you get redirected to the error controller) you would still want to get your debug information in order to find out what the heck happens. This way, you could go through an unlimited number of redirects and at the end, the debug would just contain your full debug gathered on the way to the render.
Try it our yourself!
8. Error controller
Let’s see now how can our Error Controller be extended to manage more advanced issues.
We want it to display all sorts of errors if we are in DEBUG mode or else, in case of 500 internal server errors, mail us the error stack trace and details.
In your ErrorController.php put the following content:
/**
* @name ErrorController
* @desc The controller to serve 404 and 500 errors and debugging stacktrace on DEV ENV
* or mail with the stacktrage on 500 errors in anything else than DEV ENV;
*
* @author Andrei
* @filesource application/module_name/controllers/ErrorController.php
* @version 1.0.0
*/
Class ErrorController extends Frontoffice_Library_Controller_Action_Abstract
{
private $_notifier;
private $_error;
private $_environment;
public function init()
{
parent::init();
$bootstrap = $this->getInvokeArg('bootstrap');
$environment = $bootstrap->getEnvironment();
$error = $this->_getParam('error_handler');
$mailer = new Zend_Mail();
$session = new Zend_Session_Namespace();
$cookies = $_COOKIE;
$this->_notifier = new Custom_Service_Notifier_Error(
$environment,
$error,
$mailer,
$session,
$cookies,
$_SERVER
);
$this->_error = $error;
$this->_environment = $environment;
}
public function errorAction()
{
switch ($this->_error->type) {
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
$this->getResponse()->setHttpResponseCode(404);
$this->view->message = 'Uh oh, we can\'t seem to find that page you wanted!';
$this->_applicationError();
break;
default:
$this->getResponse()->setHttpResponseCode(500);
$this->view->message = 'Looks like something\'s gone wrong! Please refresh the page - if the problem persists please report the error';
$this->_applicationError();
break;
}
$this->view->headTitle()->prepend( $this->view->code . ' Error' );
}
private function _applicationError()
{
$fullMessage = $this->_notifier->getFullErrorMessage();
$this->view->stack = nl2br($fullMessage);
$this->_notifier->notify();
}
}
Go into your application root/library/Custom/Service/Notifier (create it if you don’t have it) and creae a new file called Error.php with the following contents:
Class Custom_Service_Notifier_Error
{
protected $_environment;
protected $_mailer;
protected $_session;
protected $_cookies;
protected $_error;
protected $_profilers;
public function __construct(
$environment,
ArrayObject $error,
Zend_Mail $mailer,
Zend_Session_Namespace $session,
Array $cookies,
Array $server)
{
$this->_environment = $environment;
$this->_mailer = $mailer;
$this->_error = $error;
$this->_session = $session;
$this->_cookies = $cookies;
$this->_server = $server;
}
public function getFullErrorMessage()
{
$message = '';
if (!empty($this->_server['SERVER_ADDR'])) {
$message .= "Server IP: " . $this->_server['SERVER_ADDR'] . "\n";
}
if (!empty($this->_server['HTTP_USER_AGENT'])) {
$message .= "User agent: " . $this->_server['HTTP_USER_AGENT'] . "\n";
}
if (!empty($this->_server['HTTP_X_REQUESTED_WITH'])) {
$message .= "Request type: " . $this->_server['HTTP_X_REQUESTED_WITH'] . "\n";
}
$message .= "Server time: " . date("Y-m-d H:i:s") . "\n";
$message .= "RequestURI: " . $this->_error->request->getRequestUri() . "\n";
if (!empty($this->_server['HTTP_REFERER'])) {
$message .= "Referer: " . $this->_server['HTTP_REFERER'] . "\n";
}
$message .= "Message: " . $this->_error->exception->getMessage() . "\n\n";
$message .= "Trace:\n" . $this->_error->exception->getTraceAsString() . "\n\n";
$message .= "Request data: " . var_export($this->_error->request->getParams(), true) . "\n\n";
$it = $this->_session->getIterator();
$message .= "Session data:\n\n";
foreach ($it as $key => $value) {
$message .= $key . ": " . var_export($value, true) . "\n";
}
$message .= "\n";
$message .= "Cookie data:\n\n";
foreach ($this->_cookies as $key => $value) {
$message .= $key . ": " . var_export($value, true) . "\n";
}
$message .= "\n";
return $message;
}
public function notify()
{
if (!in_array($this->_environment, array('production', 'testing'))) {
return false;
}
$this->_mailer->setFrom('do-not-reply@YUUR_APP_DOMAIN');
$this->_mailer->setSubject("Exception on Zend Application Implementation");
$this->_mailer->setBodyText($this->getFullErrorMessage());
$this->_mailer->addTo('YOUR_SUPPORT_EMAIL_ADDRESS');
return $this->_mailer->send();
}
}
9. View Helpers
Here are some view helpers that will aid your development:
Go into your modules/MODULE_NAME/views/helpers and create the following file with the following contents:
//Xml.php
class Zend_View_Helper_ArrayToXml implements Zend_View_Helper_Interface
{
public $view;
public function setView(Zend_View_Interface $view)
{
$this->view = $view;
}
public function direct() {}
public function arrayToXml($value)
{
$response = $this->_convertValueToXml($value);
return $response;
}
protected function _convertValueToXml($value)
{
$str = '';
if (!is_array($value))
{
$str .= $value;
}
else
{
foreach ($value as $tag => $tag_value)
{
$str .= '<' . $tag . '>';
$str .= $this->_convertValueToXml($tag_value);
$str .= '' . $tag . '>';
}
}
return $str;
}
}
This helper plugin helps you convert an array to an xml as following:
If you have an array like this :
test => array(test2 => array( test3))
then you the helper would convert it to something like:
< test>< test2>test3< /test2>< /test>
10. Conclusion
The basic module setup and this debugging system should help you manage any application for any difficulty level.
The next tutorial will consist of creating a login page and signup page and protecting them with ReCaptcha.
Until next time,
Happy Coding!





