laravel accessors in controller? - php

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.

Related

Unable to get selective column names on Laravel Eloquent Nested Eager Relationship

I have a Product Model with a following relationship:
public function recommendedPricing()
{
return $this->hasMany(RecommendedPricing::class);
}
The Recommended Pricing Model is:
protected $fillable = [ 'sku_id', 'unit_type_id', 'base_price', 'min_billable_qty', 'max_billable_qty', 'discount_method'];
protected $with = ['bands'];
public function bands()
{
return $this->hasMany('App\Models\RecommendedPricingBand');
}
The Recommended Pricing Band Model is like:
protected $fillable = ['sku_id','recommended_pricing_id','start','end','percent_change','fixed_price'];
Now In my Controller I am doing this:
Product::where('id', $product->id)->with(['recommendedPricing', 'recommendedPricing.bands'])->get();
which gives the entire result. But I want selected columns from both Recommended Pricing and Recommended Pricing Band. So, I tried this:
Product::where('id', $product->id)->with(['recommendedPricing:id, base_price, discount_method', 'recommendedPricing.bands:id, percent_change, fixed_price'])->get();
But this always results in an error.
My question: Is there any better approach or way to fetch the correct result for this kind of a nested relationship scenario?
When eager loading particular fields you can't have spaces. I usually do a seperate with line for each relationship. I don't think you need the array either. And you always have to get the id first, which you have already :) hope this works...
->with('recommendedPricing:id,base_price,discount_method')
->with('recommendedPricing.bands:id,percent_change,fixed_price')

Eloquent groupBy() is not working correctly on table relations

The groupBy method isn't working correctly. I am not sure what is the problem.
UPDATED
I made made a hasMany() <-> belongsTo() relations between tables.
Place model
protected $table = "places";
protected $guarded = [];
public $with = ["plans"];
public function plans()
{
return $this->hasMany("App\Models\Plan");
}
Plan Model
protected $guarded = [];
public function place()
{
return $this->belongsTo("App\Models\Place");
}
In controller when I return json data I can see the relation.
$place = Place::with(["plans"])->get();
return $place;
The result is fine with this. check the image: https://imgur.com/a/0CHXPhQ
But, when I try to use groupBy() on place_name column. It doesn't group the their plans in one place...
Place::with(["plans"])->groupBy("place_name")->get();
and the result: https://imgur.com/a/qPxMU42
As you see second place's plan doesn't group with first place... it's gone unknown... Expected result plan object should grouped too because their place_name grouped...
Any idea what's causing this problem? How can I fix it?
Try this
$data = Place::groupBy(['name','address'])->paginate(20);
If you are facing a problem that sometimes two rows are grouping due to group by, then you can do one thing. You can use group by concat both fields.
$data = Place::groupBy(\DB::raw('CONCAT(name, ' ', address)'))->paginate();
This is the logic you can put and get desire result.

Eloquent Relationship in Loop

I'm building a Laravel frontend to an existing database (an ERP system named Epicor) with a view to extending that functionality in a separate (new) database. At the moment I am trying to display pieces of "equipment" that have a status of being shipped to a customer, and include information from the Part table. The DB relationships are all there and I can get all the information I need using SSMS - so I believe I must be going wrong in my use of Eloquent. I have the following models:
Equipment - this is a serial number in the system, so in effect an instance of a part:
<?php
class Equipment extends Model
{
protected $table = 'ERP.SerialNo';
public $timestamps = false;
protected $primaryKey = 'SerialNumber';
protected $keyType = 'string';
protected $fillable = [
'SerialNumber',
'SNStatus',
'PartNum',
'TerritoryID',
'JobNum',
'PackNum',
'PackLine',
'RMANum',
'CustNum',
'SNStatus'
];
public function Part()
{
return $this->belongsTo(Part::class,'PartNum','PartNum');
}
public function Customer()
{
return $this->belongsTo(Customer::class,'CustNum', 'CustNum');
}
}
Part
class Part extends Model
{
protected $table = 'ERP.Part';
public $timestamps = false;
protected $primaryKey = 'PartNum';
protected $keyType = 'string';
protected $fillable = [
'PartNum',
'SearchWord',
'Innactive',
'PartDescription',
'ClassID',
'CommodityCode',
'NetWeight'
];
public function ShipmentLine()
{
return $this->hasMany(Shipment::class, 'PartNum', 'PartNum');
}
public function Equipment()
{
return $this->hasMany(Equipment::class,'PartNum', 'PartNum');
}
}
Customer Controller
public function show($CustID)
{
$Customer = Customer::find($CustID);
$Shipments = $Customer->Shipment->where('Voided', '0');
$Equipments = $Customer->Equipment->where('SNStatus', 'SHIPPED');
return view('Customer.show', compact('Equipments', 'Customer','Shipments', 'Parts'));
}
show.blade.php (under Customer)
<?php
#foreach($Equipments as $Equipment)
<tr>
<td>ClassID</td>
<td>{{$Equipment->PartNum}}</td>
<td>{{$Equipment->SerialNumber}}</td>
<td>PartDescription is sometimes really really really long.....even longer than this!</td>
</tr>
#endforeach
Which all works fine and I get a list of all of the Equipment that has a status of being shipped to that customer. What I'd like to do now is, in the list of equipment, including fields from the Part table that relate (ClassID and PartDescription).
I've tried a few things, but feel I'm clutching at straws and all of my attempts fail. I have managed to display on Equipment show.blade.php Part information, so I believe the models are set up OK.
Thanks in advance,
Richard
First of all, the relations methods inside the Part model (as well as inside the Customer model) must be written at plural, since you are matching multiple entities:
public function ShipmentLines()
{
return $this->hasMany(Shipment::class, 'PartNum', 'PartNum');
}
public function Equipments()
{
return $this->hasMany(Equipment::class,'PartNum', 'PartNum');
}
Second, you can use the relation to load the equipments in the controller, instead of using lazy loading:
public function show($CustID)
{
$Customer = Customer::find($CustID);
$Shipments = $Customer->ShipmentLines()
->where('Voided', '0')
->get();
$Equipments = $Customer->Equipments()
->with('Part') // load the Part too in a single query
->where('SNStatus', 'SHIPPED')
->get();
return view('Customer.show', compact('Equipments', 'Customer', 'Shipments'));
}
Finally, in the blade template, you can use the Part of the equipment very easy:
#foreach ($Equipments as $Equipment)
<tr>
<td>{{$Equipment->Part->ClassID}}</td>
<td>{{$Equipment->PartNum}}</td>
<td>{{$Equipment->SerialNumber}}</td>
<td>PartDescription is sometimes really really really long.....even longer than this!</td>
</tr>
#endforeach
Also, I would recommend using #forelse instead of #foreach to cover those situations when no equipments exists:
#forelse ($Equipments as $Equipment)
<tr>
<td>{{$Equipment->Part->ClassID}}</td>
<td>{{$Equipment->PartNum}}</td>
<td>{{$Equipment->SerialNumber}}</td>
<td>PartDescription is sometimes really really really long.....even longer than this!</td>
</tr>
#empty
<tr>
<td colspan="4">There is no existing equipment!</td>
</tr>
#endforelse
I think what you're looking for is with().
Before I get to that though, you actually have a bigger problem there than it seems. Matei Mihai actually touched on this.
When you have something like $Customer->Equipment, you're actually making use of Eloquent's "dynamic properties". What this means is, there's a magic __get() in there somewhere that says if the desired property doesn't exist on the target model, check to see if it has a relation method by that name. And if so, lazy-load it if it hasn't already been eager-loaded via with() or load().
So when you do $Customer->Equipment, it's basically a shortcut for $Customer->Equipment()->get().
Next thing to consider is that the result of get() is an Eloquent\Collection, which is a child-class to Support\Collections. And Support\Collections have their own version of the where() method.
All that to say, $Customer->Equipment->where('SNStatus', 'SHIPPED') does not result in running a query that looks like:
SELECT * FROM Equipment WHERE customerID = ? AND SNStatus = 'SHIPPED'
What you're doing is running this instead:
SELECT * FROM Equipment WHERE customerID = ?
And then asking the Collection class to filter the resulting set by SNStatus='SHIPPED' afterwards. This can be a huge performance hit and even max out your servers RAM depending on how big those tables are. I think what you're really looking for there is this:
$Customer->Equipment()->where('SNStatus', 'SHIPPED')->get()
By calling on the actual Equipment() method rather than the dynamic property, you're telling Eloquent that you're not quite ready for it to execute the query yet, because you're still appending conditions to it.
(Also just as a side-note, your naming-convention hurts my OCD a little bit, methods should always be "camelCased". Only class names have their first letter capitalized.)
So... back to the question you actually asked, and including an understanding of the difference between Model::where() and Collection::where(), what we have is something like this:
$resutls = $Customer->Equipment()->with(['Part'])->where('SNStatus', 'SHIPPED')->get();
Since you wanted to specify a couple fields within the Parts table that you actually care about, you can use a constrained eager-load
$resutls = $Customer->Equipment()->with(['Part' => function (Illuminate\Database\Eloquent\Builder $query) {
$query->select([
'PartNum', //Per Equipment::Part(), This needs to be there for the relation to be mated with its parent
'ClassID',
'PartDescription'
]);
// Since PHP always handles objects by-reference, you don't actually need to return $query after having altered it here.
}])->where('SNStatus', 'SHIPPED')->get();
This will give you a nested Part object with just the fields you care about on each Equipment model element within the Eloquent\Collection results.
As for how to handle these results within your blade file, I'll differ to Matei Mihai on that, I think that answer is pretty good.

Calculated fields in Laravel using Query Builder

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();

How filter on calculated field (append) in Eloquent Laravel?

I have calculated property 'price' in model Room.
protected $appends = ['price'];
public function getPriceAttribute()
{
if ($this->action) return $this->action_price_per_meter*$this->area;
return $this->price_per_meter*$this->area;
}
When i filter Rooms by this virtual field ...
$rooms = Room::where('price','<',100000)->get();
of course i got error - Column 'price' not found
How to solve this problem in Laravel way?
Eloquent has a whereRaw method which allows you to construct that segment of the query manually.
In your case you need the equivalent of the model's method in SQL:
->whereRaw('price_per_meter * area < 100000')
It'll be more complicated depending on what your action column is doing but that should serve as a starting point.

Categories