By default framework supports html, json, console view resolving on both STDERR and STDOUT flows. Each view resolver on STDOUT/STDERR flow corresponds to a <resolver> tag in stdout.xml/stderr.xml. Framework comes by default with:
<resolvers>
<resolver format="html" content_type="text/html" class="Lucinda\Project\ViewResolvers\Html" charset="UTF-8"/>
<resolver format="json" content_type="application/json" class="Lucinda\Project\ViewResolvers\Json" charset="UTF-8"/>
<resolver format="console" content_type="text/plain" class="Lucinda\Project\ViewResolvers\Console" charset="UTF-8"/>
</formats>
For STDOUT flow only, it is possible to have multiple resolvers for same content type:
<resolvers>
<resolver format="html" content_type="text/html" class="Lucinda\Project\ViewResolvers\Html" charset="UTF-8"/>
<resolver format="json" content_type="application/json" class="Lucinda\Project\ViewResolvers\Json" charset="UTF-8"/>
<resolver format="custom" content_type="text/html" class="Lucinda\Project\ViewResolvers\CustomResolver" charset="UTF-8"/>
</formats>
In which case <route> that requires another resolver from default needs to set format attribute accordingly:
<routes>
...
<route ... format="custom"/>
</routes>
By default, framework supports only text/html and application/json display formats via its MVC APIs. To add support for another (eg: text/xml), developers need to setup XML tags for each MVC APIs then write referenced view resolver classes.
These view resolvers will be executed to handle responses to requests in a desired display format. To support a new display format, first you need a matching <resolver> tag @ stdout.xml or stderr.xml. Example:
<resolver format="xml" content_type="text/xml" class="Lucinda\Project\ViewResolvers\Xml"/>
This tells that Lucinda\Project\ViewResolvers\Xml class will handle text/xml responses when ever a <route> has a format attribute whose value equals xml. Example:
<route id="sitemap.xml" controller="Lucinda\Project\Controllers\Sitemap" view="sitemap" format="xml"/>
Now you need to write the view resolver itself. It must have a name that equals value of class attribute above, extend Lucinda\MVC\ViewResolver and be found in src/ViewResolvers folder. Example:
namespace Lucinda\Project\ViewResolvers;
class Xml extends \Lucinda\MVC\ViewResolver
{
public function run(): void
{
if (!$this->response->view()->getFile()) {
return;
}
// locates view
$view = $this->application->getViewsPath()."/".$this->response->view()->getFile().".php";
if (!file_exists($view)) {
throw new Lucinda\MVC\ConfigurationException("View file not found!");
}
// compiles view into a string
ob_start();
$_VIEW = $this->response->view()->getData();
require_once($view);
$output = ob_get_contents();
ob_end_clean();
// sends string to output buffer
$this->response->setBody($output);
}
}
Then, in templates/views, you need to write sitemap.php file, as referenced by view attribute above, to be rendered by resolver above. Example:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<?php foreach($_VIEW["items"] as $url=>$priority) { ?>
<url>
<loc><?php echo $url; ?></loc>
<priority><?php echo $priority; ?></priority>
</url>
<?php ?>
</urlset>
This assumes Lucinda\Project\Controllers\Sitemap sent an items response attribute as an array whose key is url and value is priority (for STDERR flow extend Lucinda\STDERR\Controller instead):
namespace Lucinda\Project\Controllers;
use Lucinda\Project\DAO\Pages;
class Sitemap extends Lucinda\STDOUT\Controller
{
public function run(): void
{
$pages = new Pages();
$this->response->view()["items"] = $pages->getAll();
}
}
MVC APIs underneath collect response to be rendered into Lucinda\MVC\Response object. Most of its methods are useful to developers!
Since everything is managed in XML via view attribute of matching <route>, setting views is seldom needed. Following Lucinda\MVC\Response method is dedicated at setting view file:
$this->response->view()->setFile("templates/views/foo/bar")
Response will render file templates/views/foo/bar.html, if response format is htmlIn order to send data to views from controllers, following Lucinda\MVC\Response method is relevant:
view()[NAME] = VALUE: sends data by name and value. Overrides if NAME is already used! Example:
$this->response->view()["user_name"]="John Doe";
By default, view resolvers used by framework send only Content-Type header and Etag/Last-Modified (if HTTP caching was activated via Setting Response Caching section above). To fine tune that behavior, following Lucinda\MVC\Response method is relevant:
headers(NAME, VALUE): sets a response header by name and value. Overrides if NAME is already used! Example:
$this->response->headers("Content-Encoding", "gzip");
If you desire to set response headers by their ISO name instead (so you won't have to keep on remembering their names), you need to open bootstrap index.php file and add this event listener:
$object->addEventListener(Lucinda\STDOUT\EventType::REQUEST, Lucinda\Project\EventListeners\HttpHeaders::class);
then use Lucinda\Headers\Response in controllers or subsequent event listeners to easily set value of response headers. Example:
$this->attributes->getHeaders()->getResponse()->addTransferEncoding("gzip")
By default, HTTP response status for every request is 200 OK (containing full response body) or 304 Not Modified (if it hadn't modified from last call), if HTTP caching was activated (see Setting Response Caching section above).
When an uncaught exception is thrown and framework enters STDERR flow, HTTP response status defaults to 500 Internal Server Error, unless another one is specifically set by http_status of matching <route>. Example:
<route id="Lucinda\STDOUT\PathNotFoundException" http_status="404"/>
For those rare cases in which you need to programmatically control http status of response, following Lucinda\MVC\Response method is relevant:
$this->response->setStatus(404)
Response will render with 404 Not Found status code.To redirect caller to another location, use Lucinda\MVC\Response\Redirect class. Example:
$redirect = new Lucinda\MVC\Response\Redirect(URI);
$redirect->setPermanent(PERMANENT); # optional
$redirect->setPreventCaching(PREVENT_CACHING); # optional
$redirect->run();
Where:
Sometimes it is necessary for controllers to write directly to output stream and thus do the task of view resolvers. To get access to output stream, use this Lucinda\MVC\Response method:
$this->response->setBody("Move along!");
If this is used, MVC APIs will not perform view resolution (since output stream was already filled) and output exactly what you filled there!
Framework promotes caching of unchanged response using your browser via HTTP headers (as if it were an image). To make it possible, you need to open bootstrap index.php file and add this event listener:
$object->addEventListener(Lucinda\STDOUT\EventType::RESPONSE, Lucinda\Project\EventListeners\HttpCaching::class);
which requires another event listener before:
$object->addEventListener(Lucinda\STDOUT\EventType::REQUEST, Lucinda\Project\EventListeners\HttpHeaders::class);
which requires a <headers> tag @ stdout.xml:
<headers cacheable="src/Cacheables/[DRIVER]"/>
Where [DRIVER] is a Lucinda\Headers\Cacheable implementation, able to get a string / date representation of response about to be sent. Framework comes by default with:
After you have completed Setting Response Caching section described above, you are now able to automatically communicate with client browser cache depending on [DRIVER] solution used and deliver an efficient response according to following rules:
Request Header Received |
If Cacheable Used Is: |
Is Response Matching? |
Then Response Header Sent |
With Response HTTP Status |
Response Body Included? |
- | Lucinda\Project\Cacheables\Etag | no | ETag | 200 | yes |
If-None-Match | Lucinda\Project\Cacheables\Etag | yes | ETag | 304 | no |
If-None-Match | Lucinda\Project\Cacheables\Etag | no | ETag | 200 | yes |
- | Lucinda\Project\Cacheables\Date | no | Last-Modified | 200 | yes |
If-Modified-Since | Lucinda\Project\Cacheables\Date | yes | Last-Modified | 304 | no |
If-Modified-Since | Lucinda\Project\Cacheables\Date | no | Last-Modified | 200 | yes |
Framework supports internationalization of views based on user's preffered locale. To make it possible, you need to open bootstrap index.php file and add this event listener:
$object->addEventListener(Lucinda\STDOUT\EventType::REQUEST, Lucinda\Project\EventListeners\Localization::class);
then create a <internationalization> tag to set up the process. Example:
<internationalization locale="en_US" method="header"/>
This sets default locale to en_US and performs internationalization by value of Accept-Language header.
After you have completed Setting Response Internationalization section described above, you are now able to make views translate automatically by client locale (language & country). Following locale detection methods are supported:
If detection fails or locale is not supported, a default set in XML is used (eg: en-US). Once locale was detected, clients must be able to see a translated version of called page! In order for that to happen, developers need to perform following steps:
{"greeting":"Hello!", "who_are_you":"I am %0 from %1."}
{"greeting":"Bon jour!", "who_are_you":"Je suis %0 de %1."}
<p><strong>Hello!<strong><br/>I am Lucian from Bucharest.</p>
You use:<p><strong>${translate('greeting')}<strong><br/>${translate('who_are_you', '', 'Lucian', 'Bucharest')}
By using a second argument for translate function, developers can call another translation file, a very useful feature against using a single huge messages.json. Example:
${translate('who_are_you', 'presentation', 'Lucian', 'Bucharest')}
This tells to look for who_are_you translation in locale/en_US/presentation.json file (if detected locale was en_US). If second function argument is empty, value of "domain" attribute value is used instead.