Modifying the API output, is it right?


(ehsaan) #1

My co-worker and I are having an argument about this piece of code (written in config/web.php):

'response' => [
    'class' => 'yii\web\Response',
    'on beforeSend' => function($event) {
        if ($event->sender->format != 'html') {
            $data = $event->sender->data;
            if (! $data) {
                $event->sender->statusCode = 500;
            } else {
                if (isset($data['ok']) && $data['ok'])
                    return json_encode($data);
                else
                    $event->sender->statusCode = isset($data['status']) ? $data['status'] : 400;
            }
        }
    }
],

My co-worker believes that this method is simply not standard, while I believe that this will make REST clients work easier by giving them responses in a constant structure and proper HTTP code responses.

It looks like my co-worker and I can’t reach a final conclusion, so we ask your opinion. What do you think?


(Alirz23) #2

it depends on the use case why you would register events on response object, but I would certainly agree with your co-worker, if you just need to send a json response and proper http status code you can simply do that in the controller

public function someAction()
{
    \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
    $data = ['foo' => 'bar']; // this could be a database call
    if (empty($data)) {
        throw new \yii\web\ServerErrorHttpException(); // this right here will set the status code 500
    }

    return $data;
}

I would only register events on response component if I am using a third party package and I need to manipulate the response before I send it to the client, there might be some other uses cases but I would certainly not use it just because i can, explicit code is always better even if you have to write some extra lines


(Stefano Mtangoo) #3

What are you trying to accomplish? I find adding such codes in config, makes it obese with no good reason. If you use modules for API versioning (which you should…) then configure it at the module level. If you are interested with events, I would suggest you check bootstrap interface.

Without knowing what you are trying to accomplish, I can only throw guesses!


(ehsaan) #4

As I’ve already mentioned, I’m trying to keep the API responses structure consistent. Let’s say that we want to respond to a client’s request. The response is structured like this:
{
“ok”: true,
“data”: “…”
}

But, if I’m going to throw an exception, response would look like this:
{
“name”: “Not Found”,
“message”: “”,
“code”: 0,
“status”: 404,
“type”: “yii\web\NotFoundHttpException”
}

Not only our using framework is exposed (which doesn’t really matter, but anyway), the error structure differs in the normal (or 2xx) responses. I think I’ve clarified my goal.


(Stefano Mtangoo) #5

In that case the logical decision is to put those code in the Module. Here is a sample code of how you can do it. It leaves the rest of app clean and we can change it any time without affecting other modules or the main app.

<?php

namespace app\modules\api;

use Yii;

/**
 * api module definition class
 */
class Module extends \yii\base\Module
{
    /**
     * @inheritdoc
     */
    public $controllerNamespace = 'app\modules\api\controllers';

    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();
        
        Yii::$app->setComponents([
            ...
            'response' => [
                'class'=>'yii\web\Response',
                'format' =>  \yii\web\Response::FORMAT_JSON,
                'formatters' => [
                    \yii\web\Response::FORMAT_JSON => [
                        'class' => 'yii\web\JsonResponseFormatter',
                        'prettyPrint' => YII_DEBUG, // use "pretty" output in debug mode
                        //'encodeOptions' => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
                    ],
                ],
            ],
        ]);
        
        Yii::$app->user->enableSession = false;
        Yii::$app->user->loginUrl = null;
        //bind to this event
        Yii::$app->response->on(\yii\web\Response::EVENT_BEFORE_SEND, function ($event) 
        {
            $response = $event->sender;
            if ($response->data !== null && $response->statusCode !== 200) 
            {
                $response->data = [
                    'success' => $response->isSuccessful,
                    'message' => $response->data['message'],
                ];
                ;
            }
        });
    }
}