lucinda / console-mvc
Ultra high-performance API for handling console requests into responses via MVC pattern for PHP applications
Requires
- php: ^8.1
- ext-simplexml: *
- lucinda/abstract_mvc: ^2.0
Requires (Dev)
- lucinda/unit-testing: ^2.0
This package is auto-updated.
Last update: 2025-03-12 01:59:24 UTC
README
Table of contents:
- About
- Configuration
- Binding Points
- Execution
- Installation
- Running Requests
- Unit Tests
- Reference Guide
- Specifications
About
This API is a skeleton (requires binding by developers) created to efficiently handle console requests into server responses using a MVC version where views and models are expected to be independent while controllers mediate between the two based on user request. Designed with modularity, efficiency and simplicity at its foundation, API is both object and event oriented: similar to JavaScript, it allows developers to bind logic that will be executed when predefined events are reached while handling.
API does nothing more than standard MVC logic, so it may need a framework to be built on top to add further features (eg: DB connectivity). In order to use it, following steps are required from developers:
- 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, a Runnable able to handle requests into responses later on based on above two
- binding events: setting up Runnable classes that will be instanced and run when predefined events are reached during handling process
- configuring shared variables: extend Attributes class to encapsulate variables specific to your project, to be shared between event listeners and controllers
- handling: calling run method @ FrontController to finally handle requests into responses, triggering events above (if any)
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
- running requests: describes how to use installed and configured API to run console requests
- 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
- example: shows a deep example of API functionality based on FrontController unit test
All classes inside belong to Lucinda\ConsoleSTDOUT 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
Tag documentation is completely covered by inherited Abstract MVC API specification! Since STDIN for this API is made of HTTP(s) requests, value of default_route attribute must point to index (homepage) for requests that come with no route.
Resolvers
Tag documentation is completely covered by inherited Abstract MVC API specification!
Routes
Maximal syntax of this tag is:
<routes> <route id="..." controller="..." view="..." format="..."/> ... </routes>
Most tag logic is already covered by Abstract MVC API specification. Following extra observations need to be made:
- id: (mandatory) requested route identified by first console argument when running API!
- 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!
Tag example:
<routes> <route id="users" controller="Lucinda\Project\Controllers\UsersSynchronization" view="users"/> <route id="groups" controller="Lucinda\Project\Controllers\GroupsSynchronization" view="groups"> </routes>
^ It is mandatory to define a route for that defined by default_route attribute @ application XML tag!
If request came without route, default route is used. If, however, request came with a route that matches no id, a RouteNotFoundException is thrown!
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:
XML Attribute @ Tag | Class Prototype | Ability Gained |
---|---|---|
controller @ route | Controller | MVC controller for any request URI |
class @ resolver | \Lucinda\MVC\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 |
---|---|
Attributes | (mandatory) Collects data (via setters and getters) to be made available throughout request-response cycle |
and addEventListener method (see: Binding Events section)!
Execution
Initialization
Now that developers have finished setting up XML that configures the API, they are finally able to initialize it by instantiating FrontController.
Apart from method run required by Runnable interface it implements, class comes with following public methods:
Method | Arguments | Returns | Description |
---|---|---|---|
__construct | string $documentDescriptor, Attributes $attributes | void | Records user defined XML and attributes for later handling |
addEventListener | EventType $type, string $className | void | Binds a listener to an event type |
Where:
- $documentDescriptor: relative location of XML configuration file. Example: "configuration.xml"
- $attributes: see configuring shared variables.
- $type: event type (see binding-events below) encapsulated by enum EventType
- $className: listener class name, including namespace and subfolder, found in folder defined when Attributes was instanced.
Example:
$handler = new FrontController("configuration.xml", new MyCustomAttributes("application/event_listeners"); $handler->run();
Binding Events
As mentioned above, API allows developers to bind listeners to handling lifecycle events via addEventListener method above. Each event type corresponds to an abstract Runnable class:
Type | Class | Description |
---|---|---|
EventType::START | EventListeners\Start | Ran before configuration XML is read |
EventType::APPLICATION | EventListeners\Application | Ran after configuration XML is read into Lucinda\MVC\Application |
EventType::REQUEST | EventListeners\Request | Ran after user request is read into Request object |
EventType::RESPONSE | EventListeners\Response | Ran after Lucinda\MVC\Response body is compiled but before it's rendered |
EventType::END | EventListeners\End | Ran after Lucinda\MVC\Response was rendered back to caller |
Listeners must extend matching event class and implement required run method holding the logic that will execute when event is triggered. It is required for them to be registered BEFORE run method is ran:
$handler = new FrontController("stdout.xml", new FrameworkAttributes(); $handler->addEventListener(EventType::APPLICATION, Lucinda\Project\EventListeners\Logging::class); $handler->run();
To understand how event listeners are located, check specifications!
Configuring Shared Variables
API allows event listeners to set variables that are going to be made available to subsequent event listeners and controllers. For each variable there is a:
- setter: to be ran once by a event listener
- getter: to be ran by subsequent event listeners and controllers
API comes with Attributes, which holds the foundation every site must extend in order to set up its own variables. Unless your site is extremely simple, it will require developers to extend this class and add further variables, for whom setters and getters must be defined!
Handling
Once above steps are done, developers are finally able to handle requests into responses via run method of FrontController, which:
- detects EventListeners\Start listeners and executes them in order they were registered
- encapsulates configuration XML file into Lucinda\MVC\Application object
- detects EventListeners\Application listeners and executes them in order they were registered
- encapsulates request information (environment info, machine info, request info) into Request object
- detects EventListeners\Request listeners and executes them in order they were registered
- initializes empty Lucinda\MVC\Response based on information detected above from request or XML
- locates Controller based on information already detected and, if found, executes it in order to bind models to views
- locates Lucinda\MVC\ViewResolver based on information already detected and executes it in order to feed response body based on view
- detects EventListeners\Response listeners and executes them in order they were registered
- sends Lucinda\MVC\Response back to caller, containing headers and body
- detects EventListeners\End listeners and executes them in order they were registered
All components that are in developers' responsibility (Controller, Lucinda\MVC\ViewResolver, along with event listeners themselves, implement Runnable interface.
Installation
First choose a folder, then write this command there using console:
composer require lucinda/console-mvc
Rename folder above to DESTINATION_FOLDER 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:
$controller = new Lucinda\ConsoleSTDOUT\FrontController("configuration.xml", new Attributes("application/events")); // TODO: add event listeners here $controller->run();
Running Requests
Now that you have installed project on your machine, go to DESTINATION_FOLDER, open console/terminal and write:
php index.php ROUTE PARAM1 PARAM2 ...
Where:
- ROUTE: route to be handled (must be matched with a route XML subtag)
- PARAM1, ...: parameters to send to route, accessible in controllers/listeners as: $this->request->parameters
Unit Tests
For tests and examples, check following files/folders in API sources:
- test.php: runs unit tests in console
- unit-tests.xml: sets up unit tests and mocks "loggers" tag
- tests: unit tests for classes from src folder
Reference Guide
These classes are fully implemented by API:
- Request: encapsulates request information (route, parameters, user info, etc.)
- Request\UserInfo: encapsulates information about console user that made request
Apart of classes mentioned in binding events, following abstract classes require to be extended by developers in order to gain an ability:
- Controller: encapsulates binding Request to Lucinda\MVC\Response based on user request and XML info
Class Request
Class Request encapsulates information detected about user request based on superglobals ($_SERVER, $_GET, $_POST, $_FILES) and defines following public methods relevant to developers:
Method | Arguments | Returns | Description |
---|---|---|---|
getRoute | void | string | Gets first console argument received by API. See execution above! |
getInputStream | void | string | Gets access to input stream for binary requests. |
parameters | void | array | Gets all console arguments received by API, minus first (route). See execution above! |
parameters | int $position | mixed | Gets value of console arguments by position. |
getOperatingSystem | void | string | Gets operating system name API is running into. |
getUserInfo | void | Request\UserInfo | Gets information about user that made request. |
Class Request UserInfo
Class Request\UserInfo encapsulates information detected about user that made request and defines following public methods relevant to developers:
Method | Arguments | Returns | Description |
---|---|---|---|
getName | void | string | Gets requester user name |
isSuper | void | bool | Gets whether or not requester is a superuser/root |
Abstract Class EventListeners Start
Abstract class EventListeners\Start implements Runnable) and listens to events that execute BEFORE configuration XML is read.
Developers need to implement a run method, where they are able to access following protected fields injected by API via constructor:
Field | Type | Description |
---|---|---|
$attributes | Attributes | Gets access to object encapsulating data where custom attributes should be set. |
A common example of a START listener is the need to set start time, in order to benchmark duration of handling later on:
class StartBenchmark extends Lucinda\ConsoleSTDOUT\EventListeners\Start { public function run(): void { // you will first need to extend Application and add: setStartTime, getStartTime $this->attributes->setStartTime(microtime(true)); } }
Abstract Class EventListeners Application
Abstract class EventListeners\Application implements Runnable) and listens to events that execute AFTER configuration XML is read.
Developers need to implement a run method, where they are able to access following protected fields injected by API via constructor:
Field | Type | Description |
---|---|---|
$application | Lucinda\MVC\Application | Gets application information detected from XML. |
$attributes | Attributes | Gets access to object encapsulating data where custom attributes should be set. |
TODO: usage example
Abstract Class EventListeners Request
Abstract class EventListeners\Request implements Runnable) and listens to events that execute AFTER Request object is created.
Developers need to implement a run method, where they are able to access following protected fields injected by API via constructor:
Field | Type | Description |
---|---|---|
$application | Lucinda\MVC\Application | Gets application information detected from XML. |
$request | Request | Gets request information. |
$attributes | Attributes | Gets access to object encapsulating data where custom attributes should be set. |
TODO: usage example
Abstract Class EventListeners Response
Abstract class EventListeners\Response implements Runnable) and listens to events that execute AFTER Lucinda\MVC\Response body was set but before it's committed back to caller.
Developers need to implement a run method, where they are able to access following protected fields injected by API via constructor:
Field | Type | Description |
---|---|---|
$application | Lucinda\MVC\Application | Gets application information detected from XML. |
$request | Request | Gets request information. |
$response | Lucinda\MVC\Response | Gets access to object based on which response can be manipulated. |
$attributes | Attributes | Gets access to object encapsulating data where custom attributes should be set. |
TODO: usage example
Abstract Class EventListeners End
Abstract class EventListeners\End implements Runnable) and listens to events that execute AFTER Lucinda\MVC\Response was rendered back to caller.
Developers need to implement a run method, where they are able to access following protected fields injected by API via constructor:
Field | Type | Description |
---|---|---|
$application | Lucinda\MVC\Application | Gets application information detected from XML. |
$request | Request | Gets request information. |
$response | Lucinda\MVC\Response | Gets access to object based on which response can be manipulated. |
$attributes | Attributes | Gets access to object encapsulating data where custom attributes should be set. |
A common example of a START listener is the need to set end time, in order to benchmark duration of handling:
class EndBenchmark extends Lucinda\ConsoleSTDOUT\EventListeners\End { public function run(): void { $benchmark = new Benchmark(); $benchmark->save($this->attributes->getStartTime(), microtime(true)); } }
Abstract Class Controller
Abstract class Controller implements Runnable) to set up response (views in particular) by binding information detected beforehand to models. It defines following public method relevant to developers:
Method | Arguments | Returns | Description |
---|---|---|---|
run | void | void | Inherited prototype to be implemented by developers to set up response based on information saved by constructor |
Developers need to implement run method for each controller, where they are able to access following protected fields injected by API via constructor:
Field | Type | Description |
---|---|---|
$application | Lucinda\MVC\Application | Gets application information detected from XML. |
$request | Request | Gets request information. |
$response | Lucinda\MVC\Response | Gets access to object based on which response can be manipulated. |
$attributes | Attributes | Gets access to object encapsulating data set by event listeners beforehand. |
TODO: usage example
To understand more about how controllers are detected, check specifications!
Class Attributes
Class Attributes encapsulates data collected throughout request-response cycle, each corresponding to a getter and a setter, and made available to subsequent event listeners or controllers. API already comes with following:
Method | Arguments | Returns | Description |
---|---|---|---|
getValidFormat | void | string | Gets final response format to use |
getValidRoute | void | string | Gets final route requested |
Most of the data collected will need to be set by developers themselves to fit their project demands so in 99% of cases class will need to be extended for each project!
TODO: usage example
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
- How Are View Resolvers Located
- How Is Route Detected
- How Are Controllers Located
- How Are Request Parameters Detected
- How Are Views Located
How Is Response Format Detected
This section follows parent API specifications only that routes are detected based on value of $_SERVER["REQUEST_URI"].
How Are View Resolvers Located
This section follows parent API specifications in its entirety.
How Is Route Detected
This section follows parent API specifications only that routes are detected based on first argument received by API in console request.
php index.php ROUTE PARAM1 PARAM2 ...
Let's take this XML for example:
<application default_route="index" ...> ... </application> <routes> <route id="index" .../> <route id="users" .../> </routes>
There will be following situations for above:
If Route Requested | Then Route ID Detected | Description |
---|---|---|
index | Because requested route came empty, that identified by default_route is used | |
users | users | Because requested route is matched to a route, specific route is used |
hello | - | Because no route is found matching the one requested a RouteNotFoundException is thrown |
How Are Controllers Located
This section follows parent API specifications only that class defined as controller attribute in route tag must extend Controller.
How Are Request Parameters Detected
Users are able to send one or more request parameters in API request:
php index.php ROUTE PARAM1 PARAM2 ...
Then query in controllers/event-listeners those parameters via:
$parameters = $this->request->parameters();
If original request was:
php index.php users hello world
Then route will be "users" and parameters will be ["hello", "world"]!
How Are Views Located
This section follows parent API specifications in its entirety. Extension is yet to be decided, since it depends on type of view resolved!