advertisement

Error Reporting in ZF2: form messages, custom error pages, logging

63 %
38 %
advertisement
Information about Error Reporting in ZF2: form messages, custom error pages, logging
Technology

Published on February 19, 2014

Author: maraspin

Source: slideshare.net

Description

Whenever users encounter errors, they do get frustrated. No matter if it's their fault or applications', risks that they'll lose interest is high. Hence it's not only important to keep track of errors, but also to prompt user with proper messages. In this presentation, given at the Zend Framework Day 2014 in Turin - Italy, I discuss about these issues and provide some hints for improving error reporting and handling.
advertisement

Error Reporting in Zend Framework 2 Zend Framework Day – Turin, Italy – 07/02/2014

STEVE MARASPIN

@maraspin

http://www.mvlabs.it/

http://friuli.grusp.org/ 5

WHY WORRY?

WE SCREW UP

WE ALL SCREW UP

Application Failures

Application Failures User Mistakes

INCREASED SUPPORT COST

ABANDONMENT

THE BOTTOM LINE

User Input = Mistake Source

Validation Handling

User Privacy Concerns • Over 40% of online shoppers are very concerned over the use of personal information • Public opinion polls have revealed a general desire among Internet users to protect their privacy Online Privacy: A Growing Threat. - Business Week, March 20, 2000, 96. Internet Privacy in ECommerce: Framework, Review, and Opportunities for Future Research - Proceedings of the 41st Hawaii International Conference on System Sciences - 2008

Validation Handling

Improved Error Message

+70% CONVERSIONS

21

Creating A Form in ZF2 <?php namespace ApplicationForm; use ZendFormForm; class ContactForm extends Form { public function __construct() { parent::__construct(); // ... } //... }

Creating A Form in ZF2 <?php namespace ApplicationForm; use ZendFormForm; class ContactForm extends Form { public function __construct() { parent::__construct(); // ... } //... }

Adding Form Fields public function init() { $this->setName('contact'); $this->setAttribute('method', 'post'); […].. $this->add(array('name' => 'email', 'type' => 'text', 'options' => array( 'label' => 'Name', ), ); $this->add(array('name' => 'message', 'type' => 'textarea', 'options' => array( 'label' => 'Message', ), ); //. […].. }

Adding Form Fields public function init() { $this->setName('contact'); $this->setAttribute('method', 'post'); […].. $this->add(array('name' => 'email', 'type' => 'text', 'options' => array( 'label' => 'Name', ), ); $this->add(array('name' => 'message', 'type' => 'textarea', 'options' => array( 'label' => 'Message', ), ); //. […].. }

VALIDATION

Form Validation: InputFilter

Validation: inputFilter <?php namespace ApplicationForm; […] class ContactFilter extends InputFilter { public function __construct() { // filters go here } }

Validation: inputFilter <?php namespace ApplicationForm; […] class ContactFilter extends InputFilter { public function __construct() { // filters go here } }

Required Field Validation $this->add(array( 'name' => 'email', 'required' => true, 'filters' => array( array( 'name' => 'StringTrim'), ), 'validators' => array( array( 'name' => 'EmailAddress', ) ) ));

Required Field Validation $this->add(array( 'name' => 'email', 'required' => true, 'filters' => array( array( 'name' => 'StringTrim'), ), 'validators' => array( array( 'name' => 'EmailAddress', ) ) ));

InputFilter Usage <?php namespace ApplicationController; […] class IndexController extends AbstractActionController { public function contactAction() { $form = new Contact(); $filter = new ContactFilter(); $form->setInputFilter($filter); return new ViewModel(array( 'form' => $form ); } }

InputFilter Usage <?php namespace ApplicationController; […] class IndexController extends AbstractActionController { public function contactAction() { $form = new Contact(); $filter = new ContactFilter(); $form->setInputFilter($filter); return new ViewModel(array( 'form' => $form ); } }

Standard Error Message

Improved Error Message

Error Message Customization $this->add(array( 'name' => 'email', 'required' => true, 'filters' => array( array('name' => 'StringTrim'), ), 'validators' => array( array('name' =>'NotEmpty', 'options' => array( 'messages' => array( NotEmpty::IS_EMPTY => 'We need an '. 'e-mail address to be able to get back to you' ), ), ), array('name' => 'EmailAddress'), ) ));

Error Message Customization $this->add(array( 'name' => 'email', 'required' => true, 'filters' => array( array('name' => 'StringTrim'), ), 'validators' => array( array('name' =>'NotEmpty', 'options' => array( 'messages' => array( NotEmpty::IS_EMPTY => 'We need an '. 'e-mail address to be able to get back to you' ), ), ), array('name' => 'EmailAddress'), ) ));

More than we need…

Check Chain $this->add(array( 'name' => 'email', 'required' => true, 'filters' => array( array('name' => 'StringTrim'), ), 'validators' => array( array('name' =>'NotEmpty', 'options' => array( 'messages' => array( NotEmpty::IS_EMPTY => 'We need an '. 'e-mail address to be able to get back to you' ), ), 'break_chain_on_failure' => true, ), array('name' => 'EmailAddress'), ) ));

Ok, good…

…but, what if?

Words to Avoid http://uxmovement.com/forms/how-to-make-your-form-error-messages-more-reassuring/

A few tips: • • • 45 Provide the user with a solution to the problem Do not use technical jargon, use terminology that your audience understands Avoid uppercase text and exclamation points

Improved message

Condensing N messages into 1 $this->add(array( 'name' => 'email', 'required' => true, 'validators' => array( array('name' =>'NotEmpty', 'options' => array( 'messages' => array( NotEmpty::IS_EMPTY => 'We need an '. 'e-mail address to be able to get back to you' )), 'break_chain_on_failure' => true, ), array('name' => 'EmailAddress', 'options' => array( 'message' => 'E-Mail address does not seem to be valid. Please make sure it contains the @ symbol and a valid domain name.', )));

Condensing N messages into 1 $this->add(array( 'name' => 'email', 'required' => true, 'validators' => array( array('name' =>'NotEmpty', 'options' => array( 'messages' => array( NotEmpty::IS_EMPTY => 'We need an '. 'e-mail address to be able to get back to you' )), 'break_chain_on_failure' => true, ), array('name' => 'EmailAddress', 'options' => array( 'message' => 'E-Mail address does not seem to be valid. Please make sure it contains the @ symbol and a valid domain name.', )));

Messages VS message $this->add(array( 'name' => 'email', 'required' => true, 'validators' => array( array('name' =>'NotEmpty', 'options' => array( 'messages' => array( NotEmpty::IS_EMPTY => 'We need an '. 'e-mail address to be able to get back to you' )), 'break_chain_on_failure' => true, ), array('name' => 'EmailAddress', 'options' => array( 'message' => 'E-Mail address does not seem to be valid. Please make sure it contains the @ symbol and a valid domain name.', )));

Translated Error Messages

Default Message Translation public function onBootstrap(MvcEvent $e) { $translator = $e->getApplication() ->getServiceManager()->get('translator'); $translator->addTranslationFile( 'phpArray', __DIR__ . '/../../vendor/zendframework/zendframework/'. 'resources/languages/it/Zend_Validate.php', 'default', 'it_IT' ); AbstractValidator::setDefaultTranslator($translator); }

Custom Message Translation $this->add(array( 'name' => 'email', 'required' => true, 'validators' => array( array('name' =>'NotEmpty', 'options' => array( 'translator' => $translator, 'message' => $translator->translate( 'Make sure your e-mail address contains the @ symbol and a valid domain name.' )), 'break_chain_on_failure' => true, ), )));

Form Factory $translator = $I_services->get('translator'); $I_form = new Contact(); $I_filter = new ContactFilter($translator); $I_form->setInputFilter($I_filter); return $I_form;

Translated Error Message

http://patterntap.com/pattern/funny-and-helpful-404-error-page-mintcom

56

Error Display Configuration Skeleton Applicaton Configuration

Hiding Exception Traces 'view_manager' => array( 'display_not_found_reason' => false, 'display_exceptions' => false, 'doctype' => 'HTML5', 'not_found_template' => 'error/404', 'exception_template' => 'error/index', 'template_map' => array( 'layout/layout' => __DIR__ . '/../view/layout/layout.phtml', 'application/index/index'=> __DIR__ . '/../view/application/index/index.phtml', 'error/404' => __DIR__ . '/../view/error/404.phtml', 'error/index' => __DIR__ . '/../view/error/index.phtml', ), 'template_path_stack' => array( __DIR__ . '/../view', ), ),

Hiding Exception Traces 'view_manager' => array( 'display_not_found_reason' => false, 'display_exceptions' => false, 'doctype' => 'HTML5', 'not_found_template' => 'error/404', 'exception_template' => 'error/index', 'template_map' => array( 'layout/layout' => __DIR__ . '/../view/layout/layout.phtml', 'application/index/index'=> __DIR__ . '/../view/application/index/index.phtml', 'error/404' => __DIR__ . '/../view/error/404.phtml', 'error/index' => __DIR__ . '/../view/error/index.phtml', ), 'template_path_stack' => array( __DIR__ . '/../view', ), ),

Custom Error Pages 'view_manager' => array( 'display_not_found_reason' => false, 'display_exceptions' => false, 'doctype' => 'HTML5', 'not_found_template' => 'error/404', 'exception_template' => 'error/index', 'template_map' => array( 'layout/layout' => __DIR__ . '/../view/layout/layout.phtml', 'application/index/index'=> __DIR__ . '/../view/application/index/index.phtml', 'error/404' => __DIR__ . '/../view/error/404.phtml', 'error/index' => __DIR__ . '/../view/error/index.phtml', ), 'template_path_stack' => array( __DIR__ . '/../view', ), ),

How about PHP Errors? class IndexController extends AbstractActionController { public function indexAction() { 1/0; return new ViewModel(); } }

How about PHP Errors? class IndexController extends AbstractActionController { public function indexAction() { 1/0; return new ViewModel(); } }

Early error detection principle

What can we do?

Handling PHP Errors public function onBootstrap(MvcEvent $I_e) { […] set_error_handler(array('ApplicationModule','handlePhpErrors')); } public static function handlePhpErrors($i_type, $s_message, $s_file, $i_line) { if (!($i_type & error_reporting())) { return }; throw new Exception("Error: " . $s_message . " in file " . $s_file . " at line " . $i_line); }

What happens now? class IndexController extends AbstractActionController { public function indexAction() { 1/0; return new ViewModel(); } }

Now… what if? class IndexController extends AbstractActionController { public function indexAction() { $doesNotExist->doSomething(); return new ViewModel(); } }

Fatal error: Call to a member function doSomething() on a non-object in /srv/apps/zfday/module/Application/src/Application/Controller/IndexController.php on line 20

FATAL ERRORS

Fatal Error Handling public function onBootstrap(MvcEvent $I_e) { […] $am_config = $I_application->getConfig(); $am_environmentConf = $am_config['mvlabs_environment']; // Fatal Error Recovery if (array_key_exists('recover_from_fatal', $am_environmentConf) && $am_environmentConf['recover_from_fatal']) { $s_redirectUrl = $am_environmentConf['redirect_url']; } $s_callback = null; if (array_key_exists('fatal_errors_callback', $am_environmentConf)) { $s_callback = $am_environmentConf['fatal_errors_callback']; } register_shutdown_function(array('ApplicationModule', 'handleFatalPhpErrors'), $s_redirectUrl, $s_callback); }

Fatal Error Handling /** * Redirects user to nice page after fatal has occurred */ public static function handleFatalPhpErrors($s_redirectUrl, $s_callback = null) { if (php_sapi_name() != 'cli' && @is_Array($e = @get_last())) { if (null != $s_callback) { // This is the most stuff we can get. // New context outside of framework scope $m_code = isset($e['type']) ? $e['type'] : 0; $s_msg = isset($e['message']) ? $e['message']:''; $s_file = isset($e['file']) ? $e['file'] : ''; $i_line = isset($e['line']) ? $e['line'] : ''; $s_callback($s_msg, $s_file, $i_line); } header("location: ". $s_redirectUrl); } return false; }

Fatal Error Handling 'mvlabs_environment' => array( 'exceptions_from_errors' => true, 'recover_from_fatal' => true, 'fatal_errors_callback' => function($s_msg, $s_file, $s_line) { return false; }, 'redirect_url' => '/error', 'php_settings' => array( 'error_reporting' => E_ALL, 'display_errors' => 'Off', 'display_startup_errors' => 'Off', ), ),

PHP Settings Conf 'mvlabs_environment' => array( 'exceptions_from_errors' => true, 'recover_from_fatal' => true, 'fatal_errors_callback' => function($s_msg, $s_file, $s_line) { return false; }, 'redirect_url' => '/error', 'php_settings' => array( 'error_reporting' => E_ALL, 'display_errors' => 'Off', 'display_startup_errors' => 'Off', ), ),

PHP Settings public function onBootstrap(MvcEvent $I_e) { […] foreach($am_phpSettings as $key => $value) { ini_set($key, $value); } }

NICE PAGE!

CUSTOMER SUPPORT TEAM REACTION http://www.flickr.com/photos/18548283@N00/8030280738

ENVIRONMENT DEPENDANT CONFIGURATION

During Deployment

Local/Global Configuration Files

During Deployment Runtime

Index.php // Application wide configuration $am_conf = $am_originalConf = require 'config/application.config.php'; // Environment specific configuration $s_environmentConfFile = 'config/application.'.$s_env.'.config.php'; if (file_exists($s_environmentConfFile) && is_readable($s_environmentConfFile)) { // Specific environment configuration merge $am_environmentConf = require $s_environmentConfFile; $am_conf = ZendStdlibArrayUtils::merge($am_originalConf, $am_environmentConf ); } // Additional Specific configuration files are also taken into account $am_conf["module_listener_options"]["config_glob_paths"][] = 'config/autoload/{,*.}' . $s_env . '.php'; ZendMvcApplication::init($am_conf)->run();

application.config.php 'modules' => array( 'Application', ),

Application.dev.config.php 'modules' => array( 'Application', 'ZendDeveloperTools', ),

Enabling Environment Confs // Application nominal environment $am_conf = $am_originalConf = require 'config/application.config.php'; // Environment specific configuration $s_environmentConfFile = 'config/application.'.$s_env.'.config.php'; // Do we have a specific configuration file? if (file_exists($s_environmentConfFile) && is_readable($s_environmentConfFile)) { // Specific environment configuration merge $am_environmentConf = require $s_environmentConfFile; $am_conf = ZendStdlibArrayUtils::merge($am_originalConf, $am_environmentConf ); } // Additional Specific configuration files are also taken into account $am_conf["module_listener_options"]["config_glob_paths"][] = 'config/autoload/{,*.}' . $s_env . '.php'; ZendMvcApplication::init($am_conf)->run();

Env Dependant Conf Files

index.php Check // What environment are we in? $s_env = getenv('APPLICATION_ENV'); if (empty($s_env)) { throw new Exception('Environment not set.'. ' Cannot continue. Too risky!'); }

Apache Config File <VirtualHost *:80> DocumentRoot /srv/apps/zfday/public ServerName www.dev.zfday.it SetEnv APPLICATION_ENV "dev" <Directory /srv/apps/zfday/public> AllowOverride FileInfo </Directory> </VirtualHost>

LOGGING http://www.flickr.com/photos/otterlove/8154505388/

Why Log? Troubleshooting • Stats Generation • Compliance • 95

Zend Log $logger = new ZendLogLogger; $writer = new ZendLogWriterStream('/var/log/app.log'); $logger->addWriter($writer); $logger->info('Informational message'); $logger->log(ZendLogLogger::EMERG, 'Emergency message');

Zend Log $logger = new ZendLogLogger; $writer = new ZendLogWriterStream('/var/log/app.log'); $logger->addWriter($writer); $logger->info('Informational message'); $logger->log(ZendLogLogger::EMERG, 'Emergency message'); ZendLogLogger::registerErrorHandler($logger); ZendLogLogger::registerExceptionHandler($logger);

Writers 98

Writers 99

Logrotate /var/log/app.log { missingok rotate 7 daily notifempty copytruncate compress endscript } 100

Writers 101

Writers 102

Writers 103

Zend Log Ecosystem 104

Filter Example $logger = new ZendLogLogger; $writer1 = new ZendLogWriterStream('/var/log/app.log'); $logger->addWriter($writer1); $writer2 = new ZendLogWriterStream('/var/log/err.log'); $logger->addWriter($writer2); $filter = new ZendLogFilterPriority(Logger::CRIT); $writer2->addFilter($filter); $logger->info('Informational message'); $logger->log(ZendLogLogger::CRIT, 'Emergency message');

Monolog use MonologLogger; use MonologHandlerStreamHandler; $log = new Logger('name'); $log->pushHandler(new StreamHandler('/var/log/app.log', Logger::WARNING)); $log->addWarning('Foo'); $log->addError('Bar');

Monolog Components 107

How to log? class IndexController { public function helloAction() { return new ViewModel('msg' =>"Hello!"); } }

Traditional Invokation

Logging Within Controller class GreetingsController { public function helloAction() { $I_logger = new Logger(); $I_logger->log("We just said Hello!"); return new ViewModel('msg' =>"Hello!"); } }

Single Responsability Violation class GreetingsController { public function helloAction() { $I_logger = new Logger(); $I_logger->log("We just said Hello!"); return new ViewModel('msg' =>"Hello!"); } }

Fat Controllers class GreetingsController { public function helloAction() { $I_logger = new Logger(); $I_logger->log("We just said Hello!"); $I_mailer = new Mailer(); $I_mailer->mail($s_msg); $I_queue = new Queue(); $I_queue->add($s_msg); return new ViewModel('msg' =>"Hello!"); } }

CROSS CUTTING CONCERNS

What can we do?

Handling Events class Module { public function onBootstrap(MvcEvent $e) { $eventManager = $e->getApplication() ->getEventManager(); $moduleRouteListener = new ModuleRouteListener(); $moduleRouteListener->attach($eventManager); $logger = $sm->get('logger'); $eventManager->attach('wesaidHello', function(MvcEvent $event) use ($logger) { $logger->log($event->getMessage()); ); } }

Triggering An Event class GreetingsController { public function helloAction() { $this->eventManager ->trigger('wesaidHello', $this, array('greeting' => 'Hello!') ); return new ViewModel('msg' => "Hello!"); } }

Traditional Invokation

Event Manager

OBSERVER http://www.flickr.com/photos/lstcaress/502606063/

Event Manager

Handling Framework Errors class Module { public function onBootstrap(MvcEvent $e) { $eventManager = $e->getApplication() ->getEventManager(); $moduleRouteListener = new ModuleRouteListener(); $moduleRouteListener->attach($eventManager); $logger = $sm->get('logger'); $eventManager->attach(MvcEvent::EVENT_RENDER_ERROR, function(MvcEvent $e) use ($logger) { $logger->info('An exception has Happened ' . $e->getResult()->exception->getMessage()); }, -200); ); } }

Event Manager

Stuff to take home 1. 2. 3. 123 When reporting errors, make sure to be nice with users Different error reporting strategies could be useful for different environments The event manager reduces coupling and provides flexibility

2 min intro

https://xkcd.com/208/

Starting Things Up input { stdin { } } output { stdout { codec => rubydebug } }

Starting Things Up input { stdin { } } output { stdout { codec => rubydebug } } java -jar logstash-1.3.3-flatjar.jar agent -f sample.conf

Integrated Elasticsearch input { file { path => ["/opt/logstash/example.log"] } } output { stdout { codec => rubydebug } elasticsearch { embedded => true } } java -jar logstash-1.3.3-flatjar.jar agent -f elastic.conf

Integrated Web Interface input { file { path => ["/opt/logstash/example.log"] } } output { stdout { codec => rubydebug } elasticsearch { embedded => true } } java -jar logstash.jar agent -f elastic.conf --web

Kibana

Thank you for your attention Stefano Maraspin @maraspin

@maraspin

Stefano Maraspin @maraspin

Add a comment

Related presentations

Presentación que realice en el Evento Nacional de Gobierno Abierto, realizado los ...

In this presentation we will describe our experience developing with a highly dyna...

Presentation to the LITA Forum 7th November 2014 Albuquerque, NM

Un recorrido por los cambios que nos generará el wearabletech en el futuro

Um paralelo entre as novidades & mercado em Wearable Computing e Tecnologias Assis...

Microsoft finally joins the smartwatch and fitness tracker game by introducing the...

Related pages

Slides in Software

Error Reporting in ZF2: form messages, custom error pages, logging. By: ngoanhtuan245 . Rationally boost your symfony2 application with caching ...
Read more

PHP: error_reporting - Manual - PHP: Hypertext Preprocessor

To enable error reporting for *ALL* error messages ... levels of error reporting with your custom error handlers is ... turn error logging ...
Read more

Errors & Logging - Laravel - The PHP Framework For Web ...

Errors & Logging; Events; ... Custom HTTP Error Pages; Logging; ... This contextual data will be formatted and displayed with the log message:
Read more

Rich Custom Error Handling with ASP.NET

Microsoft® ASP.NET. Summary: Adding your own custom error handling to ... logging errors to ... of rich error messages. Rich Custom Error Pages.
Read more

How to: Configure Microsoft Error Reporting

Microsoft Error Reporting supports custom mini ... Sample Application error message ... Do not display Microsoft Web page. Corporate Error Reporting ...
Read more

debugging - How to get useful error messages in PHP ...

How to get useful error messages in ... you don't want a "customer" seeing the error messages. ... of the page choose a parameter . error_reporting ...
Read more

HTTP Errors : The Official Microsoft IIS Site

HTTP Errors ... allows you to configure custom error messages for your Web ... each of the error message pages for your Web site to have ...
Read more