You are on page 1of 50

php | tek - Chicago, May 16-18, 2007

Exceptional PHP

When Good Code Goes Bad

Jeff Moore

I Want to Improve How My Code Deals With Errors

What Impression Does this Make?

What Impression Does this Make? Warning : mysql_connect(): Too many connections in /www2/ anonymous/php/_Database.inc on

Warning: mysql_connect(): Too many connections in /www2/ anonymous/php/_Database.inc on line 18 Too many connections

What Can Go Wrong?

Programming errorsWhat Can Go Wrong? Violation of preconditions Violation of postconditions Violation of invariants Invalid input Resource

Violation of preconditionsWhat Can Go Wrong? Programming errors Violation of postconditions Violation of invariants Invalid input Resource failures

Violation of postconditionsCan Go Wrong? Programming errors Violation of preconditions Violation of invariants Invalid input Resource failures

Violation of invariantsViolation of preconditions Violation of postconditions Invalid input Resource failures Hardware failures Who knows

Invalid inputpreconditions Violation of postconditions Violation of invariants Resource failures Hardware failures Who knows what else?

Resource failuresof preconditions Violation of postconditions Violation of invariants Invalid input Hardware failures Who knows what else?

Hardware failuresof preconditions Violation of postconditions Violation of invariants Invalid input Resource failures Who knows what else?

Who knows what else?of preconditions Violation of postconditions Violation of invariants Invalid input Resource failures Hardware failures

Formally Defining Correctness

Precondition Must be true before the code can be ExecutedFormally Defining Correctness Invariants Must be true while the code is Executed Postconditions Must be true

InvariantsPrecondition Must be true before the code can be Executed Must be true while the code

Must be true before the code can be Executed Invariants Must be true while the code

Must be true while the code is ExecutedMust be true before the code can be Executed Invariants Postconditions Must be true after the

Postconditions Must be true after the code is done executingCorrectness Precondition Must be true before the code can be Executed Invariants Must be true while

Executed Invariants Must be true while the code is Executed Postconditions Must be true after the

Basic Strategies

SuppressBasic Strategies Inform React Propagate

InformBasic Strategies Suppress React Propagate

ReactBasic Strategies Suppress Inform Propagate

PropagateBasic Strategies Suppress Inform React

Suppress

Not everything is an errorSuppress Some error conditions don’t matter Best practices Don’t suppress legitimate errors Document your intention to

Some error conditions don’t matterSuppress Not everything is an error Best practices Don’t suppress legitimate errors Document your intention to

Best practiceseverything is an error Some error conditions don’t matter Don’t suppress legitimate errors Document your intention

Don’t suppress legitimate errorsSuppress Not everything is an error Some error conditions don’t matter Best practices Document your intention

Document your intention to suppressSuppress Not everything is an error Some error conditions don’t matter Best practices Don’t suppress legitimate

Inform

Some errors are expectedInform Inform appropriate party via API UI Log

Inform appropriate party via API UI LogInform Some errors are expected

Inform Some errors are expected Inform appropriate party via API UI Log
Inform Some errors are expected Inform appropriate party via API UI Log
Inform Some errors are expected Inform appropriate party via API UI Log

React

RollbackReact Clean up Repair Try something else

Clean upReact Rollback Repair Try something else

RepairReact Rollback Clean up Try something else

Try something elseReact Rollback Clean up Repair

React

ob_start(); try { show_feed_sidebar(); ob_end_flush(); } catch (Exception $e) { ob_end_clean(); echo “Unavailable”;

}

Propagate

Propagate Error Detection Error Handling

Error Detection

Propagate Error Detection Error Handling
Propagate Error Detection Error Handling

Error Handling

Context Matters

Low LevelContext Matters Not enough perspective to know what to do High Level Too late and too

Not enough perspective to know what to doContext Matters Low Level High Level Too late and too far away to react

High LevelContext Matters Low Level Not enough perspective to know what to do Too late and too

Too late and too far away to reactContext Matters Low Level Not enough perspective to know what to do High Level

Selecting a Strategy

Strategies are not mutually exclusiveSelecting a Strategy Context matters Banking versus bookmarking

Context mattersSelecting a Strategy Strategies are not mutually exclusive Banking versus bookmarking

Banking versus bookmarkingSelecting a Strategy Strategies are not mutually exclusive Context matters

Detecting Errors

Detecting Errors Its all about the If

Its all about the If

Defensive Programming

Check everything, trust nothingDefensive Programming Check pre-conditions and invariants Most errors conditions never occur More code Slower code Error

Check pre-conditions and invariantsDefensive Programming Check everything, trust nothing Most errors conditions never occur More code Slower code Error

Most errors conditions never occur More code Slower code Error checking code can have bugs, tooDefensive Programming Check everything, trust nothing Check pre-conditions and invariants

and invariants Most errors conditions never occur More code Slower code Error checking code can have
and invariants Most errors conditions never occur More code Slower code Error checking code can have
and invariants Most errors conditions never occur More code Slower code Error checking code can have

Trusting Your Caller

Trust your caller to get it rightTrusting Your Caller Document pre-conditions and invariants Faster code Leaner code Harder to debug

Document pre-conditions and invariantsTrusting Your Caller Trust your caller to get it right Faster code Leaner code Harder to

Faster codeTrusting Your Caller Trust your caller to get it right Document pre-conditions and invariants Leaner code

Leaner codeTrusting Your Caller Trust your caller to get it right Document pre-conditions and invariants Faster code

Harder to debugTrusting Your Caller Trust your caller to get it right Document pre-conditions and invariants Faster code

Which Approach?

Which Approach? Defend Defend yourself at package boundaries Banking Trust Trust within your own package Don’t

Defend

Defend yourself at package boundariesWhich Approach? Defend Banking Trust Trust within your own package Don’t trust another organization Bookmarking

BankingWhich Approach? Defend Defend yourself at package boundaries Trust Trust within your own package Don’t trust

Defend Defend yourself at package boundaries Banking Trust Trust within your own package Don’t trust another

Trust

Trust within your own packageWhich Approach? Defend Defend yourself at package boundaries Banking Trust Don’t trust another organization Bookmarking

Don’t trust another organizationWhich Approach? Defend Defend yourself at package boundaries Banking Trust Trust within your own package Bookmarking

BookmarkingDefend yourself at package boundaries Banking Trust Trust within your own package Don’t trust another organization

Defending Exit Points

Every function or method call is an exit pointDefending Exit Points Do you verify the post conditions and invariants after the call? A.K.A Check

Do you verify the post conditions and invariants after the call?Exit Points Every function or method call is an exit point A.K.A Check return values for

verify the post conditions and invariants after the call? A.K.A Check return values for error codes?

A.K.A Check return values for error codes?

How do you indicate an error?an exit point Do you verify the post conditions and invariants after the call? A.K.A Check

Communicating Errors

Function return valueCommunicating Errors Exceptions Pass by reference parameters Error callbacks User error handlers $php_errormsg

ExceptionsCommunicating Errors Function return value Pass by reference parameters Error callbacks User error handlers $php_errormsg

Pass by reference parametersCommunicating Errors Function return value Exceptions Error callbacks User error handlers $php_errormsg

Error callbacksCommunicating Errors Function return value Exceptions Pass by reference parameters User error handlers $php_errormsg

User error handlersCommunicating Errors Function return value Exceptions Pass by reference parameters Error callbacks $php_errormsg

$php_errormsgCommunicating Errors Function return value Exceptions Pass by reference parameters Error callbacks User error handlers

Return Values

FALSE on error return FALSE; return FALSE;

Error codesReturn Values FALSE on error return FALSE; return MY_ERROR_CODE; Error Objects return new PEAR_Error();

Return Values FALSE on error return FALSE; Error codes return MY_ERROR_CODE; Error Objects return new PEAR_Error();

return MY_ERROR_CODE;Return Values FALSE on error return FALSE; Error codes Error Objects return new PEAR_Error();

Error Objects return new PEAR_Error(); return new PEAR_Error();

Return Values FALSE on error return FALSE; Error codes return MY_ERROR_CODE; Error Objects return new PEAR_Error();

Return Values

Overloading return values can be confusingReturn Values strpos FALSE versus 0 Hard to propagate One omission breaks the chain Extra propagation

strpos FALSE versus 0Return Values Overloading return values can be confusing Hard to propagate One omission breaks the chain

Hard to propagatereturn values can be confusing strpos FALSE versus 0 One omission breaks the chain Extra propagation

One omission breaks the chainValues Overloading return values can be confusing strpos FALSE versus 0 Hard to propagate Extra propagation

Extra propagation code inlineValues Overloading return values can be confusing strpos FALSE versus 0 Hard to propagate One omission

Propagating Error Return Values

Propagating Error Return Values function store($filename, $data) { $dir = dirname($filename); if ((!@is_dir($dir)

function store($filename, $data) { $dir = dirname($filename); if ((!@is_dir($dir) && !mkdir($dir, 0777, TRUE)) || !($tmpfile = tempnam($dir, '.tmp')) || !file_put_contents($tmpfile, $data) || !rename($tmpfile, $filename)) { return FALSE;

}

return TRUE;

}

Refactored

Refactored function store($filename, $data) { $dir = dirname($filename); if ((!@is_dir($dir)) { if (!mkdir($dir, 0777,

function store($filename, $data) { $dir = dirname($filename); if ((!@is_dir($dir)) { if (!mkdir($dir, 0777, TRUE) { return FALSE;

}

}

$tmpfile = tempnam($dir, ‘.tmp’); if ($tmpfile) { if (!file_put_contents($tmpfile, $data)) { return FALSE;

}

return rename($tmpfile, $filename);

}

return FALSE;

}

Suppressing Returned Error Codes

Easy to SuppressSuppressing Returned Error Codes Hard to distinguish intentional from accidental suppression

Hard to distinguish intentional from accidental suppressionSuppressing Returned Error Codes Easy to Suppress

Error Handlers

set_error_handler(‘handler’);

Primary PHP error notification mechanismError Handlers set_error_handler(‘handler’); Global Variable

Global VariableError Handlers set_error_handler(‘handler’); Primary PHP error notification mechanism

Typical Error Handler

Typical Error Handler function handleError( $type, $message, $file, $line, $context) { if ( ( $type &

function handleError( $type, $message, $file, $line, $context) {

if ( ( $type & error_reporting() ) != $type ) { return; // Ignore this error // because of error suppression

}

// What do you do here?

}

Error Suppression Operator (@)

@operation();Error Suppression Operator (@) Same as $level = error_reporting(0); @operation(); error_reporting($level);

Same asError Suppression Operator (@) @operation(); $level = error_reporting(0); @operation(); error_reporting($level);

$level = error_reporting(0); @operation(); error_reporting($level);Error Suppression Operator (@) @operation(); Same as

Be Careful Not to Over Suppress

@include_once “file.php”;Be Careful Not to Over Suppress @store($filename, $data)

@store($filename, $data)Be Careful Not to Over Suppress @include_once “file.php”;

Prevent Instead of Suppress

PreventPrevent Instead of Suppress $x = isset ( $var ) ? $var : NULL ; suppress

$x = isset ( $var ) ? $var : NULL ; = isset($var) ? $var : NULL ;

suppressPrevent Instead of Suppress Prevent $x = isset ( $var ) ? $var : NULL ;

$x = @ $var ; = @$var;

Exceptions

The class is the error codeExceptions Great ability to convey information

Great ability to convey informationExceptions The class is the error code

Defining An Exception

Defining An Exception class MyBad extends Exception { protected $extraInfo; function construct($message, $extraInfo) {

class MyBad extends Exception { protected $extraInfo;

function

construct($message,

$extraInfo) { $this->extraInfo = $extraInfo; parent:: construct($message);

}

function getExtraInfo() { return $this->extraInfo;

}

}

Throwing an Exception

We only need to define code at the point where the error occurs.Throwing an Exception throw new Exception(“oops.”);

throw new Exception(“oops.”);Throwing an Exception We only need to define code at the point where the error occurs.

Catching An Exception

And define code where we will handle the errorCatching An Exception try { } catch (Exception $e) { react_to_problem(); }

try {Catching An Exception And define code where we will handle the error } catch (Exception $e)

} catch (Exception $e) { react_to_problem();

}

Control Flow

No extra error propagation code requiredControl Flow Minimizes the distance bet ween the event and the reaction Allows the reaction to

Minimizes the distance bet ween the event and the reactionControl Flow No extra error propagation code required Allows the reaction to take place in the

Allows the reaction to take place in the appropriate contextControl Flow No extra error propagation code required Minimizes the distance bet ween the event and

Remember This?

Remember This? function store($filename, $data) { $dir = dirname($filename); if ((!@is_dir($dir)) { if (!mkdir($dir, 0777,

function store($filename, $data) { $dir = dirname($filename); if ((!@is_dir($dir)) { if (!mkdir($dir, 0777, TRUE) { return FALSE;

}

}

$tmpfile = tempnam($dir, ‘.tmp’); if ($tmpfile) { if (!file_put_contents($tmpfile, $data)) { return FALSE;

}

return rename($tmpfile, $filename);

}

return FALSE;

}

Exceptions Reveal the Intent of the Code

Exceptions Reveal the Intent of the Code function store($filename, $data) { $dir = dirname($filename); if (!is_dir($dir))

function store($filename, $data) { $dir = dirname($filename);

if (!is_dir($dir)) { mkdir($dir, 0777, TRUE);

}

$tmpfile = tempnam($dir, ‘.tmp’); file_put_contents($tmpfile, $data); rename($tmpfile, $filename);

}

Building Firewalls

Convert errors from low level errors to high level errorsBuilding Firewalls Add information at each stage Build firewalls at system boundaries

Add information at each stageBuilding Firewalls Convert errors from low level errors to high level errors Build firewalls at system

Build firewalls at system boundariesBuilding Firewalls Convert errors from low level errors to high level errors Add information at each

Inappropriate Level of Abstraction

File system driven cache:Inappropriate Level of Abstraction throw new DiskFullException(); Database driven cache: throw new

throw new DiskFullException();Inappropriate Level of Abstraction File system driven cache: Database driven cache: throw new DbConnectionException();

Database driven cache:File system driven cache: throw new DiskFullException(); throw new DbConnectionException(); Should a Feed class that

throw new DbConnectionException();cache: throw new DiskFullException(); Database driven cache: Should a Feed class that uses this cache API

Should a Feed class that uses this cache API have to know about these exceptions?File system driven cache: throw new DiskFullException(); Database driven cache: throw new DbConnectionException();

Wrapping with PEAR_Exception

class CacheWriteException extends PEAR_Exception {}

try { // Write to the cache with plugin } catch (Exception $e) { throw new CacheWriteException( “Item Not Cached”, $e);

}

Control Flow

Don’t use exceptions for “normal” control flowControl Flow Consider a find() method: Throw on Not Found? Return FALSE on Not Found?

Consider a find() method:Control Flow Don’t use exceptions for “normal” control flow Throw on Not Found? Return FALSE on

Throw on Not Found?Control Flow Don’t use exceptions for “normal” control flow Consider a find() method: Return FALSE on

Return FALSE on Not Found?Control Flow Don’t use exceptions for “normal” control flow Consider a find() method: Throw on Not

trigger_error

trigger_error Leave this communication channel to PHP

Leave this communication channel to PHP

$php_errormsg

Don’t compare it to anything$php_errormsg Influenced by too many ini parameters User error handlers cannot use

Influenced by too many ini parameters$php_errormsg Don’t compare it to anything User error handlers cannot use

User error handlers cannot use$php_errormsg Don’t compare it to anything Influenced by too many ini parameters

Error Levels & Severity

E_ERRORError Levels & Severity E_WARNING E_PARSE E_NOTICE E_CORE_ERROR E_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING

E_WARNINGError Levels & Severity E_ERROR E_PARSE E_NOTICE E_CORE_ERROR E_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING

E_PARSEError Levels & Severity E_ERROR E_WARNING E_NOTICE E_CORE_ERROR E_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING

E_NOTICEError Levels & Severity E_ERROR E_WARNING E_PARSE E_CORE_ERROR E_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING

E_CORE_ERRORLevels & Severity E_ERROR E_WARNING E_PARSE E_NOTICE E_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING

E_CORE_WARNINGSeverity E_ERROR E_WARNING E_PARSE E_NOTICE E_CORE_ERROR E_COMPILE_ERROR E_COMPILE_WARNING E_USER_ERROR

E_COMPILE_ERRORE_WARNING E_PARSE E_NOTICE E_CORE_ERROR E_CORE_WARNING E_COMPILE_WARNING E_USER_ERROR E_USER_WARNING E_USER_NOTICE

E_COMPILE_WARNINGE_PARSE E_NOTICE E_CORE_ERROR E_CORE_WARNING E_COMPILE_ERROR E_USER_ERROR E_USER_WARNING E_USER_NOTICE E_STRICT

E_USER_ERRORE_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING E_USER_WARNING E_USER_NOTICE E_STRICT E_RECOVERABLE_ERROR

E_USER_WARNINGE_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING E_USER_ERROR E_USER_NOTICE E_STRICT E_RECOVERABLE_ERROR E_DEPRECATED?

E_USER_NOTICEE_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING E_USER_ERROR E_USER_WARNING E_STRICT E_RECOVERABLE_ERROR E_DEPRECATED?

E_STRICTE_COMPILE_ERROR E_COMPILE_WARNING E_USER_ERROR E_USER_WARNING E_USER_NOTICE E_RECOVERABLE_ERROR E_DEPRECATED?

E_RECOVERABLE_ERRORE_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING E_USER_ERROR E_USER_WARNING E_USER_NOTICE E_STRICT E_DEPRECATED?

E_DEPRECATED?E_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING E_USER_ERROR E_USER_WARNING E_USER_NOTICE E_STRICT E_RECOVERABLE_ERROR

Classifying Errors by Severity

Makes you determine severity when the error is detectedClassifying Errors by Severity Ignores higher level context Very hard to get it right

Ignores higher level contextClassifying Errors by Severity Makes you determine severity when the error is detected Very hard to

Very hard to get it rightClassifying Errors by Severity Makes you determine severity when the error is detected Ignores higher level

Hide Errors

Hide Errors Don’t reveal your internal error messages to the user php_flag display_errors off php_flag

Don’t reveal your internal error messages to the user

php_flag display_errors offHide Errors Don’t reveal your internal error messages to the user php_flag display_startup_errors off

php_flag display_startup_errors offHide Errors Don’t reveal your internal error messages to the user php_flag display_errors off

Log It

Built in error loggingLog It php_flag log_errors on php_value error_log /path/to/log More efficient than your own error

php_flag log_errors onLog It Built in error logging php_value error_log /path/to/log More efficient than your own error

php_value error_log /path/to/logLog It Built in error logging php_flag log_errors on More efficient than your own error handler

Moreefficient than your own error

efficient

than

your

own

error

handler

 

Canlog errors that user space error

log

errors

that

user

space

error

handlers can’t

error_reporting

error_reporting(E_ALL);error_reporting error_reporting(E_ALL | E_STRICT); error_reporting(E_ALL ^ E_NOTICE); error_reporting(E_ALL ^ E_NOTICE ^

error_reporting(E_ALL | E_STRICT);error_reporting error_reporting(E_ALL); error_reporting(E_ALL ^ E_NOTICE); error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);

error_reporting(E_ALL ^ E_NOTICE);error_reporting error_reporting(E_ALL); error_reporting(E_ALL | E_STRICT); error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);

error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);error_reporting error_reporting(E_ALL); error_reporting(E_ALL | E_STRICT); error_reporting(E_ALL ^ E_NOTICE);

Fatal Errors

E_ERRORFatal Errors E_PARSE E_CORE_ERROR E_COMPILER_ERROR

E_PARSEFatal Errors E_ERROR E_CORE_ERROR E_COMPILER_ERROR

E_CORE_ERRORFatal Errors E_ERROR E_PARSE E_COMPILER_ERROR

E_COMPILER_ERRORFatal Errors E_ERROR E_PARSE E_CORE_ERROR

Catch Fatal Errors

Use register_shutdown_function to detect fatal errorsCatch Fatal Errors Show a 500 page Things may be squirrelly in your shutdown handler? Thanks,

Show a 500 pageErrors Use register_shutdown_function to detect fatal errors Things may be squirrelly in your shutdown handler? Thanks,

Things may be squirrelly in your shutdown handler?Catch Fatal Errors Use register_shutdown_function to detect fatal errors Show a 500 page Thanks, Derick

Thanks, DerickUse register_shutdown_function to detect fatal errors Show a 500 page Things may be squirrelly in your

Questions?