Laravel Eloquent get model by scope on associated model - php

I have an Eloquent model Foo which has a field bar_id. I define the relationship between them in the Foo model:
public function Bar()
{
$this->belongsTo('App\Bar');
}
The Bar model has a baz_id, and a scope to get all Bars which have a particular baz_id. This is the scope in my Bar model:
public function scopeFromBaz($query, $bazId)
{
return $query->where('baz_id', $bazId)
}
I now want to make a call of all Foos where their associated Bar has a baz_id of 1. How do I do this? I've tried:
Foo::where('bar', function($query) {
$query->fromBaz(1);
});
But that produces the error
Call to undefined method Illuminate\Database\Query\Builder::fromBaz()

You have to use whereHas when you're adding conditions to relationships.
The query should be
Foo::whereHas('Bar', function ($query) {
$query->fromBaz(1);
})->get();
See : https://laravel.com/docs/5.6/eloquent-relationships#querying-relationship-existence
I also spotted something on your other piece of code, you haven't added a return on your Bar function in the Foo model.

Related

use relations in scopes in laravel

I Want To Use Relation Methods In Scopes But It Gives an Error.
Error:
Call to undefined method Illuminate\Database\Eloquent\Builder::members()
Controller:
$members = $book->MembersLoanedCurrentBook()->paginate(8);
Scope:
public function scopeMembersLoanedCurrentBook(Builder $query): Builder
{
return $query->members()->orderBy('return_date')->where('book_member.returned',false);
}
Assuming your models are something akin to User hasMany BookMember and Book has an attribute called returned, you can use Laravel's with` query scope:
Users::with(['member_books', function ($q) => {
$q->returned
})->get();
#geertjanknapen was right that this is a possible duplicate. You can achieve the same result using the methods from this question.
What you are doing is defining a scope and in that scope querying a relationship for a specific property or value.
public function scopeMembersLoanedCurrentBook(Builder $query): Builder
{
return $query->members()
->orderBy('return_date')
->whereHas(['book', function ($q) => {
$q->returned == false;
});
});
}
Without knowing the model structure and relationships, it's hard to write out an exact solution, but something along these lines should work.
You can't work with relations in scope, because your work with Builder $query.
public function scopeMembersLoanedCurrentBook(Builder $query): Builder
{
return $query->orderBy('return_date')
->where('returned',false);
}
And
$members = $book->members()->MembersLoanedCurrentBook()->paginate(8);

Laravel hasMany relationship, limit with query

Scenario:
On a hasMany relationship, using the with function (for eager loading), I want to limit the results from each of the rows within (instead of the total).
I am following this tutorial (as I think it's the only way to achieve what I need from what I've read on SO) - https://softonsofa.com/tweaking-eloquent-relations-how-to-get-n-related-models-per-parent/
I have a Model - Room that hasMany Reviews.
Room Model:
class Room extends Scopes\BaseModel
public function reviews()
{
return $this->hasMany('App\Review');
}
public function latestReviews()
{
return $this->reviews()->latest()->nPerGroup('room_id', 1);
}
Scopes/BaseModel.php:
Direct copy from the website's tutorial, but with the namespace of namespace App\Scopes;
Controller using the with function:
class Room extends Scopes\BaseModel
$rooms = Room::where('room_types_id', $specialism_id)
->with('latestReviews')
->get();
Error:
RelationNotFoundException in RelationNotFoundException.php line 20:
Call to undefined relationship [latestReviews] on model [App\Room].
There are 2 ways of doing this. the first one is more like:
Room::with(['reviews' => function ($q) {
$q->where('*CONDITION IF NEEDED*')->orderBy('created_at', 'desc')->take(1);
}])->where('room_types_id', $specialism_id)->get();
The second way is using accessors.
public function getLatestReviewAttribute(){
return Review::where('room_id', $this->attributes['id'])->orderBy('created_at','desc')->take(1)->get();
}
and you can access it on your view by using $room->latest_review->review_column_name;
While on your controller you can just do Room::all();;

Laravel 5.3 Polymorphic many to many - how to get all the related rows regardless of their table sorted by pivot column?

I have a model Page and many models called SomethingSection - they're connected through a polymorphic m-m realtionship and the pivot has an additional column 'position'.
I need to write a relationship (or accessor maybe?) on the Page model that will return a collection of all connected Sections, regardless of their model (read: table).
My models:
class Page extends Model {
public function introSections()
{
return $this->morphedByMany(IntroSection::class, 'pagable');
}
public function anotherSections()
{
return $this->morphedByMany(AnotherSection::class, 'pagable');
}
}
class IntroSection extends Model {
public function pages()
{
return $this->morphToMany(Page::class, 'pagable');
}
}
class AnotherSection extends Model {
public function pages()
{
return $this->morphToMany(Page::class, 'pagable');
}
}
The pivot column looks like this:
pagables
-page_id
-pagable_id
-pagable_type
-position
I'm looking for a way to call a method/attribute on the Page model and get all the connected sections in a single collection, sorted too. What would be a good way to go about this?
I understand that the connected sections do not have the same interface, but in my case that's not a problem at all (in terms of what I will do with the data).
I also understand that relationships perform a separate query (for each relationship), so getting all of them with 1 query is impossible (also different interfaces would be a problem here). And for the same reason the sorting will need to be done on the collection level, not in query.
How could I make this as maintainable as possible and preferably with as small a performance hit as possible.
Thanks in advance.
You can use withPivot() method after your relationship to get the pivot columns with relation like this:
class Page extends Model {
public function introSections()
{
return $this->morphedByMany(\HIT\Models\Sections\IntroSection::class, 'pagable')
->withPivot(['position']);
}
public function anotherSections()
{
return $this->morphedByMany(AnotherSection::class, 'pagable');
}
}
class IntroSection extends Model {
public function pages()
{
return $this->morphToMany(Page::class, 'pagable')
->withPivot(['position']);
}
}
and you can use collection's sortBy to sort the collection by using sortBy() method like this:
$sorted_collection = IntroSection::pages->sortBy('pagables.position');
UPDATE:
You can use collection's combine() method to get all the relationships like this, add this method inside your Page Class:
public function getAllSections()
{
return $this->introSections->combine($this->anotherSections-toArray())
->sortBy('pagables.position'):
}
Hope this helps!

laravel pulling data from database

I'm still trying to wrap my head around whereHas() method. My case is this. I want to pull all users that belong to class
This is relations
Classes model
public function users() {
return $this->belongsToMany('App\User')->withTimestamps();
}
User model
public function classes() {
return $this->belongsToMany('App\Classes')->withTimestamps();
}
controller
$class_us = User::whereHas('classes', function ($query) {
$query->where('class',1);
})->get();
When I do dd($class_us) I get an empty collection.
How can I resolve this problem?
Thanks.
in whereHas clouse the builder instance is coming from the related model so,
$class_us = User::whereHas('classes', function ($query) {
// here the builder belongs to Class model not User model
$query->where('id',1); // id because classes table has column as id
})->get();

Laravel Eloquent ORM relationship naming conventions

When defining an inverse relation in Eloquent, do you have to name your dynamic property the same as your related model?
class Book extends Eloquent {
public function author()
{
return $this->belongsTo('Author');
}
}
$books = Book::all()
foreach ($books as $book) {
echo $book->author->firstname;
}
In the above example, do I have to call this method author or can I name it something else? I tried to name it to something else (just out of curiosity) but it then returns null hence the errors "Trying to get property of non-object".
EDIT: I got it to work by passing the foreign key to belongsTo, like this:
class Book extends Eloquent {
public function daauthor()
{
return $this->belongsTo('Author', 'author_id');
}
}
$book = Book::find(55);
dd($book->daauthor);
Can someone explain why?
The method belongsTo tries to determine the attribute which links to the Author model. To accomplish this Laravel uses the function name of the caller.
So in your code Laravel sees the daauthor function and tries to use the attribute daauthor_id in the books table to fully your request. As your books table does not have this attribute it fails.
By setting the $foreignKey on the method you can override the default behaviour:
public function daauthor()
{
return $this->belongsTo('Author', 'author_id');
}
For more details check out the source code of \Illuminate\Database\Eloquent\Model.

Categories