As was mentioned at the beginning of this chapter, the last stage of processing the data before sending it to the client is passing it through the response formatter. Anything that the controller action returned is wrapped into the Response
object, which decides how to ultimately send the data down the pipe. Let's see how we can utilize a custom response formatter for our purposes.
Probably the most obvious use of the custom response formatter is returning the JSON data for a given route. The following is a throwaway snippet of the exploratory code, which returns the list of attributes of the registered services from the database:
public function actionJson() { $models = ServiceRecord::find()->all(); $data = array_map(function ($model) {return $model->attributes;}, $models); $response = Yii::$app->response; $response->format = Response::FORMAT_JSON; $response->data = $data; return $response; }
In the following screenshot, the result of talking to the /services/json
route from the command line is presented. Note that the correct content-type HTTP header was set by the server.
First, we have to note that we do not render anything here. The data we get, an associative array, is wrapped into a manually crafted Response
instance and returned from the controller action and added to the Response.data
field. When we return the string generated by the Controller.render()
method, Yii wraps it in the Response
instance behind the curtains for us. In fact, everything that is not Response
descendant and is being returned from the controller action will be added to the Response.data
field automatically.
Secondly, we do not create the Response
object ourselves, but we get the reference to it as a Yii component instead. This way, we get the default values of its properties already set by Yii at the application initialization step. The Response
object is used exactly once in an application's lifetime, so there's really no difference whether we'll get it as a Yii component or by calling __construct()
.
The format
field tells the Response
object how to format the outgoing data. At the time of writing, Yii developers have the following types built-in, and hence don't need to re-implement them:
Literal |
Effect |
---|---|
|
This is the default literal. |
|
Data will be returned without any processing, except that objects will be serialized using the |
|
Data will be processed by the |
|
Data must be an array with the |
|
Data will be processed by the |
In each case when the Content-Type
header is set, charset
will be set to the value of the charset
property of the Response
instance else, to the value of the charset
property of the application. The only exception here is JSON, where charset
will always be UTF-8 by specification.
Let's do something crazy and serialize the data about services not to JSON but to YAML (see http://www.yaml.org/spec/1.2/spec.html). One of Codeception's dependencies is the YAML library from the Symfony2 project, so we'll just utilize it instead of writing our own serializer.
Create the YamlResponseFormatter
class inside the utilities
directory with the following content:
<?php namespace apputilities; use SymfonyComponentYamlYaml; use yiiwebResponseFormatterInterface; class YamlResponseFormatter implements ResponseFormatterInterface { const FORMAT = 'yaml'; public function format($response) { $response->headers->set('Content-Type: application/yaml'), $response->headers->set('Content-Disposition: inline'), $response->content = Yaml::dump($response->data); } }
Note the highlighted parts. We are using the Yaml
class from the Symfony
library, and we have to implement the ResponseFormatterInterface
.
A class constant named FORMAT
is just convenient so when we set the format of the Response
instance, we'll be more descriptive.
The implementation of the format()
method is pretty straightforward, thanks to the intuitive Yaml::dump()
method. The idea is that we need to set the headers
and content
fields of the Response
instance and not return anything from this method.
The YAML format does not have a Multipurpose Internet Mail Extensions (MIME) type registered (check here: http://www.iana.org/assignments/media-types/media-types.xhtml), so we have arbitrarily decided to use application/yaml
to stress the fact that YAML format a serialization format intended to be read by some program.
To wire this formatter to the Response
component, we need to add the formatter's declaration to the components.response.formatters
item during the application configuration, as follows:
'components' => [ 'response' => [ 'formatters' => [ 'yaml' => [ 'class' => 'apputilitiesYamlResponseFormatter' ] ] ] ]
The highlighted part is the declaration of YamlResponseFormatter
to handle the yaml
format.
Finally, add the action routine to ServiceController
:
public function actionYaml()
{
$models = ServiceRecord::find()->all();
$data = array_map(function ($model) {return $model->attributes;}, $models);
$response = Yii::$app->response;
$response->format = YamlResponseFormatter::FORMAT;
$response->data = $data;
return $response;
}
Note that the code is almost identical to the code that formatted data as JSON. This is the whole point of the custom response formatters. An example result of reaching the /services/yaml
route using CURL is shown in the following screenshot:
This YAML data can be read back to the PHP data structures using the Yaml::parse()
method, which mirrors the Yaml::dump()
method. If you open this route in the browser, it'll open the Save as... dialog prompting you to save the file, since the application/yaml
MIME type will not be opened as plain text in the browser. We included the Content-Disposition: inline
header to force the browser to display the data if it's capable of doing so.
3.16.218.221