How to eager load a relation with a condition in Eloquent? - laravel

I have a relation that can be inherited from a parent if not set for the object itself.
For an example setup let's say we have events that have a venue.
class Event extends Model
{
public function venue()
{
return $this->belongsTo('App\Venue');
}
public function activities()
{
return $this->hasMany('App\Activity');
}
}
And there are activities in the events that mostly take place in the same venue, but sometimes could be elsewhere while still belonging to the same event.
class Activity extends Model
{
public function event()
{
return $this->belongsTo('App\Event');
}
public function venue()
{
if ($this->venue_id)
return $this->belongsTo('App\Venue');
return $this->event->venue();
}
}
If I simply request activities for an event and work with them it is fine. But if I try to eager load the venues for activities, I only get the ones that are set directly on the activity, never requesting one from parent.
$activities = $event->activities;
$activities->load('venue'); // Works correctly without this line
foreach ($activities as $activity)
if ($activity->venue) // Doesn't take venue from the parent (event)
echo $activity->venue->name; //Only shows if venue_id is set on activity
Is there any chance to fix the relations so I could load them in bulk?

By their very nature, eager loaded relationships do not have the relationship method run for each parent model. If they did, you would have the N+1 issue, which is exactly what eager loading is meant to solve.
The relationship method is run once on the model that is used to start the query. This gets the base query to run, and then all of the parent model ids are injected into that query.
In order to do what you want, you need to change things up a little bit. First, your Activity can be directly related to venues, so setup that relationship without any conditions. Next, create an accessor method that will return the proper venue for the Activity.
So, your code would look something like:
class Activity extends Model
{
public function event()
{
return $this->belongsTo('App\Event');
}
public function venue()
{
return $this->belongsTo('App\Venue');
}
public function getActivityVenueAttribute()
{
return $this->venue ?? $this->event->venue ?? null;
}
}
The other option would be to always assign the venue_id on the Activity, even if it is the same as the Event venue_id. Then you don't need to worry about the venue id missing on the activity.

Related

Check if an Eloquent element is related to another one (with MorphToMany relationship)

I have those tables :
- blocks
- bloackables
--reports
--modalities
--reportGroups
Block.php :
public function reports()
{
return $this->morphedByMany(Report::class, 'blockable');
}
public function modalities()
{
return $this->morphedByMany(Modality::class, 'blockable');
}
public function reportsGroups()
{
return $this->morphedByMany(ReportsGroup::class, 'blockable');
}
Report.php :
public function blocks()
{
return $this->morphToMany(Block::class, 'blockable');
}
The same type of relationship exists for ReportGroups and Modalities...
Basically I'd like to load a Block instance and then check if the instances of Report, Modality and ReportGroup are related to this block.
The idea is to create an edition form with a checkbox for every Report, Modality or ReportGroup instances).
What are the strategies ? I read the Eloquent documentation but I am still confused...
Best regards,
Take care...
Nicolas
i'm not sure i understand you completely:
you have
$reportInstances,$modalityInstances,$reportGroupInstances
and you want to determine who has relation to the current block;
ok, you should load the model 'block' with its relations, and check the relation existance:
$blcokWithRelations=Block::with(['reports','modalities','reportsGroups'])->find($block->id):
foreach($reportInstances as $reportInstance)
{
if($blcokWithRelations->reports->where('id','=',$reportInstance->id)->get->first()!=null)
// this report has relation to the current block
else
// this report doesn't have relation to the current block
}
the same goes for the rest of relations

Laravel Eloquent Relation belongsTo update

I am trying to update/delete/create in belongsTo relations.
Company has many sports
sports is belonging to Company
Here is two models.
class CompanySports
{
public function company()
{
return $this->belongsTo(Company::class, "company_id","id");
}
class Company
public function sports()
{
return $this->hasMany(CompanySports::class,"company_id","id");
}
}
at controller, when sports is added or modified or remove, what is the best practice to update?
i know that many to many, sync can be used. In this, what is the best solution? Should i compare everytime after loading all from database which is not good practice i believe.
From your code, I would first recommend putting your models in separate files, and ensuring they are singular. If you use the artisan make:model command to generate the stubs, it should do this for you.
// app/CompanySport.php // <-- NOTE singular
class CompanySport // <-- NOTE singular
{
public function company()
{
return $this->belongsTo(Company::class, "company_id","id");
}
}
// app/Company.php
class Company {
public function sports()
{
return $this->hasMany(CompanySport::class,"company_id","id"); // singular
}
}
From there, I find it helpful to build helper methods in the various classes so that the grammar sounds natural and more importantly, belongs to the model. For example:
// app/Company.php
class Company
{
...
public function addSport(CompanySport $sport)
{
$this->sports()->save($sport);
}
public function removeSport(CompanySport $sport)
{
$this->sports()->find($sport->id)->delete();
}
}
These helper functions can then be easily called from anywhere, e.g. controller:
// CompanySportsController.php
public function store(Company $company, CompanySport $sport)
{
$company->addSport($sport);
return redirect('/company/' . $company->id);
}
If you are using these helpers, there is no comparing or sync to be done since you are only using a one to many relationship. Eloquent does everything for you.
Also, I've found this cheatsheet particularly helpful when building out the initial relationships and scaffolding of a new app.
While adding new record of Company Model, you need not to do anything as there is no child for it yet.
While updating an instance of a Company model, again you need not to update anything on its children. As relationship are based on id(primary key) which I believe you don't change while updating.
And now for deleting there are some questions. Do you want to delete the children when the parent is deleting? If so, you can use ON DELETE CASCADE which you can set up in migration like
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
in your spors table.
Well you can make your own function too like answered in here
Well if you don't want to delete the children, you can use softdelete on your Model. set up the relations then like
CompanySports
public function company()
{
return $this->belongsTo(Company::class, "company_id","id")->withTrashed();
}
This way you can get the parent of a children without any error though the parent is deleted.

laravel call to undefined method addEagerConstraints()

I have two models
Post.php
id
post
show_id
type = 'movie' or 'tv'
Show.php
id // this only auto increment counter ids
show_id
show_type = 'movie' or 'tv'
the thing is show can be either tv or movie and may two with the same show_id for exmaple one tv could have a show_id of 10 and also one movie can have it but the types are diffrent
i have in post model
public function show(){
return $this->belongsTo('App\Show', 'show_id');
}
in show model
public function post(){
return $this->hasMany('App\Post', 'id');
}
this relationship get the first show with matching show id it sees, wheather its a movie or tv, i want to restrict it to match type column on both sides
post.php:
public function show() {
return $this->belongsTo('App\Show', 'show_id', 'show_id')
->where('type', $this->type);
}
show.php
public function posts() {
return $this->hasMany('App\Post', 'show_id', 'show_id')
->where('type', $this->show_type);
}
UPDATE (the code above does not work!)
Trying to use where clauses (like in the example below) won't work when eager loading the relationship because at the time the relationship is processed $this->f2 is null.
Read more here: Compoships
I just came accross a package https://github.com/topclaudy/compoships
what it does it allows creating relationships based on more than one FK, which laravel doesnt support by default
I think what you're looking for is a polymorphic relation. Instead of having a model that may be one of two "types" there should probably be two separate models on the same relation. For example:
class Post
{
public function Show()
{
return $this->morphTo();
}
}
class TvShow
{
public function Post()
{
return $this->morphMany('App\Post', 'show');
}
}
class Movie
{
public function Post()
{
return $this->morphMany('App\Post', 'show');
}
}
Then your posts table would have a show_id and show_type field that would relate to either a tv show or movie. Check out the docs for more info, I'm not sure of the rest of your project so I'm not 100% this will fit but anytime you start putting "_type" fields in your table you should question whether or not you should be using a polymorphic relation. This will also likely keep your models cleaner and free of a bunch of if statements as you realize there are other differences between movies and shows and how you handle them.
https://laravel.com/docs/5.7/eloquent-relationships#polymorphic-relations

Associating a child model in a polymorphic relationship causes an infinite loop?

I have a base Message class for an inbox using a polymorphic relationship to attach custom message types which all implement the same interface and behave differently in views based on their type. Display of that all works swimmingly, but I hit a snag when I tried to actually add these with code.
This is the Message class:
<?php
class Message extends Eloquent {
public function author() {
$this->belongsTo("User", "author_id");
}
public function recipient() {
$this->belongsTo("User", "recipient_id");
}
public function message() {
$this->morphTo();
}
}
The model that I attach to message() implements MessageInterface, So I thought I'd be able to make a quick helper to attach this model's relationship via Message::send():
public static function send(MessageInterface $message, User $to, User $from) {
if (! $message->exists)
$message->save();
$parent = new static;
$parent->author()->associate($from);
$parent->recipient()->associate($to);
$parent->message()->associate($message); // line that errors
return $parent->save();
}
But this ends up throwing what looks to be infinite recursion at me:
FatalErrorException: Maximum function nesting level of '100' reached, aborting!
This is the studly function, and from some searching it seems to happen when two models reference each other.
The Schema for the messages table is:
$table->increments("id");
$table->integer("message_id")->unsigned();
$table->string("message_type");
$table->integer("recipient_id")->unsigned();
$table->integer("author_id")->unsigned();
$table->timestamps();
Am I just doing something really wrong, here? I've looked through the morphTo method call in the source and tried seeing if reflection is the problem here (grabbing the function name and snake casing it), but I can't seem to find what is happening. The associate method call is just setting attributes and getting the class name for message_type, then returning a relationship.
There's no useful information in the error; it's a Symfony\Component\Debug\Exception\FatalErrorException with no context.
I'm running Laravel 4.1

Eager loading related model using query scope

Say I have a model Box which holds many widgets. The widgets can be active or inactive (boolean). The Widget model has a query scope which can filter results:
models/box.php:
class Box extends Eloquent
{
public function widgets()
{
return $this->hasMany('Widget');
}
}
models/widget.php:
class Widget extends Eloquent {
public function box()
{
return $this->belongsTo('Box');
}
public function scopeActive($query)
{
return $query->whereActive(true);
}
}
Query scopes make it easy to get all widgets for a given box:
$box_widgets = Box::find($box_id)->widgets()->active()->get();
// returns an Eloquent\Collection containing a filtered array of widgets
But how can I use scopeActive to eliminate this eager loading with method's conditional function?
$boxes = Box::with(array('widgets', function ($q)
{
$q->active();
}))->get();
It seems like there's probably a shorthand for accessing a relation's scope, something like Box::with('widgets->active') or Box::with('widgets.active') but I haven't been able to find it.
Suppose most of the time you want only active widgets, so I suggest:
public function widgets()
{
return $this->hasMany('Widget')->whereActive(true);
}
public function widgetsDisabled()
{
return $this->hasMany('Widget')->whereActive(false);
}
You can setup up more, for example for loading all at once, like you have now.
Then eager load as easily as that:
Box::with('widgets')... // loads only active

Resources