Reputation: 2697
I'm building a Laravel API. I have two models: Listings and Reservations.
class Listing extends Model
{
public function reservations()
{
return $this->hasMany(Reservation::class);
}
and
class Reservation extends Model
{
public function listing()
{
return $this->belongsTo(Listing::class);
}
I have also created two Laravel resources:
class ListingResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'user_id' => $this->user->id,
'name' => $this->name,
'description' => $this->description,
'price' => $this->price,
];
}
}
I have also defined Laravel Resources:
class ReservationResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'listing_id' => $this->listing->id,
'start_date' => $this->start_date,
'end_date' => $this->end_date,
'price' => $this->price,
'total' => $this->total,
'listing' => $this->listing,
];
}
}
I want to retrieve all reservations belonging to the current logged in user. I have the following in my ReservationsController.
class ReservationController extends Controller
{
public function getUserReservations()
{
$currentUser = JWTAuth::user()->id;
$listings = Listing::where('user_id', $currentUser)->get('id');
$reservations = Reservation::with('Listing')->whereIn('listing_id', $listings)->paginate(25);
return response()->json([
'data' => $reservations
]);
}
public function getAllReservations()
{
return ReservationResource::collection(Reservation::paginate(25));
}
For the method getAllReservations
I'm using resources. For the method getUserReservations
I used a different methodology. This works but I would like to keep my code and API as consistent as possible and hence also use ReservationResource for the getUserReservations
.
How can I convert the code for getUserReservations
in the controller to make use of Laravel resources.
Upvotes: 1
Views: 4966
Reputation: 3567
Assuming your user can have many listings (one to many), and for each of this reservation many listings as well, then I would add a relation to the user to directly retrieve the user reservations through the listing without actually retrieving them:
class User extends Model
{
public function reservations()
{
return $this->hasManyThrough('App\Reservation', 'App\Listing');
}
}
You could then retrieve the reservations and pass data to the API resources:
public function getUserReservations()
{
$reservations = JWTAuth::user()->reservations()->with('listing')->paginate(25);
return ReservationResource::collection($reservations);
}
Your resources classes can be further optimized because in the ListingResource class you need to display the user id, and to do so you use this line:
'user_id' => $this->user->id,
This simple line, under the hood is much more complex than what it seems. It actually executes a new query to retrive the related User model and from that one you can actually get the id.
But if you look at your data, you should already have that information in the Listing model, because that user id is the foreign key on your listings table.
So imagine if you have to print 30 listings: for each listing, you have to retrive the user related to that listing, so you will end up to execute 30 more queries just to retrive a single user each time you print out a listing (because api resources works on a single listing at a time).
This is known as N + 1 query problem. In your case one query to get the listings plus 30 more queries (one for each model) to retrive the related user.
However, if you change the line to:
'user_id' => $this->user_id,
the value will be the same, but you are no longer getting it from the User model, but from the Listing model that you already have got from the database.
You can be sure the value is the same as before because (if you follow laravel's naming convention) that user_id
field is the id of the related model that will be retrived and put into $this->user
field.
The same procedure can be applied on the ReservationResource, because you use:
'listing_id' => $this->listing->id,
but you could do:
'listing_id' => $this->listing_id,
instead.
Old performance explaination
You could optimize better your resources as if you access the listing
property to retrieve the id you actually have to retrieve the losing object (with a query) and get the id from it.
But you have as well the foreign key on the reservation record you are currently parsing.
Your resource could then become:
class ReservationResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
// Assuming your foreign keys follow laravel's convention
'listing_id' => $this->listing_id,
'start_date' => $this->start_date,
'end_date' => $this->end_date,
'price' => $this->price,
'total' => $this->total,
'listing' => $this->listing,
];
}
}
And you would be able to remove the eager-loading of the relationship from the controller, as is no longer needed.
The same would apply for the ListingResource class.
Upvotes: 5