Laravel API Resource doesn't work in Controller Method - php

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)
];
}

Related

Conditionaly Laravel Resource

TicketResource.php
public function toArray($request) {
return [
'id' => $this->id,
'user_id' => $this->user_id,
'title' => $this->title,
'body' => $this->body,
'status' => $this->status,
'created_at' => $this->created_at->toDateTimeString(),
];
}
CommentResource.php
public function toArray($request) {
return [
'id' => $this->id,
'body' => $this->body,
'user_id' => $this->user_id,
'created_at' => $this->created_at->toDateTimeString()
];
}
TicketController.php
public function index() {
return TicketResource::collection(Ticket::all());
}
public function show(Ticket $id) {
$ticket = $id;
return new TicketResource($ticket);
}
Model Ticket.php
public function comments() {
return $this->hasMany('App\Comment');
}
Model Comment.php
public function ticket() {
return $this->belongsTo('App\Ticket');
}
routes/api.php
Route::get('tickets', 'TicketController#index');
Route::get('tickets/{id}', 'TicketController#show');
I want when I request to tickets/{id} URL, I expect to receive this response:
{
"data": {
"id": 1,
"user_id": 2,
"title": "lorem",
"body": "epsum",
"status": "open",
"created_at": "2020-03-04 18:14:56",
"comments": [
{
"id": 1,
"body": "equi",
"user_id": 1,
"created_at": "2020-03-05 18:14:56",
}
]
}
}
On the contrary, when I visit tickets URL, I don't want the comments to be added on each ticket.
How can I implement that?
You need to add relation
This is my model class:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
public function children()
{
return $this->hasMany(Category::class, 'parent_id', 'id')->select('categories.id',
'categories.cid AS key',
'categories.name AS label',
'categories.type',
'categories.lvl');
}
}
in my controller:
$parents = Category::select(
'categories.id',
'categories.id AS key',
'categories.name AS label')->where('lvl', 1)->get();
foreach ($parents as $item) {
$item->children
}
return Response::json($parents, 200, array('Content-Type' => 'application/json;charset=utf8'), JSON_UNESCAPED_UNICODE);
Result:
[
{
"id":2,
"key":2,
"label":"parent label",
"children":[
{
"id":17,
"key":"92697f63-5c50-11ea-80df-5cf9ddf839d3",
"label":"child label",
"type":"Category",
"lvl":2,
}
]
}
]

Override response of Rest authentication(HttpBearerAuth) in yii2

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.

Eager Load Pivot in nested BelongsToMany with Api Resource

I need your help!
I'm having problems returning pivot table information when using ApiResources.
If I have a model like this:
Post.php
public function likes()
{
return $this->belongsToMany(Like::class)
->withPivot(['points']) // I want this in my PostResource::collection !
}
When defining its Resources:
LikeResource.php
public function toArray($request)
{
return [
'like_field' => $this->like_field
];
}
PostResource.php
public function toArray($request)
{
return [
'title' => $this->title,
'likes' => LikeResource::collection($this->whenLoaded('likes'))
];
}
Then in PostController.php
return PostResource::collection(Post::with('likes')->get())
It will return something like this:
Controller Response
[
{
'title' => 'Post 1'
'likes' => [
{
'like_field' => 'Test'
},
{
'like_field' => 'Test 2'
}
]
},
{
'title' => 'Post 2',
...
}
]
The problem is, using that LikeResource::collection() it does not appends pivot information. How could I add 'points' of the pivot table when defining that PostResource??
Thats all,
Thx!
Solution
Well, simply reading a bit in Laravel Docs, to return pivot information you just has to use the method $this->whenPivotLoaded()
So, the PostResource becomes:
public function toArray($request)
{
return [
'title' => $this->title,
'likes' => LikeResource::collection($this->whenLoaded('likes')),
'like_post' => $this->whenPivotLoaded('like_post', function() {
return $this->pivot->like_field;
})
];
}

Remove empty array in Resource::Collection not working Laravel

I want to remove empty array when its return. I have been trying in many different ways, help plz
My controller looks :
public function index()
{
return JobsResource::collection(Jobs::all())->filter();
}
my resource file look:
class JobsCollection extends Resource
{
public function toArray($request)
{
$applicants_count =Job_applicants::where('job_id',$this->id)->get()->count();
if ($applicants_count>0) {
return [
'id' => $this->id,
'title' => $this->title,
'deadline' => $this->deadline,
'applicants_count' => $applicants_count,
'applicants' => new EmployeesResource($this->Employeess->take(2))
];
}
}
}
it always return an empty array
output :
[
[],
{
"id":99,
"title":"Construction Administrator - The Woodlands",
"deadline":"2018-06-30",
"applicants_count":10,
"applicants":[
{
"name":"Mr. Job Seeker",
"pivot":{
"job_id":99,
"employee_id":1
}
},
{
"name":"Michale Feil",
"pivot":{
"job_id":99,
"employee_id":2
}
}
]
}
Controller:
public function index() {
$jobs = Jobs::has('Employeess')->with('Employeess')->withCount('Employeess')->get();
return JobsResource::collection($jobs);
}
Resource file:
class JobsCollection extends Resource
{
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'deadline' => $this->deadline,
'applicants_count' => $this->Employeess_count,
'applicants' => new EmployeesResource($this->Employeess->take(2))
];
}
}

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