Calculated fields in Laravel using Query Builder - php

I've been working in a Laravel Project and I want to know how can I show a calculated field in a blade view? I want to retrieve some information from an invoice and a Total calculated field.
I would like to get this result, but using Eloquent ORM. The query is this:
SELECT
invoiceheader.id,
invoiceheader.customerlastname,
invoiceheader.customerfirstname,
invoiceheader.customernit,
invoicedetail.productid,
invoicedetail.quantity,
invoicedetail.saleprice,
(quantity * saleprice) as Total
FROM invoiceheader
INNER JOIN invoicedetail
ON invoiceheader.id = invoicedetail.invoiceid
Thank you so much in advance.

You can use laravels DB::raw(), which injects the string to the query, like so:
Laravel raw expressions
InvoiceHeader::select('invoiceheader.id', 'invoiceheader.customerlastname',
'invoiceheader.customerfirstname', 'invoiceheader.customernit',
'invoicedetail.productid', 'invoicedetail.quantity',
'invoicedetail.saleprice', DB::raw('(quantity * saleprice) as Total'))
->join('invoicedetail', 'invoiceheader.id', '=', 'invoicedetail.invoiceid')
->get();
Note: make sure to import use DB; on the top of the page

You can do this by utlizing Eloquent relations and accessors.
In your InvoiceHeader model:
/*
Relation with the invoicedetail table
*/
public function detail()
{
return $this->hasOne(InvoiceDetail::class, 'invoiceid', 'id');
}
In your InvoiceDetail model:
/*
The accessor field
*/
protected $appends = ['total_price'];
/*
Accessor for the total price
*/
public function getTotalPriceAttribute()
{
return $this->quantity * $this->saleprice;
}
To understand the created accessor name from the method name, here's a text from the laravel docs:
To define an accessor, create a getFooAttribute method on your model
where Foo is the "studly" cased name of the column you wish to access.
In this example, we'll define an accessor for the first_name
attribute. The accessor will automatically be called by Eloquent when
attempting to retrieve the value of the first_name attribute:
For your query you could do:
// get all invoices in descending order of creation
$invoices = InvoiceHeader::recent()->get();
// loop invoice data to get the desired fields
foreach ($invoices as $invoice) {
$customerfirstname = $invoice->customerfirstname;
$totalprice = $invoice->detail->total_price;
// more code
}
You can read more about Accessors & Mutators on the official documentation here. Read about eloquent relations here.

Try this
InvoiceHeader::join("invoicedetail",function($query){
$query->on("invoiceheader.id", "=", "invoicedetail.invoiceid")
})
->select("invoiceheader.id",
"invoiceheader.customerlastname",
"invoiceheader.customerfirstname",
"invoiceheader.customernit",
"invoicedetail.productid",
"invoicedetail.quantity",
"invoicedetail.saleprice",
\DB::raw("(invoicedetail.quantity * invoicedetail.saleprice) as Total"))
->get();

Related

Laravel hasManyThrough get the intermediate model on query results

I'm working on a Laravel 9 project. I have a model called PingtreeGroup that I need to get all associated Pingtree models through my PingtreeEntry model.
My query is working as expected and is joining the furthest model which is my Pingtree.
The problem I've encountered is that I also need to join the actual PingtreeEntry model as well to the furthest model, or somehow get the PingtreeEntry model with it.
This is my current PingtreeGroup model relationship:
/**
* Get the pingtrees for the model
*/
public function pingtrees()
{
return $this->hasManyThrough(
Pingtree::class,
PingtreeEntry::class,
'pingtree_group_id',
'id',
'id',
'pingtree_id'
);
}
Then my query:
$pingtreeGroups = PingtreeGroup::where('company_id', $company_id)
->with('pingtrees')
->withCount('pingtrees')
->paginate($request->input('perPage', 10));
How could I achieve this desired result?
I assume PingtreeEntry-Pingtree has one-on-one relationship.
// in Pingtree model add this relationship
public function pingtree_entry()
{
return $this->belongsTo(Pingtree::class);
}
And update your query to call this relationship
$pingtreeGroups = PingtreeGroup::where('company_id', $company_id)
->with('pingtrees', 'pingtrees.pingtree_entry')
->withCount('pingtrees')
->paginate($request->input('perPage', 10));
Now from a single PingtreeGroup, you can get multiple Pingtree.
And inside each of this Pingtree, you can fetch data from PingtreeEntry.
For example lets do get a single PingtreeGroup
$pingtreeGroup = PingtreeGroup::where('company_id', $company_id)
->with('pingtrees', 'pingtrees.pingtree_entry')
->first();
// get the first Pingtree
$pingTree = $pingtreeGroup->pingtrees()->first();
// and inside this Pingtree , you get fetch PingtreeEntry
$pingtreeEntry = $pingTree->pingtree_entry;
Now you can get any kind of data inside this PingtreeEntry.
I hope this can help you out.

How to use custom SELECT with JOINs and GROUP BY in Laravel model?

I want to use sophisticated SELECT query with JOINs and GROUP BY in Laravel model.
Сoncretely I want to make a messager in my application. Here is table "messages" with all messages. Now I want to create model called "Dialog". Keep in mind here is no table "dialogs", a dialog is a result of joining and grouping.
Example of query:
SELECT
cl.name AS client_name,
COUNT(m.id) AS messages_count,
MAX(m.created_at) AS last_message,
COUNT(m.id) > SUM(m.viewed_by_client) AS has_new_for_client,
COUNT(m.id) > SUM(m.viewed_by_user) AS has_new_for_user
FROM messages AS m
INNER JOIN clients AS c ON m.client_id = c.id
GROUP BY c.id
Of cource I can use raw SQL queries. But I want to use Eloquent relations later with all its benefits. For example:
$dialog->client->full_name
$dialog->client->order->ordered_items
I had an idea to create a VIEW in database from my query and to use this view as a fake table in the model. But it seems to me not ideal solution.
So, how can I use JOINs and GROUP BY in Eloquent when I do not have a real table for model entities? Or may be some different solutions for my task?
You can have a database table without an Eloquent model but not the other way around. That said, there's no rule against making more than 1 model per table. Not really standard practice though.
I experimented with making a model that would inherit from another model but the boot method didn't work as expected so I dropped it.
I think you could get all the information you take from that query with accessors in your Client model. Since your query has no where clause, a scope is not really necessary but it could also be done with that.
OPTION 1: Accessors
# App\Client
class Client extends Model
{
// Standard Eloquent relationship
public function messages()
{
return $this->hasMany(App\Message::class);
}
// Accessor $client->client_name
public function getClientNameAttribute()
{
return $this->name;
}
// Accessor $client->last_message
public function getLastMessageAttribute()
{
// Load relationship only if it hasn't been loaded yet
if(!$this->relationshipLoaded('messages'))
$this->load('messages');
// use max() method from collection to get the results
return $this->messages->max('created_at');
}
// Accessor $client->has_new_for_client
public function getHasNewForClientAttribute()
{
// Load relationship only if it hasn't been loaded yet
if(!$this->relationshipLoaded('messages'))
$this->load('messages');
return $this->messages->count() > $this->messages->sum('viewed_by_client');
}
// Accessor $client->has_new_for_user
public function getHasNewForUserAttribute()
{
// Load relationship only if it hasn't been loaded yet
if(!$this->relationshipLoaded('messages'))
$this->load('messages');
return $this->messages->count() > $this->messages->sum('viewed_by_user');
}
}
And then you can access all the properties dynamically
$dialog = Client::withCount('messages')->find($id);
$dialog->client_name;
$dialog->messages_count;
$dialog->has_new_for_client;
$dialog->has_new_for_user;
$dialog->last_message;
However if you're converting $dialog to an array or json format, accessors will be lost unless you append them. In the same way, you can hide the attributes you don't want to show.
This can be done globally for the model
protected $appends = ['client_name', 'has_new_for_client', 'has_new_for_user', 'last_message'];
protected $hidden = ['name'];
or locally for the query
$dialog->setHidden(['name']);
$dialog->setAppends(['client_name', 'has_new_for_client', 'has_new_for_user', 'last_message'];
OPTION 2: Query scopes
# App\Client
class Client extends Model
{
public function scopeDialog($query)
{
$query->select('name as client_name')
->withCount('messages') // the default name will be messages_count
->selectRaw('max(m.created_at) as last_message')
->selectRaw('count(m.id) > sum(m.viewed_by_client) as has_new_for_client')
->selectRaw('count(m.id) > sum(m.viewed_by_user) as has_new_for_user')
->join('messages as m', 'm.client_id', 'clients.id')
->groupBy('clients.id');
}
}
And then just call it like you would any scope Client::dialog()->...
OPTION 3: Just use whatever methods are already available instead of writing more logic
$dialog = Client::with('messages')->find($id);
// client_name
$dialog->name
// messages_count
$dialog->messages->count()
// last_message
$dialog->messages->max('created_at')
// has_new_for_client
($dialog->messages->count('id') > $dialog->messages->count('viewed_by_client'))
// has_new_for_user
($dialog->messages->count('id') > $dialog->messages->count('viewed_by_user'))
Create dialogs table and put 'dialog_id' column into the messages table. Each message has a dialog and a client. Create relationships in each model. So you can access attributes over models as you want. By doing this, this code works;
$dialog->client->full_name
$dialog->client->order->ordered_items
I am trying to detail example about how to get User Model's Accessor in another model with using relationship
Suppose, we have User table & Comment Table...
Now, Suppose I appends User's Profile Full URL in User model using "getProfilePhotoUrlAttribute" Method. when I call User model eloquent then it's appends User Profile Image automatically.
but Now I wants to get that user's profile Full URL in with Comments then we can't access Accessor using Join because with join we can join only out DataBase's Table Columns. If we have profile_photo_path column & doesn't have profile_photo_url named column as we define accessor function name then we can't access using jjoin. in this case we wants to use Relationship method
For example:-
Case :- 1 You wants to Get the user's comments with User details
In this case, User have one or more than one comments So we need to use One TO Many Relation
App/Models/User.php file
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = [
'profile_photo_url',
];
/**
* Get the URL to the user's profile photo.
*
* #return string
*/
public function getProfilePhotoUrlAttribute()
{
... here return full profile URL (concat profile_path with storage/public location path)...
}
/**
* Get the user's comments with User details.
*
* One To Many relation
*/
public function comments()
{
return $this->hasMany(Comment::class);
}
Now then, use Model eloquent Query like below
$user = User::with('comments')->where('id', '=', '2')->get();
echo '<pre>';
print_r($user->toarray());
Case :- 2 You wants to Get the user details of the all comments.
In this case, we need to use Many TO One Relation
App/Models/Comment.php file
/**
* Get the user details of the comments.
*
* One To Many (Inverse) / Belongs To
*/
public function user()
{
return $this->belongsTo(User::class);
}
then use Model eloquent Query like below
$comments = Comment::where('deal_id', '=', '45')->get();
print_r($comments->toarray());
foreach ($comments as $comment) {
print_r($comment->user->toarray());
echo $comment->user->profile_photo_url;
echo "<br/>";
}
NOTE:- I used Latest version - it is Laravel 8, So Syntax may vary as per your Laravel Version
For More Detail with Output Data check here my answer on another question
& you can check it in Laravel Official Documentation

laravel accessors in controller?

I've add accessors and mutators in my laravel model.
public function getAmountAttribute($amount)
{
return ($amount) / 100;
}
public function setAmountAttribute($amount)
{
$this->attributes['amount'] = $amount * 100;
}
These are working fine for me. At one place i am facing issue :
The issue is I want to apply aggregate function in my controller for amount field. But accessors is not working.
Model::with(['xxx'])->where('parent_id', $id)->sum('amount')
Value of $id is fine i checked. It's giving me sum of amount field where parent_id is equals to $id. It should give results after diving by 100.
Thanks in advance.
Accessors and mutators allow you to format Eloquent attributes when
retrieving them from a model or setting their value.
... not while querying the table.
Model::with(['xxx'])
->addSelect(DB::raw('SUM(`table`.amount/100) as sum_of_amounts'))
->where('parent_id', $id)
->get();
An accessor/mutator is called only when you access the property from the result but it doesn't work within the query so you can't do it but as an alternative, you may use Laravel's Collection::sum() method for example:
$collection = Model::with(['xxx'])->where('parent_id', $id)->get();
$someOfAmount = $collection->sum('amount');
Make sure that, you've created a protected $appends property in your Model in use where it has the following:
protected $appends = ['amount'];
Note: I'm not sure whether this $appends is required but you may give this a try.

Get column list with Eloquent relation in laravel

I want to get column list with Eloquent relation in laravel.
When I use this comman
$columns = Schema::getColumnListing('news');
Result is all fields of news table but I want to get relation fields for CategoryNews table.
News model:
public function NewsCategories()
{
return $this->belongsTo('App\CategoryNews');
}
CategoryNews model:
public function News()
{
return $this->hasMany('App\News');
}
You should be able to do something like this:
$columns = Schema::getColumnListing($news->NewsCategories()->getRelated()->getTable()));
Using getRelated() method you are getting related object for relationship (in your case it's App\CategoryNews) and now using method getTable() you can get table name for this model and you can use this table name for getColumnListing() method.

Getting count from pivot table in laravel eloquent

I have a many to many relationship for orders and products.
<?php
class Order extends Eloquent {
public function user()
{
return $this->belongsTo('User');
}
public function products()
{
return $this->belongsToMany('Product');
}
}
?>
<?php
class Product extends Eloquent {
public function orders()
{
return $this->belongsToMany('Order');
}
}
?>
Need to fetch the number of times each product is ordered.In mysql,this task can be achieved by using the following query
SELECT products.id, products.description, count( products.id )
FROM products
INNER JOIN order_product ON products.id = order_product.product_id
INNER JOIN orders ON orders.id = order_product.order_id
GROUP BY product_id
LIMIT 0 , 30
Result of the above query is as follows:-
id description count(products.id)
1 Shoes 3
2 Bag 2
3 Sun glasses 2
4 Shirt 2
How this task can be achieved using laravel eloquent (without using query builder)????How can i fetch the number of times each product is ordered using laravel eloquent??
For future viewers, as of Laravel 5.2, there is native functionality for counting relationships without loading them, without involving your resource model or accessors -
In the context of the example in the approved answer, you would place in your controller:
$products = Product::withCount('orders')->get();
Now, when you iterate through $products on your view, there is a orders_count (or, generically, just a {resource}_count) column on each retrieved product record, which you can simply display as you would any other column value:
#foreach($products as $product)
{{ $product->orders_count }}
#endforeach
This method produces 2 fewer database queries than the approved method for the same result, and the only model involvement is ensuring your relationships are set up correctly. If you're using L5.2+ at this point, I would use this solution instead.
Mind that Eloquent uses Query\Builder under the hood, so there is no such thing in Laravel, like 'query eloquent without using query builder'.
And this is what you need:
// additional helper relation for the count
public function ordersCount()
{
return $this->belongsToMany('Order')
->selectRaw('count(orders.id) as aggregate')
->groupBy('pivot_product_id');
}
// accessor for easier fetching the count
public function getOrdersCountAttribute()
{
if ( ! array_key_exists('ordersCount', $this->relations)) $this->load('ordersCount');
$related = $this->getRelation('ordersCount')->first();
return ($related) ? $related->aggregate : 0;
}
This will let you take advantage of eager loading:
$products = Product::with('ordersCount')->get();
// then for each product you can call it like this
$products->first()->ordersCount; // thanks to the accessor
Read more about Eloquent accessors & mutators,
and about dynamic properties, of which behaviour the above accessor mimics.
Of course you could use simple joins to get exactly the same query like in you example.
If you already have the $products object, you can do the following:
$rolecount = $products->roles()->count();
Or if you are using eager loading:
$rolecount = $products->roles->count();
Cheers.
I am using Laravel 5.1 and i am able to accomplish that by doing this
$photo->posts->count()
And the posts method in Photo model looks like this
public function posts(){
return $this->belongsToMany('App\Models\Posts\Post', 'post_photos');
}

Categories