Laravel: Sum Collection by Custom Method - php

I have a model that has a custom method (assume it has a field called 'fieldname')
e.g.
class X extends \Eloquent
{
function custommethodtest()
{
return rand(1);
}
}
I want to use the collection and SUM by the custom method ie:
X:all()->sum('custommethodtest');
but it appears that laravel will only sum by an actual field, not a custom method. For instance, this would work:
X::all()->sum('fieldname');
Any ideas on how to make this work?

Instead of just a method, create an attribute accessor:
public function getCustommethodtestAttribute(){
return rand();
}
And then:
X:all()->sum('custommethodtest');

You may use custom attribute to achieve same result;
class X extends \Eloquent
{
protected $appends = [
'custom_test'
];
public function getCustomTestAttribute()
{
//NB: rand() expects exactly 2 parameters, am using 1234 for parameter 2 just for testing sake
return $this->attributes['custom_test'] = rand(1,1234);
}
}
Then:
X:all()->sum('custom_test');
The method above better than using only:
public function getCustomTestAttribute(){
return rand(1);
}
For this reason:
Once the attribute has been added to the appends list, it will be
included in both the model's array and JSON forms. Attributes in the
appends array respect the visible and hidden configuration on the
model.

Related

Laravel 9 Mutating Attribute not returning mutated attribute

Environment
Laravel 9
php 8.0
I have this mutator function to transform a value from 4 decimal places to 2 decimal places. I want to test out but the Attribute::make function not returning value, below is code for the model
class SubjectWithFee extends Model
{
protected $table = 'sjfee';
protected $primaryKey = 'IndexID';
protected $fillable = [
'Amount',
'CurrencyID',
'ProgramID',
];
public $timestamps = false;
protected function Amount(): Attribute
{
return Attribute::make(
get: fn ($value) => sprintf('%0.2f', $value),
);
}
Although when I did the test it access the attribute correctly when putting dd('test') before the return but on the get function cannot be access
Anyone knows the problem?
Update
The column "Amount" starts with capital letter
Initially the source code was from laravel version 5 and someone upgraded to 9.
Update 2
All my setters are working, but only getters are not.
Try this different approach, instead of the current function you have:
public function getAmountAttribute($value)
{
return sprintf('%0.2f', $value);
}
Add the attribute name to the appends property of your model.
Appending Values To JSON
Occasionally, when converting models to arrays or JSON, you may wish
to add attributes that do not have a corresponding column in your
database. To do so, first define an
accessor for the
value:
After creating the accessor, add the attribute name to the appends
property of your model. Note that attribute names are typically
referenced using their "snake case" serialized representation, even
though the accessor's PHP method is defined using "camel case":
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = ['amount'];
}

How to get only one note from relation belongsToMany?

I just have a table that has relation belongsToMany, BUT it was a mistake by developer so I can not change this structure SO I need to get only first(). However, when I take only first it return empty array but I need in object
$animals = Cat::query()->with(['types' => function($query) {
$query->first(); //wrong
}])
So how I can get only first? Because I need to order by this field and I can't because it is array
you can do this in two ways:
1- using hasOne relation:
class Cat {
public function firstType() {
return $this->hasOne(Type::class, 'type_id', 'id')->latest();
}
}
2- using staudenmeir/eloquent-eager-limit
after installing it you can write:
class Cat extends Model
{
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
public function firstType() {
return $this->hasMany(Type::class, 'type_id', 'id')->latest()->limit(1);
}
}
class Type extends Model
{
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
// ......
}
the advantage of HasEagerLimit trait is that you can limit the result not only to one but any number you want ...
now you can write:
$animals = Cat::query()->with('firstType');
You can add a attribute getter and set up into appends attribute. follow bellow example:
class Cat {
protected $appends = ['type'];
public function getTypeAttribute() {
// return the first element from your array of the belongsToMany relationship if it exists
return isset($this->types[0])? $this->types[0] : null;
}
}
That's important to remember this method will bring just one type. If you want to get the same type everytime, you create a diferente table where the cat table has the type_id column.
obs: Sorry for my english, it's still in working progress.

Custom method chaining with Eloquent ORM?

This is my current query:
$cars = Cars::with('brand')->get();
$cars->map(function($cars){
$cars->fullName = $cars->brand->brand." ".$cars->name;
//other manipulation...
return $cars;
});
I want to manipulate my collection in the model so that I can run something like $cars = Cars::with('brand')->getWithBrand();
How can I do this, so I don't have to write map functions for every time I run the query?
In your particular example, you don't need to use map to modify the Collection at all. You can use an Eloquent accessor to define attributes on a Model that don't exist in the database. In your example, you would define the following method on your Cars model:
public function getFullNameAttribute($value)
{
// make sure brand exists first
if ($this->brand) {
return $this->brand->brand.' '.$this->name;
}
// default if brand doesn't exist
return $this->name;
}
By defining that function on your Model, that function will be called whenever you attempt to use the full_name attribute, as shown in the following code:
$car = Cars::with('brand')->first();
// this will echo the result of the getFullNameAttribute method
echo $car->full_name;
Edit
If you would also like this new attribute to automatically show up in your toArray() or toJson() output, you can add the attribute to the $appends property on your Cars model:
class Cars extends Model
{
protected $appends = ['full_name'];
public function getFullNameAttribute($value)
{
// make sure brand exists first
if ($this->brand) {
return $this->brand->brand.' '.$this->name;
}
// default if brand doesn't exist
return $this->name;
}
}
Be aware, however, that your custom attribute depends on a related object. So, if you do something that accidentally calls toArray(), toJson(), __toString(), etc on a Collection of Cars that has not eager loaded the brand relationship, this will cause the N+1 query issue.
For example:
// Bad: N+1 issue because each printed Car will execute a
// separate query to get its brand to output full_name.
echo Cars::get();
// Good: No N+1 issue because all brands are already loaded.
echo Cars::with('brand')->get();

Custom model method to get a relation in Laravel

I try to define a custom Model method in Laravel. I have a n:m relation between Subscription and Notification over SubscriptionNotification.
I already defined the default relations:
public function subscription_notifications() {
return $this->hasMany('App\SubscriptionNotification');
}
public function notifications() {
return $this->belongsToMany('App\Notification', 'subscription_notifications');
}
Now I want to define a method, which returns a collection of notifications. I collect the IDs of the notifications I want in an array and write the following method:
public function notifications_due() {
// Collect $notification_ids
return $this->belongsToMany('App\Notification', 'subscription_notifications')->whereIn('notifications.id', $notification_ids)->get();
}
But when I want to use the mothod by $subscription->notifications_due, I get the following error:
[LogicException]
Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
I'm new to Laravel (I come from Rails). I don't know if this is in Laravel even possible. Maybe someone can help me. Thanks!
Remove the ->get() part in the method notifications_due. get() will return a Collection, but when calling the method as a property (or magic method), Laravel expects the method to return an instance of Relation. Laravel will then execute the query and transform it to a Collection automatically.
Also, you can use your already defined notifications() method:
public function notifications_due() {
// Collect $notification_ids
return $this->notifications()->whereIn('id', $notification_ids);
}
Remove the get call from your relationship method, for example:
public function notifications_due() {
return $this->belongsToMany(
'App\Notification',
'subscription_notifications
')->whereIn('notifications.id', $notification_ids);
}
Use it just same:
// It'll return a collection
$dues = $subscription->notifications_due;
To get all the ids from the collection you may try this:
$ids = $dues->pluck('id');
Also, you may add more constraints if you want if you use it like:the
$dues = $subscription->notifications_due()->where('some', 'thing')->get();
Or paginate:
$dues = $subscription->notifications_due()->where('some', 'thing')->paginate(10);

Laravel 4 Present Model

In Laravel 4, I have a model linking to a database table. Let's call it Model.
Say that this model has a database column called Property A and Property B.
When I make get request call to my model, i.e. Model::all() or Model::find($id), I don't want to return Property A or Property B, but some kind of function of the two, which appears to the front-end as some kind of read-only field, i.e. Property C.
Do I need to use a presenter library here, or is there a way by overriding model functions within Laravel 4?
The key for me here is that the property shows up when I call Model::all()
EDIT:
From my understanding this should return an attribute with the name foo constantly with the value "foo":
Model
class DiscountLink extends Eloquent {
protected $table = 'discountLinks';
protected $hidden = array('tag');
protected $fillable = array('name', 'currency', 'language', 'price', 'instalments', 'expires', 'active', 'foo');
public function getFooAttribute()
{
return "foo";
}
}
Controller
class DiscountLinkController extends \BaseController {
public function index()
{
return DiscountLink::all();
}
}
Use an accessor in your Model. To concatenate A and B, for instance:
public function getPropertyCAttribute()
{
return $this->attributes['property_a'] . ' ' . $this->attributes['property_b'];
}
And then you can access Model::find($id)->propertyC.
If you want the attribute to be automatically included in your model's results array (e.g. if you're sending the results of Model::all() or Model::get() as JSON, for example), add an $appends declaration to the top of your model:
protected $appends = array('PropertyC');
If the function is something that can be done in the database (like concatenation, sum, etc.), you could also add a DB::raw command to your query, like:
Model::select(*, DB::raw('CONCAT(PropertyA, " ", PropertyA) AS PropertyC'))->...

Categories