lucinda/abstract_mvc

Abstract MVC API to be extended based on STDIN type (url requests, exceptions)

v2.1.1 2022-06-05 07:10 UTC

README

Table of contents:

About

This API is a skeleton (requires binding by developers) created to contain parts of a MVC API that do not relate to STDIN type (console, url request or exception to be handled). It serves as a foundation for:

  • STDOUT MVC API: where STDIN comes from URL requests
  • STDERR MVC API: where STDIN comes from STDERR of url/console requests
  • Console MVC API (to be done): where STDIN comes from console requests

API is fully PSR-4 compliant, only requiring PHP8.1+ interpreter and SimpleXML extension. To quickly see how it works, check:

  • 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
  • reference guide: describes all API classes, methods and fields relevant to developers
  • unit tests: API has 100% Unit Test coverage, using UnitTest API instead of PHPUnit for greater flexibility

All classes inside belong to Lucinda\MVC namespace!

Configuration

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

  • application: (mandatory) configures your application on a general basis
  • resolvers: (mandatory) configures formats in which your application is able to resolve responses to
  • routes: (mandatory) configures routes that bind requested resources to controllers and views

Application

Maximal syntax of this tag is:

<application default_format="..." default_route="..." version="...">
	<paths views="..."/>
</application>

Where:

  • application: (mandatory) holds settings to configure your application based on attributes and tag:
    • default_format: (mandatory) defines default display format (extension) for your application.
      Must match a format attribute in resolvers!
    • default_route: (mandatory) defines implicit route when your application is invoked with none.
      Must match a id attribute in routes!
    • version: (optional) defines your application version, to be used in versioning static resources.
    • paths: (optional) holds where core components used by API are located based on attributes:
      • views: (optional) holds folder in which user-defined views will be located (if HTML)

Tag example:

<application default_format="html" default_route="" version="1.0.1">
	<paths views="application/views"/>
</application>

Resolvers

Base syntax of this tag is:

<resolvers>
	<resolver format="..." content_type="..." class="..."/>
	...
</resolvers>

Where:

  • resolvers: (mandatory) holds settings to resolve views based on response format (extension). Holds a child for each format supported:
    • resolver: (mandatory) configures a format-specific view resolver based on attributes:
      • format: (mandatory) defines display format (extension) handled by view resolver.
        Example: "html"
      • content_type: (mandatory) defines content type matching display format above.
        Example: "text/html"
      • class: (mandatory) name of user-defined PS-4 autoload compliant class (including namespace) that will resolve views.
        Must be a ViewResolver instance!

Tag example:

<resolvers>
    <resolver format="html" content_type="text/html" class="Lucinda\Project\Resolvers\Html" charset="UTF-8"/>
    <resolver format="json" content_type="application/json" class="Lucinda\Project\Resolvers\Json" charset="UTF-8"/>
</resolvers>

Routes

Maximal syntax of this tag is:

<routes>
    <route id="..." controller="..." view="..." format="..."/>
    ...
</routes>

Where:

  • routes: (mandatory) holds routing rules for handled requests
    • route: (optional) holds routing rules specific to a requested URI based on attributes:
      • id: (mandatory) unique route identifier (eg: requested requested resource url without trailing slash)
        Example: "users/(name)"
      • controller: (optional) name of user-defined PS-4 autoload compliant class (including namespace) that will mitigate requests and responses based on models.
        Must be a Runnable instance!
      • view: (optional) holds user-defined template file that holds the recipe of response for request. Example: "homepage"
      • format: (optional) holds response format, if different from default_format @ application.
        Must match a format attribute @ resolvers!

Tag example:

<routes>
    <route id="index" controller="HomepageController" view="index"/>
    <route id="user/(id)" controller="UserInfoController" view="user-info" format="json"/>
</routes>

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 via XML:

XML Attribute @ Tag Class Prototype Ability Gained
controller @ route Runnable MVC controller for any STDIN type
class @ resolver ViewResolver Resolving response in a particular format (eg: html)

Programmatic Binding

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

Class Prototype Ability Gained
ErrorHandler (mandatory) Handler to use if a \Throwable while API handles request into response

Unit Tests

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

Reference Guide

These classes are fully implemented by API:

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

Class Application

Class Application encapsulates information detected from XML and defines following public methods relevant to developers:

Method Arguments Returns Description
getVersion void string Gets application version based on version attribute @ application XML tag
getTag string $name \SimpleXMLElement Gets a pointer to a custom tag in XML root

Other public methods are relevant only to APIs built on top of this.

Class Application Route

Class Application\Route encapsulates information detected from matching route XML tag and defines following public methods:

Method Arguments Returns Description
getID void string Gets route unique identifier based on value of id XML attribute
getController void string Gets controller class name/path/namespace based on value of controller XML attribute
getFormat void string Gets custom route-specific display format based on value of format XML attribute matching a format attribute of resolver XML tag
getView void string Gets view path based on value of view XML attribute

Class Application Format

Class Application\Format encapsulates information detected from matching resolver XML tag and defines following public methods:

Method Arguments Returns Description
getName void string Gets reponse format (extension) based on value of format XML attribute
getCharacterEncoding void string Gets response character encoding based on value of charset XML attribute
getContentType void string Gets response content type based on value of content_type XML attribute
getViewResolver void string Gets view resolver class based on value of class attribute

To better understand how views will be resolved in the end, check How Are Views Resolved documentation below!

Class Response

Class Response encapsulates operations to be used in generating response. It defines following public methods relevant to developers:

Method Arguments Returns Description
getBody void string Gets response body saved by method below.
setBody string $body void Sets response body.
getStatus void Response\Status Gets response http status saved by method below.
setStatus Response\HttpStatus void Sets response http status and splits it into id and description
headers void array Gets all response http headers saved by methods below.
headers string $name ?string Gets value of a response http header based on its name. If not found, null is returned!
headers string $name, string $value void Sets value of response http header based on its name.
view void Response\View Gets a pointer to view encapsulating data based on which response body will be compiled

When API completes handling, it will call commit method to send headers and response body back to caller!

Class Response Status

Class Response\Status encapsulates response HTTP status and defines following public methods relevant to developers:

Method Arguments Returns Description
getId void int Gets http response status id (eg: "304")
getDescription void string Gets http response status description (eg: "not modified").

Class Response View

Class Response\View implements \ArrayAccess and encapsulates template and data that will later be bound to a response body. It defines following public methods relevant to developers:

Method Arguments Returns Description
getFile void string Gets location of template file saved by method below.
setFile string int Sets location of template file to be used in generating response body.
getData void array Gets all data that will be bound to template when response body will be generated.

By virtue of implementing \ArrayAccess, developers are able to work with this object as if it were an array:

$this->response->view()["hello"] = "world";

Class Response Redirect

Class Response\Redirect is a Runnable that encapsulates http status 301/302 redirection with its options. It defines following public methods relevant to developers:

Method Arguments Returns Description
__construct string $location void Sets location to redirect to (by default redirection will be permanent and cacheable).
setPermanent bool $flag void Sets whether redirection is permanent.
setPreventCaching bool $flag void Sets whether browsers should prevent caching redirection.
run - void Performs redirection and exits code.

Example:

$redirection = new Lucinda\\MVC\\Redirect("https://www.google.com");
$redirection->setPermanent(false);
$redirection->run();

Interface Runnable

Interface Runnable interface it implements, class comes with following public method:

Method Arguments Returns Description
run void Executes component logic

Usage example:

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

Abstract Class ViewResolver

Abstract class ViewResolver implements Runnable and encapsulates conversion of Response\View to response body for final response format.

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

Field Type Description
$application Application Gets application information detected from XML.
$response Response Gets access to object based on which response can be manipulated.

Usage example:

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

In order to better understand how view resolvers work, check How Are View Resolvers Located section below!

Specifications

Since this API is a skeleton to build MVC APIs on top it comes with a series of specifications that may or may not be automatically implemented here:

How Is Response Format Detected

To better understand how default_format and default_route attributes in application XML tag play together with format attribute in routes tag and format attribute in resolvers tag, let's take this XML for example:

<application default_route="index" default_format="html" ...>
	...
</application>
<routes>
    <route id="index" .../>
    <route id="blog" format="json" .../>
    <route id="users" .../>
    ...
</routes>
<resolvers>
    <resolver format="html" .../>
    <resolver format="json" .../>
    ...
</resolvers>

There will be following situations for above:

If Route Detected Then Format Detected Description
index html Because no specific format is set, value of default_format is used
users html Because no specific format is set, value of default_format is used
blog json Because route detected has format its value is used

This logic requires to be implemented by child APIs because the nature of resolvers differs based on STDIN type!

How Are Views Resolvers Located

To better understand how default_format attribute in application XML tag plays together with format and class attributes in resolvers tag, let's take this XML for example:

<application default_format="html" ...>
	...
</application>
...
<resolvers>
    <resolver format="html" class="Lucinda\Project\ViewResolvers\Html" .../>
</resolvers>

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

  • file autoloaded will be src/ViewResolvers/Html.php
  • class found there must:
    • be named: "Html"
    • belong to namespace: "Lucinda\Project\ViewResolvers"
    • extend ViewResolver

This logic is entirely implemented by this API! Developers only need to plug in suitable ViewResolver classes in XML.

How Is Route Detected

To better understand how default_route attribute in application XML tag plays together with id attribute in routes tag, let's take this XML for example:

<application default_route="index" ...>
	...
</application>
<routes>
    <route id="index" .../>
    <route id="blog" .../>
    ...
</routes>

Assuming STDIN comes from HTTP requests, there will be following situations for above:

If Request Is Then Route ID Detected Description
/ index Because no specific page was requested, that identified by default_route is used
/blog blog Because page is routed, that whose id matches request is used

This logic requires to be implemented by child APIs since logic of request depends on STDIN type!

How Are Controllers Located

To better understand how default_route attribute in application XML tag plays together with id and controller attributes in routes tag, let's take this XML for example:

<application default_route="index" ...>
	...
</application>
...
<routes>
    <route id="index" controller="Lucinda\Project\Controllers\Homepage" .../>
</routes>

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

  • file autoloaded will be: "src/Controllers/Homepage.php"
  • class found there must:
    • be named: "Homepage"
    • belong to namespace: "Lucinda\Project\Controllers"
    • extend Lucinda**???**\Controller

As you can see above, controller namespace was ommitted because controller itself must be implemented by child APIs (since it depends on STDIN type).

How Are Views Located

To better understand how views attribute in application XML tag plays together with id and view attributes in routes tag, let's take this XML for example:

<application ...>
	<paths views="application/views"/>
</application>
...
<routes>
    <route id="user/info" view="user-info" .../>
</routes>

In that case if route "user/info" matches STDIN request then:

  • file autoloaded will be: "application/views/user-info.???"

As you can see above, view extension was ommitted because view itself must be implemented by child APIs (since it depends on STDIN type).