Using Laravel's ORM - what's the difference between the longhand and the shorthand? - laravel

For instance:
return Product::first()->BaseProduct->Products
works, whereas
return Product::first()->BaseProduct()->Products()
does not, and I get a BadMethodCallException.
I understand that there's a notable difference, then, between those two lines, but what is difference, and how does it work?

I'm assuming that BaseProduct() and Products() are both relationships in your models? Calling Products() won't return eloquent objects, it'll return a hasMany or belongsToMany (children of Relation) object.
Calling Products instead of Products() triggers a magic get method. This magic get calls the getResults() method on the Relation object. This way you actually get back a collection of the Product models. This is typically the way that you should work with relationships.
In other words:
BaseProduct::first()->Products == BaseProduct::first()->Products()->getResults()
I suggest that you have a look at the source code

Related

How to access one relation inside another relation in laravel?

I have a query in which I have eagar loaded two models using with function like this:
ModelA::with(['relationB', 'relationC.relationC.A'])->where(condition)->get();
So, ModelA has two relations like this:
public function B(){ return $this->blongsTo(B::class);}
public function C(){ return $this->blongsTo(C::class);}
Now, my requirement is that I want to add a condition in B() function like this:
public function C() {
if($this->B->status) {
return $this->blongsTo(C::class)->withTrashed();
}
return $this->blongsTo(C::class);
}
But it return null on line this statement:
if($this->B->status)
Here is the error message
Trying to get property 'status' of non-object
My ultimate requirement is that using one relation function I want to fetch deleted records and non deleted based on the condition, but somehow it is not working.
My laravel application version is 7.30.4.
A relational function (such as your public function C()) works a bit of magic under the hood. This is because really it is designed to be called in a query way like you show already with the ::with(['relationB', ...]).
However, because of this, if you were to eager load C, then $this is not yet loaded as the full model, and therefore B is not defined (this is assuming that modelA always has a B relation). If you were to dd($this) while performing your query, you'd see that the result would be a model without any attributes.
Getting this to work from within a relational function (with the goal of eager loading) is very difficult. You're probably better off doing the logic elsewhere, with a second query for example. This is because within the relational function, there is no way to know who or what the potential target is. However, if you only use it after modelA is loaded, then it works without issues.
You can do some things with a whereHas, but then you'd still have to do 2 queries, or you can try and see if you can get it done with an SQL IF statement, but that will not result in a relation.

Can I use with() instead Load() Laravel eloquent

$school=$school->teachers->load('TeacherCourses');
This will return all teachers with their courses in my laravel project..
can I use with() function like below?? if I cant then how ?
$school=$school->teachers->with('TeacherCourses');
Yes, but the correct syntax would be different. load() is used after you've retrieved the model from the Database, but with() is used prior to that.
$school = School::first();
$school->teachers->load('teacherCourses');
̶T̶h̶i̶s̶ ̶w̶o̶n̶'̶t̶ ̶w̶o̶r̶k̶,̶ ̶a̶s̶ ̶̶t̶e̶a̶c̶h̶e̶r̶s̶̶ ̶s̶u̶g̶g̶e̶s̶t̶s̶ ̶a̶ ̶C̶o̶l̶l̶e̶c̶t̶i̶o̶n̶,̶ ̶w̶h̶i̶c̶h̶ ̶d̶o̶e̶s̶n̶'̶t̶ ̶h̶a̶v̶e̶ ̶a̶ ̶̶l̶o̶a̶d̶(̶)̶̶ ̶m̶e̶t̶h̶o̶d̶.̶ Corrected by #patricus; Collections do have a load() method, so ->teachers->load('teacherCourses') will work. Alternative syntax would be:
$school->load('teachers.teacherCourses');
The more efficient way to write this is:
$school = School::with('teachers.teacherCourses')->first();
This will eager-load both the teachers and teacherCourses nested relationships, in a minimal number of database calls.
Edit
Route Model Binding will do this behind-the-scenes:
$school = School::findOrFail($schoolId);
Since it's only loading a single Model, using load() is perfectly acceptable. with() loads the relationships via a single query, for a total of 1 + (# of relationships loaded) queries (3 in your case, school, teachers and teacherCourses). load() will do the same thing, so there's no need to use with() over load() in this case.

In Laravel Eloquent, how to check for multiple relationship existence in one "has"?

So, in order to check the existence of a relationship on a model, we use the has function on the relationship like model1->has('relationship1').
While it is possible to supply the model1->with() function with an array of relations to eager load them all, both has and whereHas functions do not accept arrays as parameters. How to check for the existence of multiple relationships?
Right now, I am running multiple has functions on the same model (The relations are not nested):
model1->has('relationship1')
->has('relationship2')
->has('relationship3')
But that is tedious and error-prone. Solution anyone?
There unfortunately isn't a way to pass an array of relationships to has() or whereHas(), but you can use a QueryScope instead. On your Model, define the following:
public function scopeCheckRelationships($query){
return $query->has("relationship1")->has("relationship2")->has("relationship3");
}
Then, when querying your Model in a Controller, simply run:
$result = Model::checkRelationships()->get();
The function name to use a Scope is the name of the function, minus the word scope, so scopeCheckRelationships() is used as checkRelationships().
Also, it's actually possible to pass the relationships you want to query as a param:
public function scopeCheckRelationships($query, $relationships = []){
foreach($relationships AS $relationship){
$query->has($relationship);
// Might need to be `$query = $query->has(...);`, but I don't think so.
}
return $query;
}
...
$result = Model::checkRelationships(["relationship1", "relationship2", "relationship3"])->get();
In case you need this to be dynamic.
Here's the documentation for Query Scopes if you need more info: https://laravel.com/docs/5.8/eloquent#query-scopes

Laravel whereDoesntHave() - multiple OR conditions

In Laravel 4.2 I have a model called Product with many-to-many relationshis to other models like Country or Category. I want to filter out products that are "incomplete", which means they have no connected countries or no connected categories. I can use whereDoesntHave() method to filter out one relation. When I use it two times in one query it creates AND condition, but I need OR. I can't find orWhereDoesntHave() method in API documentation. I can't pass multiple relations as arguments because it expects first argument to be a string.
I need something like this:
$products = Product::whereDoesntHave('categories')->orWhereDoesntHave('countries')->get();
Is there any way to achive whereDoesntHave() with multiple OR conditions?
You can use doesntHave and specify the boolean operator:
$products = Product::doesntHave('categories')->doesntHave('countries', 'or')->get();
Actually you only need whereDoesntHave if you want to pass in a closure to filter the related models before checking if any of them exist. In case you want to do that you can pass the closure as third argument:
$products = Product::doesntHave('categories', 'or', function($q){
$q->where('active', false);
})->doesntHave('countries', 'or')->get();
Since Laravel 5.5 there is an orWhereDoesntHave function.
You may use it like this
Product::whereDoesntHave('categories', function($q){ //... })
->orWhereDoesntHave('countries', function($q){//...})
->get();
From you example it seems that you are not using a where clause, so you may just use
Product::doesntHave('categories')
->orDoesntHave('countries')
->get();
Use
Product::whereDoesntHave('categories')->doesntHave('countries', 'or')->get();
Laravel Source Code:
whereDoesntHave https://github.com/illuminate/database/blob/master/Eloquent/Builder.php#L654
calls
https://github.com/illuminate/database/blob/master/Eloquent/Builder.php#L628
internally.
Let’s say we have Authors and Books, with 1-n relationship – one Author can have one or many Books. Here’s how it looks in app\Author.php:
public function books()
{
return $this->hasMany(\App\Book::class, 'author_id');
}
Now, what if we want to show only those Authors that have at least one book? Simple, there’s method has():
$authors = Author::has('books')->get();
Similarly, there’s an opposite method – what if we want to query only the authors without any books? Use doesnthave():
$authors = Author::doesnthave('books')->get();
It’s not only convenient, but also super-easy to read and understand, even if you’re not a Laravel developer, right?

Eloquent ORM: Obtain a object, not an array of objects

I have this statement in Laravel 4:
$prepayment = Prepayment::where('hash', '=', $custom)->take(1)->get();
But this return an array of objects, and I want only one object, because there are only one result with the hash that I search.
How is the way to obtain only one object, not an array of objects?
Thanks!
Use the first() method instead of the get() method
$prepayment = Prepayment::where('hash', '=', $custom)->first();
This is a good article to learn more about how the eloquent orm works. http://laravel.io/topic/14/how-eloquent-relationship-methods-work It describes how the belongs to relationship will just give you one object whereas the has many relationship will give you an arrray of objects

Resources