Eager Loading vs Lazy Loading with API Resource - laravel

Could you please suggest which option I should use for the following example.
I'm currently building an API to get Places, Place model contains User and Location models.
Relationships are set like that:
class Place extends Model
{
protected $guarded = [];
public function createdBy()
{
return $this->belongsTo(User::class, 'created_by', 'id');
}
public function location()
{
return $this->belongsTo(Location::class);
}
}
Option 1: Lazy Loading:
public function getPlaces()
{
return Place::all()->get();
}
class PlaceResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'createdBy' => (new UserResource($this->createdBy)),
'location' => (new LocationResource($this->location)),
];
}
}
Option 2: Eager Loading:
public function getPlaces()
{
return Place::with([
'createdBy',
'location',
])
->get();
}
class PlaceResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'createdBy' => (new UserResource($this->createdBy)),
'location' => (new LocationResource($this->location)),
];
}
}
Logically thinking with the Eager Loading option data should loads quicker, right? I have tested both options for about 100 places but the loading time seems to be the same.
What's the right way (option to use) to load data quicker?

It is better to Eager Load those relationships when dealing with a result set like that. Since you are iterating through each record in that result set and accessing the relationship this would cause a N+1 issue otherwise. Since it is 2 relationships being accessed it would be (N*2) + 1 .

Related

Laravel Algolia Scout, whereIn on relationships

I am working on a Laravel project. I am using Scout based on Algolia. But I struggling to apply whereIn on the relationships. I have 2 models as follow.
Place.php
class Place extends Model
{
use Searchable, Localizable;
protected $with = [
'images',
'phones',
'emails',
'categories'
];
protected $casts = [
'is_featured' => 'boolean'
];
public function categories()
{
return $this->belongsToMany(Category::class, 'place_category');
}
public function searchableAs()
{
return "places_index";
}
public function toSearchableArray()
{
$record = $this->toArray();
$record['_geoloc'] = [
'lat' => $record['latitude'],
'lng' => $record['longitude'],
];
$record['categories'] = $this->categories->map(function ($data) {
return [
'id' => $data['id'],
'en_name' => $data['en_name'],
'mm_name' => $data['mm_name'],
];
})->toArray();
unset($record['created_at'], $record['updated_at'], $record['latitude'], $record['longitude']);
unset($record['images'], $record['phones'], $record['emails']);
return $record;
}
}
Category.php
class Category extends Model
{
use Searchable;
protected $touches = [
'places',
];
public function places()
{
return $this->belongsToMany(Place::class, 'place_category');
}
}
Now, I am searching the Place models/ data filtering by category. As you can see, I have also indexed the categories with places in toSearchableArray method.
I am trying to achieve something like this.
Place::search($keyword)->whereIn('categories', ????);//how can I filter by the Ids here
How can I do that?

Laravel Polymorphic Relationships with order by

Laravel version 7.2.5.
I am using Polymorphic Relationships to store the access logs (login, logout) for multi-role application.
The data storing part is working completely fine. Now, I need to show the list of records in desc format with pagination. But, It's loading the data in the asc format
SomeModel.php
class SomeModel extends Model
{
/**
* Polymorphic Relationships
*/
public function loginLog()
{
return $this->morphMany(LoginLog::class, 'employable');
}
public function show($token)
{
return self::where([
'token' => $token,
])->with([
'loginLog:employable_id,ip,created_at,updated_at'
])->first() ?? null;
}
}
I did find the solution. But, somehow it doesn't feel the appropriate way to solve this issue.
Here is the link Laravel order results by column on polymorphic table
Try this
class SomeModel extends Model
{
/**
* Polymorphic Relationships
*/
public function loginLog()
{
return $this
->morphMany(LoginLog::class, 'employable')
->latest();
}
}
I found another way to solve this issue...
class SomeModel extends Model
{
/**
* Polymorphic Relationships
*/
public function loginLog()
{
return $this->morphMany(LoginLog::class, 'employable');
}
public function show($token, $pagination = 0)
{
return self::where([
'token' => $token,
])->with([
'loginLog' => function ($query) use ($pagination) {
return $query->select([
'employable_id',
'ip',
'created_at',
'updated_at',
])
->skip($pagination)
->take(\Common::paginationLimit())
->orderBy('id', 'DESC');
}
])
->orderBy('id', 'DESC')
->first('id') ?? null;
}
}
Since I do not need the base table's parameters, therefore, I am fetching only id from it.
Also, with this way I am able to use the pagination too (I am using loadmore pagination).

Pagination with relationships

I'm having problems returning the data for two relationships when using paginate.
$things = $this->model->with('fruits')->with('animals')->paginate(5, ['id, 'name']);
Returns the "things" I want but the "fruits" and "animals" arrays are empty.
To clarify, the relationships check out, things "has Many" fruits and also "has many through (fruits)" animals.
public function fruits()
{
return $this->hasMany('App\Fruit');
}
public function animals()
{
return $this->hasManyThrough('\App\Animal', '\App\Fruit');
}
I would like to be able to load "things" along with any relationships while being able to paginate the "things"
with() only tells Eloquent to pre-fetch the related models, so you can avoid the N+1 query problem. It does not mean that when you return the model, that information is also returned.
I would create an Eloquent Resource:
Class FruitResource extends \Illuminate\Http\Resources\Json\Resource {
public function toArray($request) {
return [
'id' => $this->id,
'name' => $this->name
];
}
}
Class AnimalResource extends \Illuminate\Http\Resources\Json\Resource {
public function toArray($request) {
return [
'id' => $this->id,
'name' => $this->name
];
}
}
Class ThingResource extends \Illuminate\Http\Resources\Json\Resource {
public function toArray($request) {
return [
'id' => $this->id,
'fruits' => FruitResource::collection($this->fruits),
'animals' => AnimalResource::collection($this->animals)
];
}
}
Once you created these resource, you can alter your controller to do the following:
$things = $this->model->with('fruits')->with('animals')->paginate(5, ['id, 'name']);
return ThingsResource::make($things);
By structuring your resources, you can easily get more control on what you return. Make life a lot nicer...

Laravel API ResourceCollection WhenLoaded

I try to include a relationship in my resource array if it has been eager loaded, but don't get it working.
Anyone has an idea, how I can check the relationships in the ResourceCollection?
Database schema looks like this:
Here is my Post Model
class Post extends Model
{
function categories() {
return $this->belongsToMany('App\Category');
}
}
Here is my Category Model
class Category extends Model
{
function posts() {
return $this->belongsToMany('App\Post');
}
}
Here is my Post Controller
Class PostController extends Controller
{
public function index()
{
return new PostResourceCollection(Post::with("categories")->get());
}
}
Here is my Post ResourceCollection
class PostResourceCollection extends ResourceCollection
{
public function toArray($request)
{
return [
'data' => $this->collection->transform(function($page){
return [
'type' => 'posts',
'id' => $page->id,
'attributes' => [
'name' => $page->title,
],
];
}),
//'includes' => ($this->whenLoaded('categories')) ? 'true' : 'false',
//'includes' => ($this->relationLoaded('categories')) ? 'true' : 'false',
];
}
}
Maybe too late, below solution is a workaround for this case:
return [
...,
'includes' => $this->whenLoaded('categories', true),
];
Loading custom attribute:
return [
...,
'includes' => $this->whenLoaded('categories', fn() => $this->categories->name),
];
You relationship is wrong, a post belongs to many categories while a category has many posts so change:
class Category extends Model
{
function posts() {
return $this->belongsToMany('App\Post', 'category_post');
}
}
to
class Category extends Model
{
function posts() {
return $this->hasMany('App\Post', 'category_post');
}
}
Now when you load the post you can load the categories also:
$posts = Post::with('categories')->get();
got it.. That was the missing piece. if anyone has a better solution for this it would be much appreciated.
foreach ($this->collection as $item) {
if ($item->relationLoaded('categories')) {
$included = true;
}

How to create self referential relationship in laravel?

I am new to Laravel. I Just want to create a self referential model. For example, I want to create a product category in which the field parent_id as same as product category id. How is this possible?
Model Shown below
class Product_category extends Eloquent
{
protected $guarded = array();
public static $rules = array(
'name' => 'required',
'parent_id' => 'required'
);
function product_category()
{
return $this->belongsto('Product_category','parent_id');
}
}
It results Maximum function nesting level of '100' reached, aborting! Error
You can add a relation to the model and set the custom key for the relation field.
Update:
Try this construction
class Post extends Eloquent {
public function parent()
{
return $this->belongsTo('Post', 'parent_id');
}
public function children()
{
return $this->hasMany('Post', 'parent_id');
}
}
Old answer:
class Post extends Eloquent {
function posts(){
return $this->hasMany('Post', 'parent_id');
}
}
Your model is not at fault for producing the "maximum function nesting level of '100' reached" error. It's XDebug's configuration; increase your xdebug.max_nesting_level.
The following is from a 2015 post by #sitesense on laracasts.com:
This is not a bug in Laravel, Symfony or anything else. It only occurs when XDebug is installed.
It happens simply because 100 or more functions are called recursively. This is not a high figure as such and later versions of XDebug (>= 2.3.0) have raised this limit to 256. See here:
http://bugs.xdebug.org/bug_view_page.php?bug_id=00001100
EDIT: In fact the latest Homestead provisioning script already sets the limit to 250. See line 122 here:
https://github.com/laravel/settler/blob/master/scripts/provision.sh#L122
So the addition of xdebug.max_nesting_level = 250 to php.ini should do it.
I've added a little more to the code based on your comments trying to access the parent!
class Person extends \Eloquent {
protected $fillable = [];
var $mom, $kids;
function __construct() {
if($this->dependency_id<>0) {
$this->mother->with('mother');
}
}
public function children() {
$children = $this->hasMany('Person','dependency_id');
foreach($children as $child) {
$child->mom = $this;
}
return $children;
}
public function mother() {
$mother = $this->belongsTo('Person','dependency_id');
if(isset($mother->kids)) {
$mother->kids->merge($mother);
}
return $mother;
}
}
Then you can access the parent from the child with eager loading, see more here: http://neonos.net/laravel-eloquent-model-parentchild-relationship-with-itself/
you can refer self, using $this
class Post extends Eloquent {
function posts(){
return $this->hasMany($this, 'parent_id');
}
}
Take a look at my answer here.
The key is this code below in Model.php
public function children()
{
return $this->hasMany(Structure::class, 'parent_id')->with('children');
}

Resources