Framework Skeleton: Setting STDOUT Controllers

Page controllers are STDOUT MVC API components that query models in order to bind routes (pages requested) to responses. In Lucinda, each controller corresponds to a single route (identified by request uri without querystring) and generally to a single response (aka view).

How are they located?

Controllers are detected based on route (page url) requested by client by finding a <route> tag whose value of "url" attribute matches (without querystring). Once a match is found, controller class name will be identified as value of "controller" attribute. If found, its file will be loaded from disk in folder defined by application>paths>controllers tag as a PHP file with identical name as class (without namespace).

If no controller is found, execution continues as they are not mandatory!

To promote code organization, controllers can also be put in subfolders of search folder above, in which case value of "controller" attribute becomes: subFolder/ControllerName.

How to write a controller?

All controllers must be defined by developers in folder defined by application>paths>controllers tag. In order to be recognized by STDOUT MVC API, they must extend Lucinda\MVC\STDOUT\Controller directly or indirectly and implement its required run method. By extending it, code gains access to Lucinda\MVC\STDOUT\Application, Lucinda\MVC\STDOUT\Request and Lucinda\MVC\STDOUT\Response objects already compiled by API. In accordance to MVC design principles, controllers are privileged in being able to "talk" with models (recipients of shared user defined logic in your application) in order to fulfil their purpose of bridging request and response.

Example:

require_once("application/models/MyModel.php"); class MyController extends Lucinda\MVC\STDOUT\Controller { public function run() { $this->response->attributes("page", $this->request->getURI()->getPage()); $myModel = new MyModel(); $this->response->attributes("info", $myModel->getInfo()); } }

RestController

Whenever a requested resource corresponds to an entity that can be created/shown/deleted depending on HTTP verb it is called with, matching controller should extend RestController (that should be loaded manually in bootstrap after STDOUT FrontController). For that case alone, instead of defining a single run() method, developer will need to implement a method for each HTTP verb page is expected to be called with. That method is named as a uppercase version of matching http verb (GET requests to route will execute GET() method). Failure to have a method for a HTTP verb page is called with will result into a Lucinda\MVC\STDOUT\MethodNotAllowedException thrown to STDERR stream.

Example:

require_once("application/models/MyModel.php"); class MyController extends RestController { // executed automatically when route is called using GET public function GET() { $this->response->attributes("page", $this->request->getURI()->getPage()); $myModel = new MyModel(); $this->response->attributes("info", $myModel->getInfo()); } // executed automatically when route is called using PUT public function PUT() { $myModel = new MyModel(); $myModel->save($this->request->parameters("data")); } }

How are they communicating to views?

Generally, controllers MUST NOT output responses since that is not in their responsibility. In Lucinda applications, it is in framework's responsibility to manage response initialization and display. Initialization is performed before controller is ran and involves setting content type header and view defined as "view" attribute of matching <route> tag. Controller can set (another) view via:
$this->response->setView(PATH)
or send data to latter via:
$this->response->attributes("KEY", VALUE)
For those rare cases when controllers need to output directly, they are able to do so by requesting a redirection via:
$this->response->redirect(LOCATION, IS_PERMANENT)
or writing to output stream via:
$this->response->getOutputStream()->write(TEXT)
Requesting a redirection has immediate effect, while writing to output stream lets framework manage response only that output will be exactly the string given by controller (instead of a view).

Side notes

When should user controllers extend each other?

Whenever a controller is logically a type of another controller, it can extend latter and inherit its functionality. If a controller shares some logic with another controller without being in a type of relationship, it MUST NOT extend the other but delegate shared logic to separate models instead.

Why are there no 'actions'?

The concept of "actions" used by other frameworks, allowing a single controller to serve multiple "related" routes breaks single responsibility principle (because it's no longer a page controller) and it is bound to produce brittle unmaintainable code (because developers have different ideas what is related and what is not). Because we are striving for efficient code that does nothing more than needed and we value simplicity and predictability above all the "actions" idea was completely discarded!


Share