Framework Skeleton: Setting STDOUT Event Listeners

STDOUT MVC API event listeners function in a way similar to JavaScript event listeners. For each event in application's lifecycle as request is processed for response, users registers behaviors as <listener> tag in stdout.xml that will be executed when lifecycle event is reached in the order they were set in <listeners> tag.

Supported lifecycle events are:

Framework skeleton implements a number of event listeners, each optional, each generally integrating an API that adds particular functionality to your site:

Event Listener Class Extended Needed When
LoggingListener Lucinda\MVC\STDOUT\ApplicationListener you want logging abilities to your application based on development environment
SQLDataSourceInjector Lucinda\MVC\STDOUT\ApplicationListener you want to query sql databases based on development environment
NoSQLDataSourceInjector Lucinda\MVC\STDOUT\ApplicationListener you want to query nosql databases based on development environment
ErrorListener Lucinda\MVC\STDOUT\RequestListener you want errors to be rendered using route's content type instead of default.
SecurityListener Lucinda\MVC\STDOUT\RequestListener you want authentication and authorization for your application
ValidationListener Lucinda\MVC\STDOUT\RequestListener you want request to be validated automatically based on values of parameters
LocalizationListener Lucinda\MVC\STDOUT\RequestListener you want response to be automatically translated based on client locale
HttpCachingListener Lucinda\MVC\STDOUT\ResponseListener you want response to be returned from client cache if not changed server-side

Because integration logic has very low probability of change by developers event listeners generally choose to delegate it to Framework Engine API, whose components (binders) have single responsibility in integrating an API based on matching stdout.xml tag, then optionally save results into attributes to be used later on:

Event Listener Binder Used APIs Integrated XML Tag Attributes Saved
LoggingListener Lucinda\Framework\LoggingBinder Logging API loggers logger
SQLDataSourceInjector Lucinda\Framework\SQLDataSourceBinder SQL Data Access API servers > sql -
NoSQLDataSourceInjector Lucinda\Framework\NoSQLDataSourceBinder NoSQL Data Access API servers > nosql -
ErrorListener - MVC STDERR API - -
SecurityListener Lucinda\Framework\SecurityBinder HTTP Security API

OAuth2 Client API
security
users
routes
user_id
csrf
oauth2
access_token
ValidationListener Lucinda\Framework\ValidationBinder Request Validation API routes -
LocalizationListener Lucinda\Framework\LocalizationBinder Internationalization API internationalization -
HttpCachingListener Lucinda\Framework\CachingBinder HTTP Caching API http_caching validation_results

LoggingListener

An LucindaMVC\STDOUT\ApplicationListener that sets up logging by integrating Logging API based on contents of loggers XML tag.

How is it working?

Framework first inspects contents of loggers XML tag based on development environment detected in bootstrap. If one or more <logger> tags are found, it locates and instances classes defined by their "class" attribute and saves all instances into a MultiLogger instance, which will act like a hub that, once logging is requested, will distribute same message to each logger.

It already comes with two loggers preinstalled:

but developers can add their own there as long as their classes extend Lucinda\Framework\AbstractLoggerWrapper and they are found in folder application/models/loggers.

To allow logging in subsequent listeners and controllers, it saves MultiLogger instance above into a logger attribute.

SQLDataSourceInjector

An LucindaMVC\STDOUT\ApplicationListener that sets up a Lucinda\SQL\DataSource object to be used later on in sql database connection (eg: MySQL) by integrating SQL Data Access API based on contents of <sql> XML tag.

How is it working?

Framework first inspects contents of <sql> XML tag based on development environment detected in bootstrap. Then:

How to query a SQL database afterwards?

To get a connection to database later on in models:

// if you are using a single SQL server for same environment $connection = Lucinda\SQL\ConnectionSingleton::getInstance(); // if you are using multiple SQL servers for same environment $connection = Lucinda\SQL\ConnectionFactory::getInstance("YOUR SERVER NAME");

Each connection retrieved is a singleton of Lucinda\SQL\Connection object. This not only insures a single connection is used throught STDOUT session, but also that connections are automatically closed when session ends. To query database:

// if you are using normal statement $result = $connection->createStatement()->execute("YOUR QUERY"); // if you are using prepared statement $preparedStatement = $connection->createPreparedStatement(); $preparedStatement->prepare("YOUR QUERY"); $result = $preparedStatement(PARAMETERS_TO_BIND=array());

To avoid this laborious procedure for a simple query, use DB wrapper included in skeleton!

NoSQLDataSourceInjector

An LucindaMVC\STDOUT\ApplicationListener that sets up a Lucinda\NoSQL\DataSource object to be used later on in nosql database connection1 (eg: Redis) by integrating NoSQL Data Access API based on contents of <nosql> XML tag.

How is it working?

Framework first inspects contents of <nosql> XML tag based on development environment detected in bootstrap. Then:

Here is where symmetry with SQLDataSourceInjector ends: Lucinda\NoSQL\DataSource is just an interface that does nothing but signaling whoever implements it is a data source. Each vendor supported (redis, memcache, etc) thus needs to come with its own implementation and those that rely on a server will need to implement Lucinda\NoSQL\ServerDataSource.

How to query a NoSQL database afterwards?

To get a connection to database later on in models:

// if you are using a single NoSQL server for same environment $connection = Lucinda\NoSQL\ConnectionSingleton::getInstance(); // if you are using multiple NoSQL servers for same environment $connection = Lucinda\NoSQL\ConnectionFactory::getInstance("YOUR SERVER NAME");

Each connection retrieved is a singleton of Lucinda\NoSQL\Driver object, to be directly used in querying the server. This not only insures a single connection is used throught STDOUT session, but also that connections are automatically closed when session ends. To query database follow Lucinda\NoSQL\Driver documentation or this example:

// this saves a value by key that will expire in N seconds $driver->set("key", "value", EXPIRATION_TIME=0); // this retrieves saved value by key $value = $driver->get("key");

If you ever need to work with driver wrapped (eg: Redis object), you can write:

// returns Redis instance (if driver is redis) $redis = $driver->getDriver();

Notes:
1. APC/APCu are embedded, so no server connection needs to be opened if they are used and no getDriver() method is implemented!

ErrorListener

A Lucinda\MVC\STDOUT\RequestListener that informs STDERR MVC API of response format expected based on that of route requested. If not used, response in STDERR flow will always use default format.

How is it working?

The way it works is as simple as these four lines of code:

$handler = \Lucinda\MVC\STDERR\PHPException::getErrorHandler(); if ($handler instanceof \Lucinda\MVC\STDERR\FrontController) { $handler->setDisplayFormat($this->request->getValidator()->getFormat()); }

First line gets default error handler (STDERR MVC API). Third line sets response format to match that calculated by STDOUT MVC API for route requested.

SecurityListener

A Lucinda\MVC\STDOUT\RequestListener that performs authentication or authorization on requested route by integrating WebSecurity API and OAuth2 Client API based on contents of <security> and <users>/<routes> (required only if you're using access control list authentication & authorization) XML tags.

How is it working?

The algorithm in which security checks are made can be summarized as following:

Notes:
1. It is allowed for a site to have multiple authentication methods, but only a single authorization method.
2. It is allowed for a site to have multiple authentication state persistence methods (eg: session and remember me)

ValidationListener

A Lucinda\MVC\STDOUT\RequestListener able to perform automatic request method or parameters validation by integrating Request Validation API based on contents of <routes> XML tag.

How is it working?

Validation is performed according to following rules:

  1. Each parameter to validate corresponds to a <parameter> subtag of <route> tag, itself child of <routes>.
  2. If path and request parameters have same name (not recommended!), only value of former will be taken into consideration while validating.
  3. If a parameter is optional, attribute "required" must be set to "0" in its matching tag. This means when that parameter is absent from request, validation is skipped. If, however, parameter is present in request, validation will be performed.
  4. It is not mandatory to cover each parameter with a validation rule! This means if a parameter matches no <parameter> tag, its value is simply ignored.
  5. If request method is different from GET, query string parameters are automatically ignored from validation. This is because parameters must be consistent to request method.

For each <parameter> tag there must be a "validator" attribute, whose value is name of a class that extends Lucinda\RequestValidator\ParameterValidator. This class will be the one performing validation algorithm for value of that parameter via its required validate method, whose response must be validation result. Value of validation result can be:

For each parameter validated, validation outcome is packed as a Lucinda\RequestValidator\Result object, which combines:

then sent to a Lucinda\RequestValidator\ResultsList object. Later will be made available by listener to the rest of application through request attribute validation_results.

LocalizationListener

A Lucinda\MVC\STDOUT\RequestListener able to add localization and internationalization capabilities for your application by integrating Internationalization API based on contents of <internationalization> XML tag.

How is it working?

When used, skeleton must first determine client (user agent) locale (aka language) that will later be used in translation. Following locale detection methods are supported:

Each locale detected from client must be reductible to a language and country code (because language standards can differ, as for example between English from US and English from UK), thus should follow standard format: ISO-LANGUAGE-CODE[2], followed by underscore or minus, followed by COUNTRY-CODE[2] (eg: en-US or en_US). If an invalid or not supported locale is supplied, framework skeleton will employ default locale found in XML. Once locale is detected, developers are able to break texts in views into units, where each unit is translated by KEYWORD via translate function.

HttpCachingListener

A Lucinda\MVC\STDOUT\ResponseListener that should always run right before response is rendered on screen, able to apply HTTP caching logic to Response output buffer pending output by integrating HTTP Caching API based on contents of <http_caching> XML tag.

How is it working?

API transforms your PHP application into a proxy service that serves HTML instead of static resources, able to:

As a client of HTTP Caching API, framework skeleton or developers need to implement the algorithm by which Etag/Last-Modified value is calculated for requested resource. It does so via a class that must be present as "class" attribute in <internationalization> tag and extend Lucinda\Framework\CacheableDriver. Skeleton implements:

In addition of that, framework skeleton goes further by allowing pages to have different caching policies. For example, for some pages with low probability of change it's safe to instruct client cache to use its own version of resource for a time period (in seconds) until asking the server again to revalidate.

How useful is it?

This is an incredibly efficient and cost free mechanism to speed up an application, since always answering with 200 OK (as pretty much all other frameworks do) causes client to download response body (which induces slowness depending on network connection speed). All of this makes HttpCachingListener a de-facto requirement for Lucinda-based applications.


Share