lucinda/errors-mvc

AOP-based MVC API for handling errors and uncaught exceptions in PHP applications

v3.0.6 2024-04-07 10:51 UTC

README

Table of contents:

About

This API is a skeleton (requires binding by developers) created to efficiently handle errors or uncaught exceptions in a web application using a dialect of MVC paradigm where:

  • models are reusable logic to report \Throwable instances handled or holders of data to be sent to views
  • views are the response to send back to caller after an error/exception was handled
  • controllers are binding \Throwable instances to models in order to configure views (typically send data to it)

diagram

Just as MVC API for STDOUT handles http requests into responses, so does this API for STDERR handle errors or uncaught exceptions that happen during previous handling process. It does so in a manner that is both efficient and modular, without being bundled to any framework:

  • first, MVC API for STDERR (this API) registers itself as sole handler of errors and uncaught exceptions encapsulated by \Throwable instances, then passively waits for latter to be triggered
  • then, MVC API for STDOUT (your framework of choice) starts handling user requests into responses. Has any error or uncaught exception occurred in the process?
    • yes: MVC API for STDERR gets automatically awaken then starts handling respective \Throwable into response
    • no: response is returned back to caller

Furthermore, this whole process is done in a manner that is made flexible through a combination of:

  • configuration: setting up an XML file where this API is configured
  • binding points: binding user-defined components defined in XML/code to API prototypes in order to gain necessary abilities
  • initialization: instancing FrontController to register itself as sole \Throwable handler
  • handling: once any error or uncaught exception has occurred in STDOUT phase, method handle of above is called automatically kick-starting \Throwable-response process

API is fully PSR-4 compliant, only requiring Abstract MVC API for basic MVC logic, PHP7.1+ interpreter and SimpleXML extension. To quickly see how it works, check:

  • installation: describes how to install API on your computer, in light of steps above
  • unit tests: API has 100% Unit Test coverage, using UnitTest API instead of PHPUnit for greater flexibility
  • example: shows a deep example of API functionality based on FrontController unit test
  • reference guide: describes all API classes, methods and fields relevant to developers

All classes inside belong to Lucinda\STDERR namespace!

Configuration

To configure this API you must have a XML with following tags inside:

  • application: (mandatory) configures handling on general application basis
  • display_errors: (optional) configures whether or not errors should be displayed
  • reporters: (optional) configures how your application will report handled error/exception
  • resolvers: (mandatory) configures formats in which your application is able to resolve responses to a handled error/exception
  • routes: (mandatory) configures error/exception based routing to controllers/views

Application

Tag documentation is completely covered by inherited Abstract MVC API specification! Since STDIN for this API is made of handled throwables but there is no generic throwable value of default_route attribute must be default.

Display_Errors

Maximal syntax of this tag is:

<display_errors>
    <{ENVIRONMENT}>{VALUE}</{ENVIRONMENT}>
    ...
</display_errors>

Most of tag logic is already covered by Abstract MVC API specification. Following extra sub-tags/attributes are defined:

  • display_errors: (optional) holds whether or not handled error/exception details will be exposed back to callers based on:
    • {ENVIRONMENT}: name of development environment (to be replaced with "local", "dev", "live", etc). {VALUE} can be:
      • 1: indicates error/exception details will be displayed to caller
      • 0: indicates error/exception details won't be displayed to caller

If no display_errors is defined or no {ENVIRONMENT} subtag is found matching current development environment, value 0 is assumed (\Throwable details won't be exposed)!

Tag example:

<display_errors>
    <local>1</local>
    <live>0</live>
</display_errors>

Reporters

Maximal syntax of this tag is:

<reporters>
    <{ENVIRONMENT}>
        <reporter class="..." {OPTIONS}/>
        ...
    </{ENVIRONMENT}>
    ...
</reporters>

Where:

  • reporters: (mandatory) holds settings to configure your application for error reporting based on:
    • {ENVIRONMENT}: (mandatory) name of development environment (to be replaced with "local", "dev", "live", etc). Holds one or more reporters, each defined by a tag:
      • reporter: (mandatory) configures an error/exception reporter based on attributes:
        • class: (mandatory) name of user-defined PS-4 autoload compliant class (including namespace) that will report \Throwable.
          Class must be a Reporter instance!
        • {OPTIONS}: a list of extra attributes necessary to configure respective reporter identified by class above

Tag example:

<reporters>
    <local>
        <reporter class="Lucinda\Project\Reporters\File" path="errors" format="%d %f %l %m"/>
    </local>
    <live>
        <reporter class="Lucinda\Project\Reporters\SysLog" application="unittest" format="%v %f %l %m"/>
    </live>
</reporters>

Resolvers

Tag documentation is completely covered by inherited Abstract MVC API specification!

Routes

Maximal syntax of this tag is:

<routes>
    <route id="..." controller="..." view="..." error_type="..." http_status="..."/>
    ...
</routes>

Most of tag logic is already covered by Abstract MVC API specification. Following extra observations need to be made:

  • id: (mandatory) mapped error/exception class name or default (matching default_route @ application tag).
    Class must be a \Throwable instance!
  • controller: (optional) name of user-defined PS-4 autoload compliant class (including namespace) that will mitigate requests and responses based on models.
    Class must be a Controller instance!
  • error_type: (mandatory) defines default exception/error originator. Must match one of ErrorType enum cases values! Example: "LOGICAL"
  • http_status: (mandatory) defines default response HTTP status. Must be a valid HTTP status code! Example: "500"

Tag example:

<routes>
    <route id="default" http_status="500" error_type="LOGICAL" view="500"/>
    <route id="Lucinda\MVC\STDOUT\PathNotFoundException" controller="Lucinda\Project\Controllers\PathNotFound" http_status="404" error_type="CLIENT" view="404"/>
</routes>

If handled \Throwable matches no route, default route is used!

Binding Points

In order to remain flexible and achieve highest performance, API takes no more assumptions than those absolutely required! It offers developers instead an ability to bind to its prototypes in order to gain certain functionality.

Declarative Binding

It offers developers an ability to bind declaratively to its prototype classes/interfaces via XML:

Programmatic Binding

It offers developers an ability to bind programmatically to its prototypes via FrontController constructor:

Execution

Initialization

Now that developers have finished setting up XML that configures the API, they are finally able to initialize it by instantiating FrontController.

As a handler of \Throwable instances, above needs to implement ErrorHandler. Apart of method run required by interface above, FrontController comes with following public methods, all related to initialization process:

Where:

  • $documentDescriptor: relative location of XML configuration file. Example: "configuration.xml"
  • $developmentEnvironment: name of development environment (to be replaced with "local", "dev", "live", etc) to be used in deciding how to report or whether or not to expose handled \Throwable
  • $includePath: absolute location of your project root (necessary because sometimes include paths are lost when errors are thrown). Example: DIR
  • $emergencyHandler: a ErrorHandler instance to be used in handling errors occurring during execution of FrontController's handle method
  • $displayFormat: value of display format, matching to a format attribute of a resolver @ resolvers XML tag

Very important to notice that once handlers are registered, API employs Aspect Oriented Programming concepts to listen asynchronously for error events then triggering handler automatically. So once API is initialized, you can immediately start your preferred framework that handles http requests to responses!

Handling

Once a \Throwable event has occurred inside STDOUT request-response phase, handle method of FrontController is called. This:

All components that are in developers' responsibility (Controller, \Lucinda\MVC\ViewResolver, Reporter) implement \Lucinda\MVC\Runnable interface.

Installation

First choose a folder, then write this command there using console:

composer require lucinda/errors-api

Then create a configuration.xml file holding configuration settings (see configuration above) and a index.php file (see initialization above) in project root with following code:

// detects current development environment from ENVIRONMENT environment variable (eg: set in .htaccess via "SetEnv ENVIRONMENT local");
define("ENVIRONMENT", getenv("ENVIRONMENT"));

// starts API as error handler to listen for errors/exceptions thrown in STDOUT phase below
new FrontController("configuration.xml", getenv("ENVIRONMENT"), __DIR__, new EmergencyHandler());

// runs preferred STDOUT framework (eg: STDOUT MVC API) to handle requests into responses

Example of emergency handler:

class EmergencyHandler implements \ErrorHandler
{
    public function handle($exception): void
    {
        var_dump($exception);
        die();
    }
}

Unit Tests

For tests and examples, check following files/folders in API sources:

Reference Guide

These classes are fully implemented by API:

These abstract classes require to be extended by developers in order to gain an ability:

Class Application

Class Application extends Lucinda\MVC\Application and adds one method relevant to developers:

Class Application Route

Class Application\Route extends Lucinda\MVC\Application\Route and adds following public methods:

Class Request

Class Request encapsulates handled \Throwable and matching Application\Route. It defines following public methods relevant to developers:

Interface ErrorHandler

Interface ErrorHandler contains blueprint for handling \Throwable via method:

Usage example:

https://github.com/aherne/lucinda-framework/blob/master/src/EmergencyHandler.php

Abstract Class Reporter

Abstract class Reporter implements \Lucinda\MVC\Runnable and encapsulates a single \Throwable reporter. It defines following public method relevant to developers:

Developers need to implement run method for each reporter, where they are able to access following protected fields injected by API via constructor:

Usage example:

https://github.com/aherne/lucinda-framework-engine/blob/master/src/AbstractReporter.php https://github.com/aherne/lucinda-framework/blob/master/src/Reporters/File.php

For more info how reporters are detected, check How Are Reporters Located section below!

Abstract Class Controller

Abstract class Controller implements \Lucinda\MVC\Runnable) to set up response (views in particular) based on information detected beforehand. It defines following public method relevant to developers:

Developers need to implement run method for each controller, where they are able to access following protected fields injected by API via constructor:

Usage example:

https://github.com/aherne/lucinda-framework/blob/master/src/Controllers/SecurityPacket.php

For more info how controllers are detected, check How Are Controllers Located section below!

Specifications

Since this API works on top of Abstract MVC API specifications it follows their requirements and adds extra ones as well:

How Is Response Format Detected

This follows parent API specifications only that routes are detected based on \Throwable handled. One difference is that detected value can be overridden using setDisplayFormat method (see Initialization).

How Are View Resolvers Located

This follows parent API specifications in its entirety.

How Is Route detected

This follows parent API specifications only that routes are detected based on \Throwable handled. Let's take this XML for example:

<application default_route="default" ...>
	...
</application>
<routes>
    <route id="default" .../>
    <route id="\Bar\Exception" .../>
    ...
</routes>

There will be following situations for above:

How Are Controllers Located

This follows parent API specifications only that class defined as controller attribute in route tag must extend Controller.

How Are Reporters Located

To better understand how class attributes @ reporter tags matching development environment, let's take this XML for example:

<reporters>
    <ENVIRONMENT1>
        <reporter class="Lucinda\Project\Reporters\File" .../>
        <reporter class="Lucinda\Project\Reporters\SysLog" .../>
    </ENVIRONMENT1>
    <ENVIRONMENT2>
        <reporter class="Lucinda\Project\Reporters\SysLog" .../>
    </ENVIRONMENT2>
    ...
</reporters>

In that case if "psr-4" attribute in composer.json associates "Lucinda\Project\" with "src/" folder then:

All classes referenced above must be instance of Reporter!

How Are Views Located

This follows parent API specifications in its entirety. Extension is yet to be decided, since it depends on type of view resolved!