Override response of Rest authentication(HttpBearerAuth) in yii2 - php

I have token based authorization, for which i have did below changes.
In User model, override findIdentityByAccessToken() method as below.
public static function findIdentityByAccessToken($token, $type = null)
{
$userlogin = Userdevices::find()->where(['access_token' => $token])->one();
if ($userlogin == array()) {
return null;
} else {
$User = Users::findOne(['id' => $userlogin->user_id]);
if (!count($User))
{
return null;
}
else {
$dbUser = [
'id' => $User->id,
];
return new static($dbUser);
}
}
}
In Controller, I add behaviors() as below.
public function behaviors()
{
$behaviors[] = [
'class' => \yii\filters\ContentNegotiator::className(),
'formats' => [
'application/json' => \yii\web\Response::FORMAT_JSON,
],
];
$behaviors['authenticator'] = [
'class' => HttpBearerAuth::className(),
];
return $behaviors;
}
When API does not get token or token is not valid it gives below response
{
"name": "Unauthorized",
"message": "You are requesting with an invalid credential.",
"code": 0,
"status": 401,
"type": "yii\\web\\UnauthorizedHttpException"
}
I want to change response as per my requirement as below.
{
"code": 401,
"name": "Unauthorized",
"is_logout": "Y",
"status": "error",
"message": "logout"
}

You can change format of response using beforeSend event of yii\web\Response.
For example add following methods in your api controller:
public function init()
{
parent::init();
\Yii::$app->response->on(
\yii\web\Response::EVENT_BEFORE_SEND,
[$this, 'beforeResponseSend']
);
}
public function beforeResponseSend(\yii\base\Event $event)
{
/**
* #var \yii\web\Response $response
*/
$response = $event->sender;
if ($response->data['status'] == 401) {
$response->data = [
'code' => 401,
'name' => 'Unauthorized',
'is_logout' => 'Y',
'status' => 'error',
'message' => 'logout',
];
}
}
The init method of controller registers the beforeSend event. The beforeResponseSend method handles the event and changes the response format.
If you want to format response in multiple controller it might be better to put the event handler into own class for example
namespace app\components;
class ErrorResponseHelper
{
public static function beforeResponseSend(Event $event)
{
// ... formating code ...
}
}
And register the event in config/web.php
return [
// ...
'components' => [
'response' => [
'class' => 'yii\web\Response',
'on beforeSend' => [
\app\components\ErrorResponseHelper::class,
'beforeResponseSend',
],
],
],
];
But be careful with this solution because this way the \app\components\ErrorResponseHelper::beforeResponseSend will be called during each request.

Related

Laravel API Resource doesn't work in Controller Method

My Post Model has the following format:
{
"id": 1,
"title": "Post Title",
"type: "sample"
}
Here is my controller method:
public function show($id) {
$post = App\Post::find($id);
$transformedPost = new PostResource($post);
return $transformedPost;
}
Here is how my PostResource looks:
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->title,
'type' => $this->convertType($this->type),
];
}
public function convertType($type)
{
return ucfirst($type);
}
So in show/1 response, I should get:
{
"id": 1,
"name": "Post Title",
"type: "Sample"
}
Instead, I am getting:
{
"id": 1,
"title": "Post Title",
"type: "sample"
}
So my PostResource is clearly not working as expected. Key "title" is not being substituted by key "name".
What am I missing here? I know there could be possible duplication of this post but the solutions in other questions seem not working for me.
I am using Laravel 6.x.
//I'm trusting you want to use an Accessor.
//In your Post Model, try something like this
public function getTypeAttribute($value)
{
return ucfirst($value);
}
Your PostResource should now be
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->title,
'type' => $this->type
];
}
Short way;
PostResource;
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->title,
'type' => ucfirst($this->type)
];
}

Yii2 request does not redirect to actionError

I have setup my config to handle errors in the error function of ApiController. For example, when I go to a page that does not exist, it displays: message => page not found like defined in the action. However, when I make an unauthorized request the site does not display a message defined in the action but instead the following response is returned:
{
"name": "Unauthorized",
"message": "Your request was made with invalid credentials.",
"code": 0,
"status": 401,
"type": "yii\\web\\UnauthorizedHttpException"
}
What do I need to change to send a response as defined in actionError?
api/config/main.php:
'components' => [
'user' => [
'identityClass' => 'common\models\User',
'enableAutoLogin' => true,
'identityCookie' => ['name' => '_identity-api', 'httpOnly' => true],
],
'errorHandler' => [
'errorAction' => 'api/error',
],
...
api/controllers/ApiController.php:
<?php
namespace api\controllers;
use Yii;
use yii\web\BadRequestHttpException;
use yii\web\Controller;
use yii\web\Response;
class ApiController extends Controller
{
/**
* #param $action
* #return bool
* #throws BadRequestHttpException
*/
public function beforeAction($action)
{
Yii::$app->response->format = Response::FORMAT_JSON;
$this->enableCsrfValidation = false;
return parent::beforeAction($action);
}
public function afterAction($action, $result)
{
$session = Yii::$app->getSession();
if($session->getIsActive()) {
$session->destroy();
}
return parent::afterAction($action, $result);
}
/**
* #return array
*/
public function actionError()
{
$exception = Yii::$app->errorHandler->exception;
Yii::$app->response->format = Response::FORMAT_JSON;
Yii::$app->response->statusCode = $exception->statusCode;
switch($exception->statusCode)
{
case 401:
return [
'error' => 'Unauthorized',
'message' => $exception->getMessage()
];
case 404:
return [
'message' => 'Page not found'
];
default:
{
return [
'error' => 'Something went wrong',
'message' => $exception->getMessage()
];
}
}
}
}

Customizing JSON response with additional index keys

I use the response() helper on HTTP Response as documented. The straightforward use:
response()->json(
$this->response,
$this->status
)->withHeaders([]);
This will output:
{
"key" : "desired response"
}
However, I wanted to add a key on the response:
$return['message'] = 'Request successful';
$return['data'] = response()->json(
$this->response,
$this->status
)->withHeaders([]);
But the response resulted to:
{
"message": "Request successful",
"data": {
"headers": {},
"original": {
"key" : "desired response"
},
"exception": null
}
}
There are extra keys on the response: headers, original & exception. How can I get rid of that in order to achieve this desired format:
{
"message": "Request successful",
"data": {
"key" : "desired response"
}
}
You can you Laravel Provider
php artisan make:provider ResponseMacroServiceProvider
<?php
namespace App\Providers;
use Response;
use Illuminate\Support\ServiceProvider;
class ResponseMacroServiceProvider extends ServiceProvider
{
/**
* Bootrap the application service
*/
public function boot() {
Response::macro('success', function ($headers, $originals) {
return Response::json([
'message' => "Request successful",
'data' => [
'headers' => $headers,
'original' => $originals,
],
'error' => [
'code' => 0 ,
'message' => []
]
]);
});
Response::macro('error', function ($message, $status = 400) {
if(is_array($message))
$message_repsonse = $message;
else
$message_repsonse = [$message];
return Response::json([
'message' => "Request failed",
'data' => [
'headers' => null,
'original' => null,
]
'error' => [
'code' => $status,
'message' => $message_repsonse
]
]);
});
}
/**
* Register application service
* #override
*/
public function register() {
}
}
Edit your config/app.php
/*
* Application Service Providers...
*/
App\Providers\ResponseMacroServiceProvider::class,
And try to handle at your Controller
$headers = 'Your header';
$originals = Your_Model::find(1);
return response()->success($headers, $originals);
return response()->error($validator->errors()->all(), 300);
you can achieve this by using this code:
$return =[] ;
$return['message'] = 'Request successful'; // your message
$return['data'] = $this->response; // your data
return response()->json( // return json
$return, // your data here
$this->status // status here
);

Yii2 Modify 404 message according to the controller rest api

I am working on Yii2 REST API and all APIs are working fine. I just want to know how can I modify 404 error message according to the controller and their actions.
For example.
I call api /users/100 and the response, when there is no user with 100 id, is
{
"name": "Not Found",
"message": "Object not found: 55554",
"code": 0,
"status": 404,
"type": "yiiwebNotFoundHttpException"
}
I have modified this response by the following code in web.php
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => false,
'showScriptName' => false,
'rules' => [
'<alias:index|about|contact|login|doc>' => 'site/<alias>',
'users/<id:\d+>' => 'users/',
]
]
'response' => [
'class' => 'yii\web\Response',
'on beforeSend' => function ($event) {
$response = $event->sender;
if ($response->data !== null && Yii::$app->request->get('suppress_response_code')) {
$response->data = [
'success' => $response->isSuccessful,
'data' => $response->data,
];
$response->statusCode = 200;
} else if ($response->statusCode == 404) {
$response->data = [
'success' => false,
'message' => 'Resource not found.',
];
$response->statusText = json_encode(['success' => false, 'message' => 'Resource not found.']);
} else if ($response->statusCode == 401) {
$response->statusText = json_encode(['success' => false, 'message' => 'Unauthorized: Access is denied due to invalid key.']);
}
},
'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,
// ...
],
],
],
But now this is the common message for all 404 errors.
What I want is, If it is for user then the message should be
No user found
if it is for categories
No categories found
All these APIs are following the default YII2 REST API standards.
If you are using yii\rest\ActiveController, you could simply use a custom findModel method (no need to modify response as you did).
In your controller :
public function actions()
{
$actions = parent::actions();
// customize findModel
$actions['view']['findModel'] = [$this, 'findModel'];
return $actions;
}
public function findModel($id)
{
$model = User::findOne($id);
if (isset($model )) {
return $model ;
} else {
throw new NotFoundHttpException("No user found");
}
}
EDIT :
users/sdafsda is not handled by your user controller, you should fix your url rule : users/<id> instead of users/<id:\d+>.

Laravel Dingo API - How to respond with multiple collections / transformers?

To initialize my app I have the following route:
/initialize
This returns Taxonomies, Enumerables and a couple of other taxonomy like collections. This saves multiple HTTP requests.
Although with Dingo / Fractal, I cannot see how I can respond with multiple collections?
e.g.
return [
'taxonomies' => $this->response->collection($taxonomies, new TaxonomyTransformer);
'enumerables' => $this->response->collection($enumerables, new EnumerableTransformer);
'otherStuff' => $this->response->collection($otherStuff, new OtherStuffTransformer);
];
return response()->json([
'data' => [
'taxonomies' => $this->fractal->collection($taxonomies, new TaxonomyTransformer);
'enumerables' => $this->fractal->collection($enumerables, new EnumerableTransformer);
'otherStuff' => $this->fractal->collection($otherStuff, new OtherStuffTransformer);
]
], 200);
This should return the JSON in the format you are looking for.
I have the same issue ,and I found the solution from How to use Transformer in one to many relationship. #1054.
Here is the collection I want to return with the transfomer of dingo in my controller.
$user = User::where('email','=',$input['email'])->with('departments')->with('roles')->get();
DepartmentTransformer
class DepartmentTransformer extends TransformerAbstract
{
public function transform($department)
{
return [
'id' => $department['id'],
'name' => $department['name'],
'level' => $department['level'],
'parent_id' => $department['parent_id']
];
}
}
RolesTransformer
class RolesTransformer extends TransformerAbstract
{
public function transform($role)
{
return [
'name' => $role['name'],
'slug' => $role['slug'],
'description' => $role['description'],
'level' => $role['level']
];
}
}
UserTransformer
class UserTransformer extends TransformerAbstract
{
protected $defaultIncludes = ['departments','roles'];
public function transform($user)
{
return [
'id' => $user['id'],
'name' => $user['name'],
'email' => $user['email'],
'phone' => $user['phone'],
];
}
public function includeDepartments(User $user)
{
$dept = $user->departments;
return $this->collection($dept, new DepartmentTransformer());
}
public function includeRoles(User $user)
{
$rl = $user->roles;
return $this->collection($rl, new RolesTransformer());
}
}
In my controller
$user = User::where('email','=',$input['email'])->with('departments')->with('roles')->get();
return $this->response->collection($user, new UserTransformer());
And I got the result
"data": {
{
"id": 43,
"name": "test7",
"email": "test7#foxmail.com",
"phone": "186********",
"departments": {
"data": {
{
"id": 1,
"name": "业务一部",
"level": 1,
"parent_id": 0
}
}
},
"roles": {
"data": {
{
"name": "agent",
"slug": "agent",
"description": "业务员",
"level": 1
}
}
}
}
}
Please take note of the usage of $defaultIncludes and includeXXX() methonds in the UserTransformer.You can get more detail info from Fractal Doc.

Categories