how to change sql query to laravel model query without DB - php

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')

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.

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

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

Laravel relationship with a link table - Confused

I'm playing with laravel for the first time and i'm a little confused on how to achieve a link table relationship. I have 3 tables: Tank, TankContent and Content
I have the following models:
Tank:
class Tank extends Model
{
public function getTankContent()
{
return $this->hasMany('App\TankContent', 'tankID', 'tankID');
}
}
TankContent:
class TankContent extends Model
{
public function getTank()
{
return $this->belongsTo('App\Tank', 'tankID', 'tankID');
}
public function getContent()
{
return $this->belongsTo('App\Content', 'contentID', 'contentID');
}
}
Content:
class Content extends Model
{
public function getContentTanks()
{
return $this->hasMany('App\ContentTank', 'contentID', 'contentID');
}
}
Now im trying to call say tankID 2 and get all the content details inside of that
$content = Tank::find(2);
$items = $content->getTankContent;
This will list me the content. But then how do i go about linking the results to getContent() from the TankContent model?
Thanks in advance. Hopefully I just need it explaining and then it will all click.
p.s i have tried reading the https://laravel.com/docs/5.5/eloquent-relationships#many-to-many and im still stumped!
TankContent isn't required, the belongsToMany relationship methods belong on the Tank and Content models.
https://laravel.com/docs/5.5/eloquent-relationships#many-to-many
From here you can see there is no RoleUser model, the models are only Role and User. The belongsToMany relationship on those models defines that the intermediate, or pivot, table is role_user.
You can additionally define other fields on that relationship with withPivot() chained onto the belongsToMany method. (read "Retrieving Intermediate Table Columns"). Therefore, there is likely no reason to have a separate model for the intermediate/pivot table.

Trying to aggregate column values in eloquent

I have model called Organisation, which has a many to one relationship with a model called Client, it looks like this,
public function clients() {
return $this->hasMany('Client', 'owner_id')->orderBy('name', 'asc');
}
Client has a one to many relationship with projects that looks like this,
public function projects()
{
return $this->hasMany('Project');
}
The project model / table as a column called total_cost within an organisation I can gets it's clients, and then the clients projects, what I wanting to do is get the value of the client by aggregating all the clients projects total_cost entries, I am trying to do this with the following,
public function clientsValue() {
return $this->clients()->projects()->select(DB::raw("SUM(total_cost) as client_value"));
}
In my mind this getting the projects through the clients relationship and then running a select on the project model, however I am getting the following error,
Call to undefined method Illuminate\Database\Query\Builder::projects()
But I am not sure why as the clients has a projects relationship.
$this->clients() returns relation definition - object of class HasMany, not a Client object. HasMany class does not have a projects() method, that's why you're getting an error.
In order to calculate total clients value, you first need to define additional relation between organization and projects:
public function projects() {
return $this->hasManyThrough('Project', 'Client');
}
This will tell Eloquent how to fetch projects for given organization using intermediate Client model.
Once you have the relation you can call aggregate functions on it:
public function clientsValue() {
return $this->projects()->sum('total_cost');
}
You can read more about aggregate functions here: http://laravel.com/docs/5.1/queries#aggregates
You can read more about hasManyThrough relations here: http://laravel.com/docs/5.1/eloquent-relationships#has-many-through

Using Laravel Eloquents HasManyThrough relation with multiple relations through polymorphism

I got a rather simple application where a user can report other users comments and recipes. I use a polymorphic relation to store the reports. Which works fine; however, I am now trying to get the offences that a user has made.
Getting users reports is not a problem, this can be done directly using user->reports() but I would very much like to get the reports in which other people has reported said user. I can get this to work using either the hasManyThrough relation or queries BUT only on one model at a time.
ex.
public function offenses() {
return $this->hasManyThrough('Recipe', 'Reports');
}
Or
->with('user.recipe.reports')
The problem is that my reportable object is not just recipes, it could be comments, images, etc. So instead of having to use multiple functions, the logical way would be to parse the relationship between hasManyThrough various parameters somehow.
Theoretically looking like this:
public function offenses() {
return $this->hasManyThrough(['Recipe', 'RecipeComments'], 'Reports');
}
Is this in any way possible? With some undocumented syntax? If not is there any clever workarounds/hacks?
Possible solution?
Would an acceptable solution be to add another column on my report table and only add offender_id like this?
ID | User_id | Offender_id | Reportable_type | Reportable_id
That would mean I could just make a relation on my user model that connects offences through that column. But would this be considered redundant? Since I already have the offender through the reportable model?
Models
Polymorphic Model
class Report extends Model {
public function reportable() {
return $this->morphTo();
}
public function User() {
return $this->belongsTo('App\User');
}
}
Recipe Model
class Recipe extends Model {
public function user() {
return $this->belongsTo('App\User');
}
public function reports() {
return $this->morphMany('App\Report', 'reportable');
}
}
Comment Model
class RecipeComment extends Model {
public function user() {
return $this->belongsTo('App\User');
}
public function reports() {
return $this->morphMany('App\Report', 'reportable');
}
}
Using your current model, you can receive all the reported models for a user by the following code:
$recipeCommentReport = RecipeComment::whereHas('reports',function($q){
return $q->where('user_id','=',Auth::user()->id)
});
$recipeReport = Recipe::whereHas('reports',function($q){
return $q->where('user_id','=',Auth::user()->id)
});
//Get all reports into one
$reports = $recipeReport->merge([$recipeCommentReport]);
This is messy at best, because:
We can't sort the results, given that we are using two separate db queries.
If you have other models who have a report relationship, just imagine the chaos.
The best solution, is as you have figured out above:
Add an offender_id column to your report table.
It is cleaner and follows the DRY principle.
Typical Case Scenarios
Get all Recipe Comment reports for User
Report::where('offender_id','=',Auth::check()->id)
->where('reportable_type','=','RecipeComment')
->get();
Count offense by type for User
Report::where('offender_id','=',Auth::check()->id)
->grouBy('reportable_type')
->select(Db::raw('count(*)'),'reportable_type')
->get();

Categories