View Source

<ac:macro ac:name="toc"><ac:parameter ac:name="maxLevel">3</ac:parameter></ac:macro>
<h2>Overview</h2>

<p>Zend Framework exceptions use the following pattern:</p>

<ul>
<li>A top-level <code>Zend_Exception</code> class is defined, extending <code>Exception</code>, and providing forward compatibility with PHP 5.3 exception support (namely, the &quot;additional exception&quot; argument).</li>
<li>Each component defines a component-level exception extending <code>Zend_Exception</code>, named after the component: e.g., <code>Zend_Application_Exception</code>.</li>
<li>Subcomponents may optionally define additional exceptions, extending from their component exception class.</li>
<li>Only one exception per level in the hierarchy is supported.</li>
</ul>


<p>This approach, while pragmatic, introduces some inflexibility:</p>

<ul>
<li>No component may be distributed without <code>Zend_Exception</code>; this becomes a hard dependency.</li>
<li>In many cases, it would make sense to utilize and/or extend one of the various SPL exception classes. However, due to the requirement that all exceptions derive from <code>Zend_Exception</code>, this is currently impossible.</li>
<li>Currently, developers must rely on exception messages to understand why and/or where an exception was thrown. This has led to several requests for translatable exceptions and/or development of unique exception codes &ndash; all of which lead to increased maintenance costs.</li>
</ul>


<p>To illustrate, with the current situation, if we want to handle different exceptions from the same component separately, we need to know (a) what type of exception is thrown, and (b) some static part of the exception message:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
try {
$someComponent->doSomething();
} catch (Some_Component_Exception $e) {
if (strstr($e->getMessage(), 'unknown')) {
// handle one type of exception
} elseif (strstr($e->getMessage(), 'not found')) {
// handle another type of exception
} else {
throw $e;
}
}
]]></ac:plain-text-body></ac:macro>

<p>If we were to allow simply using SPL exceptions when they seem appropriate, suddenly you need multiple <code>catch</code> blocks to catch all possible types of exceptions for a given operation:</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
try {
$component->doSomething();
} catch (Some_Component_Exception $e) {
// handle exceptions that derive from this class
} catch (InvalidArgumentException $e) {
// handle a particular type of exception
}
]]></ac:plain-text-body></ac:macro>

<p>What we've found is that developers typically want one of the following situations:</p>

<ul>
<li>Catch specific exceptions</li>
<li>Catch all exceptions by component</li>
<li>Catch exceptions by SPL exception type (including the base Exception class)</li>
</ul>


<p>Our recommendation is to modify how exceptions are defined in Zend Framework 2.</p>

<ul>
<li>There <strong>WILL NOT</strong> be a top level <code>Zend\Exception</code> class</li>
<li>Each component <strong>WILL</strong> define a marker <code>Exception</code> interface; subcomponents may optionally define a similar marker interface extending that interface.</li>
<li>Each component <strong>WILL</strong> define a generic <code>ComponentException</code> class, extending <code>\Exception</code> and implementing the component level <code>Exception</code> marker interface.</li>
<li>Exceptions extending other SPL exception classes and implementing the marker <code>Exception</code> interface <strong>MAY</strong> be created.
<ul>
<li>Exceptions deriving from SPL exception classes <strong>SHOULD</strong> be named after the SPL exception they extend, but <strong>MAY</strong> be named uniquely. In most cases, we would recommend using the original SPL exception name to prevent a proliferation of exceptions.</li>
<li>Exceptions not named after SPL exceptions <strong>WILL</strong> be named meaningfully: <code>InvalidTemplateException</code>, <code>UnbalancedTagException</code>, etc. Exception types can be re-used, but only if the name has a similar meaning in each context in which it is used.</li>
</ul>
</li>
<li>Because the previous requirement may lead to a proliferation of exception classes, exception classes <strong>MAY</strong> be grouped into an &quot;Exception&quot; subcomponent. The rule of thumb will be that if the number of exception classes exceeds 1/2 the number of regular classes and interfaces, they should be segregated.</li>
</ul>


<h2>Theory of Operation</h2>

<p>The theory of operation is best summed up in a use case.</p>

<p>Consider the following directory hierarchy:</p>

<ac:macro ac:name="code"><ac:default-parameter>text</ac:default-parameter><ac:plain-text-body><![CDATA[
Mustache
|-- _autoload.php
|-- Exception
| |-- DomainException.php
| |-- InvalidArgumentException.php
| |-- InvalidStateException.php
| |-- TemplateNotFoundException.php
|-- Exception.php
|-- Lexer.php
|-- Mustache.php
|-- MustacheException.php
|-- Pragma
| |-- AbstractPragma.php
| `-- ImplicitIterator.php
|-- Pragma.php
`-- Renderer.php
]]></ac:plain-text-body></ac:macro>

<p><code>Exception.php</code> contains a single interface, <code>Exception</code> in the current namespace. The class <code>Mustache</code> throws exceptions in several cases:</p>

<ul>
<li>When given an invalid template path, it throws <code>Exception\InvalidArgumentException</code>.</li>
<li>When it is unable to resolve a template file, it throws <code>Exception\TemplateNotFoundException</code>, which extends <code>\DomainException</code></li>
</ul>


<p>While you can catch the individual exception types, it will often be easier to catch more general types. In these cases, you can use one of the SPL exception types (e.g., <code>DomainException</code>, <code>InvalidArgumentException</code>), or the marker interface for the component's exceptions (e.g., <code>Zend\Markup\Exception</code>):</p>

<ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
use Zend\Markup;
try {
$output = $markup->render($string);
} catch (Markup\Exception $e) {
// do something...
}
]]></ac:plain-text-body></ac:macro>