Skip to content

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

}