What is the real query behind Laravel's Model::belongsTo() method? - php

For example, let's take a look at this model in a laravel project:
<?php
//...
class Post extends Model
{
//...
public function user()
{
return $this->belongsTo(User::class); // User model is previously defined
}
}
Now I can get the user (author) info of a specific post:
Post::find(1)->user()->username;
Of course laravel has to run some DB queries to get the data; and I wanna know what is the query behind this belongsTo() method.

You cant use the toSql() method to check query will run on laravel illuminate,
$sql = Person::query()->with('user')->find(1)->toSql();
dd($sql);

try use this
$result=Post::with('user')->find(1);
dd($result);
and look the response
belongs to is meaning to relation between tables
if the relation is one to one use belongsTo and hasOne
if the relation is one to many use belongsTo and hasMany

Related

Laravel 8.x realationship - Use HasMany in HasOne

I'm trying to use a HasMany relation in a HasOne.
I have following Models:
class Auction extends Model
{
//...
public function bids(): HasMany
{
return $this->hasMany(Bid::class, 'auction_id');
}
public function approvedBids(): HasMany
{
return $this->bids()->approved();
}
public function topBids(): HasMany
{
return $this->approvedBids()->orderByDesc('price')->take(10);
}
public function topBid(): HasOne
{
//return $this->topBids()->firstOfMany(); // Not Working
//return $this->hasOne(Bid:class, 'auction_id)->ofMany('price','max')->approved(); // not working
//return $this->hasOne(Bid:class, 'auction_id)->approved()->ofMany('price','max'); // not working
//return $this->hasOne(Bid::class, 'auction_id')->ofMany('price', 'max'); // working but not as I expecting
}
}
class Bid extends Model
{
//...
public function scopeApproved(Builder $query): Builder
{
return $query->where('state', BidState::STATE_APPROVED);
}
//...
}
As you can see in the source, I'm looking for a way to make a relation that retrieve the Top Bid (ONE BID) from topBids() relation, but I don't know how, and none of my approaches works:
$this->topBids()->firstOfMany(); // Not Working
$this->hasOne(Bid:class, 'auction_id')->ofMany('price','max')->approved(); // not working
$this->hasOne(Bid:class, 'auction_id')->approved()->ofMany('price','max'); // not working
Unfortunately these shouldn't be a relationships
Real question is why are you trying to make these relationships?
Usually you should be using relationships on model to describe how they are correlating together within the database, the rest of the things you should be defining as a scope on a query or a model, or as an attribute.
So, what I'm trying to say is this:
Keep bids as a relationship, as that is actually a relationship to the Bid model
Update approvedBids to be a scope (or an attribute)
Update topBids to be a scope (or an attribute)
Then, you will be able to find top bid easily by doing something like this:
$this->topBids->first() -> if it is an attribute
$this->topBids()->first() -> if it is a scope
This is how you can create a scope: https://laravel.com/docs/9.x/eloquent#local-scopes
In the end, you can even create an attribute that will allow you to retrieve topBid like this:
public function getTopBidAttribute(){
$this->bids()->approved()->orderByDesc('offered_token_price')->first();
}
Then later you can just do $this->topBid.
I think I've found the solution
public function topBid(): HasOne
{
return $this->hasOne(Bid::class, 'auction_id')
->approved()
->orderByDesc('price');
}
You see the problem was in ofMany() function, which creates a huge SQL and I don't know why!
I've returned a HasOne object here, which supports all kinds of query manipulations. Basically HasOne class, tells the main query, to:
Retrieve the first record of the query I've provided.
So if we use orderBy it only provides an order for HasOne's query. and the main query will take cares of the rest and selects the first record.

how to change sql query to laravel model query without DB

i am going to change sql query to laravel model query without DB.
DB means illuminate\Fascade\DB;
->join(array(\DB::expr('(select SUM(p.amount) as paid, p.case_id
from payments p
where p.status_id = 3
group by p.case_id)'),'p'), 'LEFT')->on('p.case_id','=','c.id')
this is sql query of fuelphp.
i want to change it with laravel model style.
how can i convert?
It is quite difficult guess your whole sql query but in general you need two models. You need create relations between them and then you can use Eloquent.
For example have two models (read tables) - users and payments:
class User extends Model
{
public function payments(): HasMany
{
return $this->hasMany(Payment::class);
}
}
class Payment extends Model
{
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
Then you can create Eloquent query:
Payment::with('user')->where('status_id', 3)->sum('amount')

Call a Model method using Laravel Relationship

I'm currently trying to use Laravel Relationships to access my achievements Model using User model, I use this relationship code:
public function achievements()
{
return $this->hasMany('App\Models\User\Achievement');
}
I can easily make some eloquent queries, however I can't access any method that I created there, I can't access this method:
class Achievement extends Model
{
public function achievementsAvailableToClaim(): int
{
// Not an eloquent query
}
}
Using the following code:
Auth::user()->achievements()->achievementsAvailableToClaim();
I believe that I am using this Laravel function in the wrong way, because of that I tried something else without using relationship:
public function achievements()
{
return new \App\Models\User\Achievement;
}
But that would have a performance problem, because would I be creating a new class instance every time I use the achievements function inside user model?
What would be the right way of what I'm trying to do?
it's not working because your eloquent relationship is a hasMany so it return a collection. you can not call the related model function from a collection.
you can var dump it on tinker to understand more what i mean.
You can use laravel scopes.Like local scopes allow you to define common sets of constraints that you may easily re-use throughout your application.
In your case you use this like, Define scope in model:
public function scopeAchievementsAvailableToClaim()
{
return $query->where('achivement_avilable', true);
}
And you can use this like :
Auth::user()->achievements()->achievementsAvailableToClaim();

Laravel - belongsTo relationship in custom pivot model not working

Edit:
I dont think its the same issue as:
https://stackoverflow.com/questions/40022929/laravel-relationship-error-undefined-property-illuminate-database-eloquent-col
because in that issue hasMany relationship is used which returns an array but i used belongsTo which should return a certain object.
I have a database structure where i have a many to many relationship between users and companies table. For that i have a crossreference table company_user. Also each user has a certain role in a company so the crossreference table also has a role_id. The problem is that for some reason i get an exception when i try retrieve the role from the crossreference table.
Here is how i defined the relationship in Company model:
public function users() {
return $this->belongsToMany('App\User')->withPivot('role_id')->using('App\CompanyUser');
}
Now if i just try to get role_id from pivot everything works fine:
#foreach($company->users as $user)
{{$user->pivot->role_id}} // this displays correct role_id
#endforeach
But i also need the data of the role so i defined a relationship in my custom pivot. Here is the whole model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Relations\Pivot;
class CompanyUser extends Pivot
{
public function role()
{
return $this->belongsTo('App\Role');
}
}
And i tried to access it like this:
#foreach($company->users as $user)
{{$user->pivot->role()->id}}
#endforeach
But this gives me an exception:
Undefined property: Illuminate\Database\Eloquent\Relations\BelongsTo::$id
What am I missing?
try changing to
#foreach($company->users as $user)
{{$user->pivot->role->id}}
#endforeach
The exception itself gives you your answer.
Undefined property: Illuminate\Database\Eloquent\Relations\BelongsTo::$id
It's telling you you're trying to access the $id property on the belongs to builder instance and not the actual related model.
The difference is small but is worth understanding as it will make your laravel life much happier.
Access the relation builder: $user->role()
This will directly call the role() method and return exactly what you see in your definition. Accessing this method is handy for aggregate functions - things like getting the count of related records for HasMany or BelongsToMany relations. eg: $user->role()->count().
Access the related record: $user->role
This will actually retrieve the related record from the database and hydrate it for you, thus giving you the power of your related eloquent model and access to it's columns as properties. :D
You can see why/how this works by diving into Laravel's source and checking out the Illuminate\Database\Eloquent\Model class. Specifically the getAttribute() method.

Laravel: belongsTo() relation assume one-to-many relationship instead of one-to-one

I'm having a tedious problem with Laravel's ORM.
I have a model with multiple relationships, like this:
class Pages extends Eloquent {
protected $guarded = array('id','config_id');
public function template()
{
return $this->belongsTo('Templates', 'templates_id');
}
public function updateUser()
{
return $this->belongsTo('Users', 'updated_by');
}
Now I can access the template related item in a simple way, like this:
$this->template->name;
And it works out of the bat, because Laravel's ORM assumes it is a one-to-one relationship and internally calls the first() method.
But when I try the same with updateUser it fails, returning an error, saying that it can't call name on a non-object.
But if I try this:
$this->updateUser()->first()->name;
it works, but it doesn't feel right to me.
So my question is, how Laravel's ORM decide if a relationship defined with belongsTo() is one-to-one or one-to-many? Is there a way to force a needed behaviour?
Thanks
You need to define the relationship. You can define 'different' relationships on the perspective.
The ->belongsTo() function is an inverse function - but you havent defined anything on the users table - so it is wrongly assuming the inverse is one-to-many.
Just add this to your users class:
class Users extends Eloquent {
public function pages()
{
return $this->hasMany('Pages');
}
}

Categories