Tutorials: Working with Responses

Setting View Resolvers

By default framework supports html and json view resolving on both STDERR and STDOUT flows. Json will be handled by JsonResolver/JsonRenderer, while html will generally be handled by HtmlRenderer/HtmlResolver.

Each view resolver on STDOUT flow corresponds to a tag in stdout.xml. Framework comes by default with:

<format name="html" content_type="text/html" class="HtmlResolver" charset="UTF-8"/> <format name="json" content_type="application/json" class="JsonResolver" charset="UTF-8"/> </formats>

Each view resolver on STDERR flow corresponds to a tag in stderr.xml. Framework comes by default with:

<resolvers> <resolver format="html" content_type="text/html" class="HtmlRenderer" charset="UTF-8"/> <resolver format="json" content_type="application/json" class="JsonRenderer" charset="UTF-8"/> </resolvers>

Setting multiple resolvers for same content type

For STDOUT flow only, it is possible to have multiple resolvers for same content type:

<format name="html" content_type="text/html" class="HtmlResolver" charset="UTF-8"/> <format name="custom" content_type="text/html" class="CustomResolver" charset="UTF-8"/> <format name="json" content_type="application/json" class="JsonResolver" charset="UTF-8"/> </formats>

In which case that requires another resolver from default needs to set format attribute accordingly:

... <route ... format="custom"/> </routes>

Writing View Resolvers

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.

STDOUT View Resolvers

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 tag @ stdout.xml. Example:

<format name="xml" content_type="text/xml" class="XMLRenderer"/>

This tells that XMLRenderer class will handle text/xml responses when ever a has a format attribute whose value equals xml. Example:

<route url="sitemap.xml" controller="SitemapController" 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 and be found in application/resolvers folder. Example:

class XMLResolver extends { 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 ("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 application/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 SitemapController sent an items response attribute as an array whose key is url and value is priority:

class SitemapController extends { public function run(): void { ... $this->response->view()["items"] = $model->getPages(); } }

STDERR View Resolvers

These view resolvers will be executed to handle responses to exceptions in a desired display format. To support a new display format, first you need a matching tag @ stderr.xml. Example:

<renderer format="html" content_type="text/xml" class="XMLRenderer"/>

This tells that XMLRenderer class will handle text/xml responses if ErrorListener is active and originally called had a format attribute whose value equals xml (see above).

Now you need to write the view resolver itself. It must have a name that equals value of class attribute above, implement and be found in application/renderers folder. Example:

class XMLRenderer implements { public function run(): void { if (!$this->response->view()->getFile()) { return; } // locates view $view = $response->view()->getFile().".php"; if (!file_exists($view)) { throw new ("View file not found!"); } // compiles view into a string ob_start(); $_VIEW = $response->view()->getData(); require_once($view); $output = ob_get_contents(); ob_end_clean(); // sends string to output buffer $this->response->setBody($output); } }

Then, in application/views, you need same sitemap.php file as in previous section and an ErrorsController modified to send items response attribute as an array whose key is url and value is priority:

class ErrorsController extends { public function run(): void { ... $this->response->view()["items"] = $model->getPages(); } }

Setting Response Information

Framework or developers collect response information into Response objects: a (encapsulating response to requests) and a (encapsulating response to exceptions).

Both classes were done with symmetry in mind (for shared logic, methods have same signature). Most of their shared methods are useful to developers!

Setting View Files

Since everything is managed in XML via view attribute of matching , setting views is seldom needed. Following method is dedicated at setting view file:

Sending Data to Views

In order to send data to views from controllers, following method is relevant:

Setting Response Headers

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 method is relevant:

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(::REQUEST, "HttpHeadersListener");

then use in controllers or subsequent event listeners to easily set value of response headers. Example:

$this->attributes->getHeaders()->getResponse()->addTransferEncoding("gzip")

Setting Response Status

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 . Example:

<exception class="" http_status="404"/>

For those rare cases in which you need to programmatically control http status of response, following method is relevant:

Redirecting to Another Location

To redirect caller to another location, following static method is available:

Writing Directly to Output Stream

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 method:

If this is used, MVC APIs will not perform view resolution (since output stream was already filled) and output exactly what you filled there!

Setting Response Caching

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(::RESPONSE, "HttpCachingListener");

which requires another event listener before:

$object->addEventListener(::REQUEST, "HttpHeadersListener");

which requires a tag @ stdout.xml:

<headers cacheable="application/cacheables/[DRIVER]"/>

Where [DRIVER] is a implementation, able to get a string / date representation of response about to be sent. Framework comes by default with:

Performing Response Caching

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 Cacheable Response Header Matching Response Status Response Body
- EtagCacheable ETag no 200 yes
If-None-Match EtagCacheable ETag yes 304 no
If-None-Match EtagCacheable ETag no 200 yes
- DateCacheable Last-Modified no 200 yes
If-Modified-Since DateCacheable Last-Modified yes 304 no
If-Modified-Since DateCacheable Last-Modified no 200 yes

Setting Response Internationalization

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(::REQUEST, "LocalizationListener");

then create a 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.

Performing Response Internationalization

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:

  1. create a locale folder in site root
    Name must equal value of "folder" attribute in tag above.
  2. create a subfolder in above for each locale your site supports
    Name must equal locale in language_COUNTRY code format (eg: en_US, fr_FR)
  3. create a messages.json file inside above subfolder, which will store your translations/locale
    Name must equal value of "domain" attribute in tag above.
  4. break down views into small translatable units and store each translation by name and value in file above
    Example of locale/en_US/messages.json:
    {"greeting":"Hello!", "who_are_you":"I am %0 from %1."}
    Example of locale/fr_FR/messages.json:
    {"greeting":"Bon jour!", "who_are_you":"Je suis %0 de %1."}
  5. convert views to never use hardcoded texts, but calls to translate function instead.
    So instead of:
    <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')}
  6. make sure translations for default locale are always filled (eg: locale/en_US/messages.log above)

By using a second argument for translate functions, 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.

×