Web and mobile applications are more than just rendered HTML nowadays. Modern architecture moves the UI to the client, where all user interactions are handled by the client-side, utilizing server APIs to drive the frontend. The JSON and XML formats are often used for serializing and transmitting structured data over a network, so the ability to create such responses is a must for any modern server framework.
As you probably know, in Yii2 you need to return
the result from your action, instead of echoing it directly:
// returning HTML result
return $this->render('index', [
'items' => $items,
]);
Good thing about it is now you can return different types of data from your action, namely:
- an array
- an object implementing
Arrayable
interface - a string
- an object implementing
__toString()
method.
Just don't forget to tell Yii what format do you want as result, by setting \Yii::$app->response->format
before return
. For example:
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
Valid formats are:
- FORMAT_RAW
- FORMAT_HTML
- FORMAT_JSON
- FORMAT_JSONP
- FORMAT_XML
Default is FORMAT_HTML
.
Let's return an array:
public function actionIndex()
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$items = ['some', 'array', 'of', 'data' => ['associative', 'array']];
return $items;
}
And - voila! - we have JSON response right out of the box:
Result
{
"0": "some",
"1": "array",
"2": "of",
"data": ["associative", "array"]
}
Note: you'll get an exception if response format is not set.
As we already know, we can return objects too.
public function actionView($id)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$user = \app\models\User::find($id);
return $user;
}
Now $user is an instance of ActiveRecord
class that implements Arrayable
interface, so it can be easily converted to JSON:
Result
{
"id": 1,
"name": "John Doe",
"email": "john@example.com"
}
We can even return an array of objects:
public function actionIndex()
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$users = \app\models\User::find()->all();
return $users;
}
Now $users
is an array of ActiveRecord objects, but under the hood Yii uses \yii\helpers\Json::encode()
that traverses and converts the passed data, taking care of types by itself:
Result
[
{
"id": 1,
"name": "John Doe",
"email": "john@example.com"
},
{
"id": 2,
"name": "Jane Foo",
"email": "jane@example.com"
},
...
]
Just change response format to FORMAT_XML
and that's it. Now you have XML:
public function actionIndex()
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_XML;
$items = ['some', 'array', 'of', 'data' => ['associative', 'array']];
return $items;
}
Result
<response>
<item>some</item>
<item>array</item>
<item>of</item>
<data>
<item>associative</item>
<item>array</item>
</data>
</response>
And yes, we can convert objects and array of objects the same way as we did before.
public function actionIndex()
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_XML;
$users = \app\models\User::find()->all();
return $users;
}
Result:
<response>
<User>
<id>1</id>
<name>John Doe</name>
<email>john@example.com</email>
</User>
<User>
<id>2</id>
<name>Jane Foo</name>
<email>jane@example.com</email>
</User>
</response>
Let's create a custom response format. To make example a bit fun and crazy we'll respond with PHP arrays.
First of all, we need formatter itself. Create components/PhpArrayFormatter.php
:
<?php
namespace app\components;
use yii\helpers\VarDumper;
use yii\web\ResponseFormatterInterface;
class PhpArrayFormatter implements ResponseFormatterInterface
{
public function format($response)
{
$response->getHeaders()->set('Content-Type', 'text/php; charset=UTF-8');
if ($response->data !== null) {
$response->content = "<?php\nreturn " . VarDumper::export($response->data) . ";\n";
}
}
}
Now we need to register it in application config (usually it's config/web.php
):
return [
// ...
'components' => [
// ...
'response' => [
'formatters' => [
'php' => 'app\components\PhpArrayFormatter',
],
],
],
];
Now it's ready to be used. In controllers/SiteController
create a new method actionTest
:
public function actionTest()
{
Yii::$app->response->format = 'php';
return [
'hello' => 'world!',
];
}
That's it. After executing it, Yii will respond with the following:
<?php
return [
'hello' => 'world!',
];
You can use the ContentNegotiator
controller filter in order to choose format based on what is requested. In order
to do so you need to implement behaviors
method in controller:
public function behaviors()
{
return [
// ...
'contentNegotiator' => [
'class' => \yii\filters\ContentNegotiator::className(),
'only' => ['index', 'view'],
'formatParam' => '_format',
'formats' => [
'application/json' => \yii\web\Response::FORMAT_JSON,
'application/xml' => \yii\web\Response::FORMAT_XML,
],
],
];
}
public function actionIndex()
{
$users = \app\models\User::find()->all();
return $users;
}
public function actionView($id)
{
$user = \app\models\User::findOne($id);
return $user;
}
That's it. Now you can test it via the following URLs:
/index.php?r=user/index&_format=xml
/index.php?r=user/index&_format=json