Merging results from belongsToMany and belongsTo relationships in Eloquent - laravel

In my Image model I have two relationships with my Article model, one many-to-many and one one-to-many:
public function articlesAlbums()
{
return $this->belongsToMany('Article', 'article_image', 'image_id', 'article_id')->publishedFilter();
}
public function articleThumb()
{
return $this->hasMany('Article')->publishedFilter();
}
I merge the results from these to get all images used by Article:
public function getArticlesAllAttribute()
{
return $this->articlesAlbums->merge($this->articleThumb);
}
In my Article model I have two relationships with my Image model:
public function images()
{
return $this->belongsToMany('Image', 'article_image', 'article_id', 'image_id');
}
public function thumbnail()
{
return $this->belongsTo('Image', 'image_id');
}
I would like to merge these as well, same way I do in my Image model:
public function getImagesAllAttribute()
{
return $this->images->merge($this->thumbnail);
}
But that doesn't work, it seems to be because my thumbnail relationship is belongsTo, not hasMany. So maybe it's not a collection. When I try I get an exception:
Call to a member function getKey() on a non-object
I've tried converting it to a collection with:
new Collection($this->thumbnail)
but get an error saying:
__construct() must be of the type array, object given
How can I merge $this->images and $this->thumbnail in my Article model to get the same results that I do in my Image model? Meaning that the results are merged with no duplicates.
Many thanks.
Update:
Since Razor made me realized that $this->thumbnail did not in fact return a collection, but rather a single object it made me rethink if merge was really the proper function to use.
return $this->images->merge($this->thumbnail()->get());
This seemed to cause a lot of unnecessary queries, even though I was eager loading.
So I ended up doing this instead:
public function getImagesAllAttribute()
{
$imagesAll = $this->images;
if ( ! is_null($this->thumbnail)) $imagesAll->add($this->thumbnail);
return $imagesAll->unique();
}
It drastically reduced the number of queries, and the result is the same.

$this->thumbnail is the same as $this->thumbnail()->first(), to get a collection, you may try:
public function getImagesAllAttribute()
{
return $this->images->merge($this->thumbnail()->get());
}

Related

Querying on a pivot table using Model::query in Livewire

I have been using this table package lately, MedicOneSystems/livewire-datatables and I cannot seem to grab information from pivot tables.
public function builder()
{
return $this->model::query();
}
This is in their base livewire controller, then you can create your own controller by using some of the functions they offer you.
My problem is that once I enter a workbook(Workbook) I need to get the stations that are stored in a pivot table(StationWorkbook) and then go further and take the station(Station) names and everything else from the station table. I am able to get either all the stations, or just the pivot or just the workbook, but I cannot seem to be able to go through them
This is what I have tried so far in my controller.
public function builder()
{
return $this->current_workbook->stations->query();
}
Method Illuminate\Database\Eloquent\Collection::query does not exist
public function builder()
{
return Station::query();
}
public function builder()
{
return StationWorkbook::query();
}
public function builder()
{
return Workbook::query();
}
public function builder()
{
return Workbook::query()->where('id', $this->workbook)->stations;
}
Property [stations] does not exist on the Eloquent builder instance.
None of these was able to return the stations of the workbook.
You are trying to access the collection stations on the Eloquent QueryBuilder - that won't work until after you use get(), which you cannot do with this package, since it expects a QueryBuilder instance.
I'm not entirely sure what you need here, but if you need the station that belongs to that Workbook, then you need to return the querybuilder of the Stations model.
If we assume that you have defined a workbook relation on your Station model, then you can use whereHas() - which can be called on the querybuilder - to limit the data you need. I'm assuming that $this->workbook is the ID, and not the full instance of the model (if its the model, you need to use $this->workbook->id).
public function builder()
{
return Station::whereHas('workbook', function($query) {
return $query->where('id', $this->workbook);
});
}
You can also eager-load the workbook in that query by adding with(). Eager-loading means that Laravel will retrieve the stations joined with the workbook, so that you can access the data of both without extra queries required.
public function builder()
{
return Station::with('workbook')->whereHas('workbook', function($query) {
return $query->where('id', $this->workbook);
});
}

Getting data from two separate models in laravel

I wanted to get data which is related to an id and I used the find($id) method to get those data, now I wanna get data from two tables which have one to many relationship.
How can I get data which is related to the same id from two table?
I try to this way but it hasn't worked:
public function show($id)
{
$post=Clients::find($id);
return view('pet.shw',['post'=>$post,'pets'=>$post->pets]);
}
Why you dont use with() I have simple solution but maybe not best solution:
Post::with('pets')->where('id',$id)->first();
Maybe below code is work to i dont test it:
Post::with('pets')->find($id);
Of course you should have comments method in your Post Object:
public function pets(){
return $this->hasMany(Pet::class);
}
hope help
You need to first define a relationship between your Client model and Pet model
So, in App\Client, you would have the following relationship:
public function pets()
{
return $this->hasMany(Pet::class);
}
and in App\Pet, you would have the following relationship:
public function client()
{
return $this->belongsTo(Client::class)
}
You should then be able to do this in your Controller:
public function show($id)
{
$post = Client::with('pets')->find($id);
return view('pet.shw')
->withPost($post);
}
and accesses your relationship like this in the pet.shw view:
foreach($post->pets as $pet) {}
For more information read about Eloquent Relationships

Laravel 5: Eloquent relationship method with conditions

Normal relationship methods don't usually have a condition, and tend to look like this:
class StripeCustomer extends Model
{
public function user()
{
return $this->belongsTo(User::class, 'stripe_customer_id');
}
}
In my model I have a condition in the relationship method like so:
class StripeCustomer extends Model
{
public function user()
{
if ($this->type === 'normal') {
return $this->hasOne(User::class, 'stripe_customer_id');
} else {
return $this->hasOne(User::class, 'stripe_customer_charity_id');
}
}
}
Does Laravel support conditional relationships in Eloquent like above. A lot of the usual methods still work like so:
StripeCustomer::get()->first()->user;
StripeCustomer::get()->first()->user()->get();
But would the following work predictably:
Foo::with('user')->get();
The issue here is that I am unsure in how the "with" operator works in Eloquent internally.
A reason I believe it also doesn't work is that the user() method needs to be executed for every model. However, when I added a dump(...) at the start of the method, I found it was only run once, indicating that with() does not work.
No, it won't work with with(). What do you think will happen when you try to execute the following code:
Foo::with('user')->get();
The answer is Laravel will create new instance of Foo and try to call user() to get the relationship object. This new instance doesn't have any type ((new Foo)->type will be null), therefore your method user() will always return $this->hasOne(Bar::class, 'b_id') and this relationship object will be used to construct a query.
As you can see this is clearly not what you wanted since only type B users will be eager loaded for all Foo rows. What you need to do in this case is create two relationships (one for each type) and accessors (get/set) for user:
class Foo extends Model
{
public function userA()
{
return $this->hasOne(Bar::class, 'a_id');
}
public function userB()
{
return $this->hasOne(Bar::class, 'b_id');
}
public function getUserAttribute()
{
if ($this->type === 'a') {
return $this->userA;
} else {
return $this->userB;
}
}
public function setUserAttribute($user)
{
if ($this->type === 'a') {
$this->userA()->associate($user);
} else {
$this->userB()->associate($user);
}
}
}
Then you can use with() for both relations to utilize eager loading:
$fooRows = Foo::with('userA', 'userB')->get();
...
foreach ($fooRows as $row) {
$row->user;
}
edit:
Since you've edited code in your question the example code in my answer no longer represents your case, but I hope you get the overall idea.
Yep, with() works. It runs a subquery on any relation your user() method returns. Since your relation already has a constraint, it applies said constraint to the subquery as you'd expect.

How to add a condition to a eloquent relation

This is the case I have
$user = User_picture::where('is_main','=',1)->where('user_id','=',Auth::user()->id);
This means a user has many pictures but there is only one picture that has is_main value 1.
I tried this.
public function active_picture()
{
return $this->hasMany('App\User_picture')->find($this->is_main);
}
This doesn't return anything but an error that says it should be an object. Can anyone help me out with the case?
HasMany is meant to return a collection since it is a one to many relationship method.
If you only want to return one result, you should use HasOne with conditions. To define conditions, you use where() not find(). Find() is only used to match the model's primary key.
public function active_picture()
{
return $this->hasOne('App\User_picture')
->where('is_main', 1);
}
You could declare an accessor like:
In User model:
// declare relationship:
public function pictures()
{
return $this->hasMany(Picture::class);
}
// declare accessor:
public function getMainPictureAttribute()
{
return $this->pictures()->where('is_main', 1)->first();
}
So you can do this:
auth()->user()->main_picture ...

Eloquent construction returning too many results

I have the following code in my DesignsController:
public function show($id)
{
return Design::find($id)->with('variables')->get();
}
When I GET /designs/1 I should get back json of just the design with id=1, but I get back all the current designs.
In the design model:
/* Define relationship between designs and variables */
public function variables()
{
return $this->hasMany('Variable');
}
The routes.php:
Route::resource('designs', 'DesignsController');
What am I doing wrong here?
Edit: a bit more information. I get all the results back as long as I hit an id of an actual design, so it seems to be finding the result according to the id, but then returning all results.
If I remove ->with('variables')->get(); then this works, but I need the variables too. Here's the model for Design:
class Design extends Eloquent {
/* Define relationship between designs and variables */
public function variables()
{
return $this->hasMany('Variable');
}
/* Define relationship between designs and variables */
public function user()
{
return $this->belongsTo('User');
}
}
Variable model:
class Variable extends Eloquent {
public $timestamps = false;
}
You're doing your "with" statement incorrectly:
Eager load:
public function show($id)
{
return Design::with('variables')->find($id);
}
Actually I think you're problem was calling get() after find() since find already returns a model. Find should be called at the end of a query you build because it essentially calls get() inside of it.
Lazy-Eager alternative:
public function show($id)
{
return Design::find($id)->load('variables');
}

Resources