Exceptions
The \Exception class should never been thrown directly. Instead, the \Exception class should either be extended with a custom exception or one of the available SPL "main" exceptions should be extended.
Exceptions Types
There a two main classes of Exception types.
A SPL \LogicException is an exception that requires a fix or change in content managed by the developer. This includes source code, configurations and database content. This Exception occurs during the "compile time". A LogicException is mainly an assertion. (see SPL Examples below)
A SPL \RuntimeException is an exception caused by input from end users or from communications with an external entity and does not require a fix or change in the code. It occurs because of external circumstances where the developer has no influence. This Exception does not occur during the "compile time". (See SPL Examples below)
Both of these previously described Exception classes should always fail loud! As the LogicException is mostly an error in the code and the RuntimeException prevents a clean execution of the script because of external circumstances. These exceptions should not be thrown and catched in e.g. sanatization checks or right checks, etc. In these cases, CustomExceptions should be used, which should extend the \Exception class.
Basic Examples
<?php
class DatabaseException extends \RuntimeException {}
class InvalidValueException extends \LogicException {}
class InvalidRightsException extends \Exception {}
class InvalidFileException extends \Exception {}
class File {
public function moveTo(Target $target) {
if (!$this->canRead()) {
throw new InvalidRightsException('CUSTOM: User has no right to read.');
}
if (!$target->canWrite()) {
throw new InvalidRightsException('CUSTOM: User has no right to write in target');
}
if ($this->hasZeroBytes()) {
throw new InvalidFileException('CUSTOM: This file seems to have no content.');
}
if ($this->hasVirus()) {
throw new InvalidFileException('CUSTOM: This file contains a virus.');
}
// ... Do stuff
}
}
function moveFiles(array $files, int $targetId) {
$target = findTargetById($targetId);
if ($target === null) {
// DISCUSS:
throw new CustomException("CUSTOM: No Target found for ID: " . $targetId);
}
foreach ($files as $file) {
if (!($file instanceof File)) {
// Assert that $file is of type "File" -> LogicException,
// because we absolutely need an object of type File now.
// If we do not have it here, there are missing checks somewhere before
throw new InvalidValueException('LOGIC: $file is an invalid type.');
}
try {
$file->moveTo($target);
} catch (InvalidRightsException $e) {
// .. Do stuff, Log, build User Message, etc.
// But continue with remaining files from array
} catch (InvalidFileException $e) {
$file->delete();
// .. Do stuff, Log, build User Message, etc.
// But continue with remaining files from array
}
}
}
/**
* @return null|Target
*/
function findTargetById(int $id): ?Target {
if (!DB::establishConnection()) {
// Throw runtime exception if Database is not reachable
throw new DatabaseException('RUNTIME: Connection could not be established!');
}
// ... Do stuff
// Return either a valid target or null if not found
return $target;
}
Custom Exceptions
You can use custom exceptions in your module. These exceptions must be named with the suffix "Exception". They should be bundled into a separate "Exceptions" folder.
E.g.
Class: XELOS\Modules\MyModule\Exceptions\MyModuleException
Location: <XELOSROOT>/modules/my_module/class/Exceptions/MyModuleException
XELOS Exceptions
The XELOS framework provides and uses the following exceptions.
All of them are in the XELOS\Framework\Core\Exception namespace.
Class name | Parent | Description |
---|---|---|
AuthenticationException | Exception | Thrown by authentication hooks, e.g. when the connection the AD server fails. |
AccessException | Exception | Thrown in case of accessing protected content/file not being accessible by the user or system. |
InsufficientRightsException | AccessException | Thrown when the user has insufficient rights, e.g an operation expects write rights but the operator has only read rights. |
DatabaseException | LogicException | Thrown when the database query contains an error. |
DatabaseConnectionException | RuntimeException | Thrown when the database connection failed. |
ModuleException | RuntimeException | Thrown for generic module exceptions, e.g. dependency not found. Can be inherited for specific module errors. |
ContentException | Exception | Thrown by AJAX operations to finish the request quickly. |
UserImportException | Exception | Thrown during an user import process if something unexpected happened. |
UserCreationException | UserImportException | Thrown during an user import process if the creation of a user failed. (e.g. user limit reached). |
ModuleException
The ModuleException is used for unexpected module related exceptions like generic module dependency problems or operating with modules with invalid states.
This exception provides also a basis for other more specific module related exceptions.
<?php
/**
* Example module my_module
*/
class MyModuleController extends XELOS\Framework\Module\Controller {
/** @var XELOS\Modules\DMS\DMSController */
private $dms;
/** @var int */
private $user_id;
/**
* Returns a dms instance in user group context.
*
* @return XELOS\Modules\DMS\DMSController
*
* @throws XELOS\Framework\Core\Exception\ModuleException
*/
public function get_dms(){
if (!$this->dms && !($this->dms = $this->get_dep('dms', $this->user_id))) {
throw new XELOS\Framework\Core\Exception\ModuleException(_("Missing required DMS dependency for managing user files."));
}
return $this->dms;
}
}
Best Practice
Always keep the original exception if a new exception is thrown (third parameter).
try {
// ... Do stuff
} catch (CustomException $e) {
// ... Do stuff
// Use original exception $e as third parameter on new throw
throw new MyUserMessageException("These aren't the droids you're looking for", 0, $e);
}
SPL Exceptions & SPL UseCases
The SPL class tree
- LogicException (extends Exception)
- BadFunctionCallException
- BadMethodCallException
- DomainException
- InvalidArgumentException
- LengthException
-
OutOfRangeException
-
RuntimeException (extends Exception)
- OutOfBoundsException
- OverflowException
- RangeException
- UnderflowException
- UnexpectedValueException
LogicException: BadFunctionCallException / BadMethodCallException
Exception thrown if a callback refers to an undefined function or if some arguments are missing.
LogicException: DomainException
Exception thrown if a value does not adhere to a defined valid data domain.
Example:
// DomainException
switch ($imageType) {
case 'jpg':
case 'jpeg':
// ... Do stuff
break;
case 'png':
// ... Do stuff
break;
default:
throw new DomainException('Unknown image type: ' . $imageType);
break;
}
LogicException: InvalidArgumentException
Exception thrown if an argument is not of the expected type.
Example:
// InvalidArgumentException
function tripleInteger($int) {
if(!is_int($int)) {
throw new InvalidArgumentException('tripleInteger function only accepts integers. Input was: '.$int);
}
return $int * 3;
}
LogicException: LengthException
LogicException: OutOfRangeException
Exception thrown when an illegal index was requested. This represents errors that should be detected at compile time
Example:
// OutOfRangeException
function prepareData($config) {
if(!isset($config['api_active'])) {
throw new OutOfRangeException('The config value "api_active" doesn't exist. Please check the config values.');
}
// ... Do stuff
}
RuntimeException: OutOfBoundsException
Exception thrown if a value is not a valid key. This represents errors that cannot be detected at compile time.
Example:
// OutOfBoundsException
function handleRequest() {
if(!isset($_POST['secret_code'])) {
throw new OutOfBoundsException('The application hasn't send a secret code for authentication!');
}
}
RuntimeException: OverflowException
Exception thrown when adding an element to a full container.
RuntimeException: RangeException
Exception thrown to indicate range errors during program execution. Normally this means there was an arithmetic error other than under/overflow. This is the runtime version of DomainException.
Example:
// RangeException (Runtime version of DomainException)
function divide($number, $divident) {
if(!is_numeric($divident) || !is_numeric($input)) {
throw new InvalidArgumentException("Function accepts only numeric values");
}
if($input == 0) {
throw new RangeException("Divisor must not be zero");
}
return $divident / $input;
}
RuntimeException: UnderflowException
Exception thrown when performing an invalid operation on an empty container, such as removing an element.
RuntimeException: UnexpectedValueException
Exception thrown if a value does not match with a set of values.
Example:
// UnexpectedValueException
function setPageCount(int $pageCount) {
if ($pageCount > $this->maxPageCount) {
throw new UnexpectedValueException("Page count of {$pageCont} exceeds allowed page count of {$this->maxPageCount}.");
}
// ... Do stuff
}