Zend Framework exceptions use the following pattern:
- A top-level Zend_Exception class is defined, extending Exception, and providing forward compatibility with PHP 5.3 exception support (namely, the "additional exception" argument).
- Each component defines a component-level exception extending Zend_Exception, named after the component: e.g., Zend_Application_Exception.
- Subcomponents may optionally define additional exceptions, extending from their component exception class.
- Only one exception per level in the hierarchy is supported.
This approach, while pragmatic, introduces some inflexibility:
- No component may be distributed without Zend_Exception; this becomes a hard dependency.
- 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 Zend_Exception, this is currently impossible.
- 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 – all of which lead to increased maintenance costs.
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:
If we were to allow simply using SPL exceptions when they seem appropriate, suddenly you need multiple catch blocks to catch all possible types of exceptions for a given operation:
What we've found is that developers typically want one of the following situations:
- Catch specific exceptions
- Catch all exceptions by component
- Catch exceptions by SPL exception type (including the base Exception class)
Our recommendation is to modify how exceptions are defined in Zend Framework 2.
- There WILL NOT be a top level Zend\Exception class
- Each component WILL define a marker Exception interface; subcomponents may optionally define a similar marker interface extending that interface.
- Each component WILL define a generic ComponentException class, extending \Exception and implementing the component level Exception marker interface.
- Exceptions extending other SPL exception classes and implementing the marker Exception interface MAY be created.
- Exceptions deriving from SPL exception classes SHOULD be named after the SPL exception they extend, but MAY be named uniquely. In most cases, we would recommend using the original SPL exception name to prevent a proliferation of exceptions.
- Exceptions not named after SPL exceptions WILL be named meaningfully: InvalidTemplateException, UnbalancedTagException, etc. Exception types can be re-used, but only if the name has a similar meaning in each context in which it is used.
- Because the previous requirement may lead to a proliferation of exception classes, exception classes MAY be grouped into an "Exception" 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.
The theory of operation is best summed up in a use case.
Consider the following directory hierarchy:
Exception.php contains a single interface, Exception in the current namespace. The class Mustache throws exceptions in several cases:
- When given an invalid template path, it throws Exception\InvalidArgumentException.
- When it is unable to resolve a template file, it throws Exception\TemplateNotFoundException, which extends \DomainException
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., DomainException, InvalidArgumentException), or the marker interface for the component's exceptions (e.g., Zend\Markup\Exception):