Laravel, Scope Methods on Model - php

im writing a Laravel Application, that calculates monthly income.
Income table has a column that saves this monthly value. In some months the value will be 0 but when I access to the method using:
$service->income->getMonthTotal(11);
I get the following error (HasOne could not be converted to string)
This is the scope method i'm using, where $result is returning 0, and laravel is giving me the error.
Any ideas on how to return 0 for echoing value?
public function scopeGetMonthTotal($query, $month)
{
$result = $query->where('month',$month)->sum('value');
return $result;
}

Accessing relationships as properties will automatically run get(), resulting in your error. You need to access your relationship as a method instead:
$service->income()->getMonthTotal(11);
To see the result, make sure to run get() yourself:
echo $service->income()->getMonthTotal(11)->get();

Related

Why query result suddenly changed after called from other medthod in Laravel

I have problem here with query result from Eloquent, I tried to query from DB and put in variable $contractList in my mount() method and the result as expected. But when I tried to retrieve specific data from $contractList with $contractList->find($id), the result not same as in mount() method.
Here is query from mount():
public function mount(){
$contractList = Kontrak::select(['id', 'mou_id'])->with(['order:id,kontrak_id', 'order.worklist:id', 'order.worklist.khs:id,mou_id,worklist_id,khs', 'amdNilai:id,kontrak_id,tgl_surat'])->withCount('amdNilai')->get()
}
Here the result:
But when I tried to find specific data from $contractList, properties that shown not same as in mount:
public function itemSelected($id)
{
//amd_nilai_count not showing
$kontrak = $this->contractList->find($id);
if ($kontrak->amd_nilai_count == 1) {
$this->nilai_amd = $this->calculateNilai($id);
}
}
Here is the result called from itemSelected():
I have tried use get() but the result still problem, how to get same properties same as in mount().By the way im use laravel & livewire.
As i read your comments you seem to mix up ActiveRecords ORM with an Data Mapper ORM. Laravel uses active records.
Laravel will always fetch models on every data operation.
Kontrak::select('name')->find(1); // select name from kontraks where id = 1;
Kontrak::find(1); // select * from kontraks where id = 1;
This will always execute two SQL calls no matter what and the objects on the heap will not be the same. If you worked with Doctrine or similar, this would be different.
To combat this you would often put your logic in services or similar.
class KontrakService
{
public function find(int $id) {
return Kontrak::select(['id', 'mou_id'])->find($id);
}
}
Whenever you want the same logic, use that service.
resolve(KontrakService::class)->find(1);
However, many relationship operations is hard to do with this and then it is fine to just fetch the model with all the attributes.

Difference between laravel value() function and direct model access

I'm doing an Eloquent query in my Profession model, i ran an where query, retrieve the first value, then caught only the value in "id" column with ->value('id')
//This always return 1
$profession = Profession::where('name', $profession)->first()->value('id');
//This return the right value
$profession = Profession::where('name', $profession)->first()->id;
Does value() function depends on some other aspect of the query/configuration? Couldn't find anything in Laravel query builder documentation.
Profession::where('name', $profession)->first() returns a model. You cannot call value on a model. However, you may call it on a query builder as explained in the docs?
So instead of calling
$profession_id = Profession::where('name', $profession)->first()->id;
you could achive the same thing with
$profession_id = Profession::where('name', $profession)->value('id');
There is a helper function in laravel with the name value . It will always returns that value it is given for example value(true) is true
But for eloquent if you want to access the fields either you use pluck function like ->pluck(id) it will give you ids only

Laravel: Get column value from first() result of relationship query

I'm trying to get a single column value from the first result of a Model's belongsToMany relationship query, as i'm returning the ->first() result of the relationship I was hoping $code->reward->title would work but it doesn't.
I get an Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation error
What I'm trying to do is the get the title of the current reward that is linked to a specific code - the code_reward pivot table has a valid_from and expires_at date as the reward linked to a code will change as time goes by, hence the need to get the currently active reward for that code.
Here's my code:
Model: Code
public function rewards()
{
return $this->belongsToMany('App\Reward')->withPivot('valid_from', 'expires_at')->withTimestamps();
}
public function reward()
{
$now = Carbon::now();
return $this->rewards()
->wherePivot('valid_from', '<', $now)
->wherePivot('expires_at', '>', $now)
->first();
}
Controller: CodeController
public function index()
{
$codes = Code::all();
return view('codes/index')->with('codes', $codes);
}
View: Codes/index
#foreach ($codes as $code)
{{$code->id}}
{{$code->reward->title}}
#endforeach
Any help is really appreciated!
Update
Unfortunately both suggestions below ($code->reward()->title and getRewardAttribute() return an Trying to get property of non-object error.
If I remove ->first() from the Code->reward() method and replace $code->reward->title with $code->reward->first() in the view it echoes out the whole reward model as json, however $code->reward->first()->title still returns the Trying to get property of non-object error
Update 2
If I do {{dd($code->reward->title)}} in the view I get the reward title but if I just do {{$code->reward->title}}, I don't!
AND the $code->reward->title works as expected in a #Show view, so could it be that the collection of codes supplied by the controller's #index method isn't passing the necessary data or not passing it in a necessary format??
SOLVED
The issue was caused by one of the $code->rewards in the foreach loop in the index view returning null! The first one didn't, hence the dd() working but as soon as the loop hit a null it crashed.
Once I wiped and refreshed the db (and made sure my seeds where adding only valid data!) it worked. Doing {{$code->reward ? $code->reward->title : ''}} fixed the issue. Grrr.
Your statement is failing because $code->reward->title tells Laravel that you have defined a relationship on your Code model in a method called reward(). However, your relationship is actually defined in the method rewards(). Instead, reward() is a custom method on the model that you have made up. Calling it as a method and not a relation is the quickest way to get what you want.
{{$code->reward()->title}}
As #andrewtweber points out below, you could also make your custom reward() method into an attribute accessor. To do that, just rename the function to getRewardAttribute() and then you can call it in your view like you originally did.
Alternatively, you could get rid of that reward() method entirely and move all of that logic to the controller, where it probably makes more sense. You'd have to use constrained eager loading to pull that off. So in your controller you'd have something like this:
$codes = App\Code::with(['rewards' => function ($query) {
$query->wherePivot('valid_from', '<', $now)
->wherePivot('expires_at', '>', $now);
])->get();
Of course, this would return all of your filtered codes. This is because you cannot apply a sql limit inside a nested eager relationship as outlined here. So in your view, you would then have to do something like this:
{{$code->rewards->first()->title}}
However, it will be simpler to go with my first solution, so that's entirely up to you.
Try to set this method in Code Model, because query builder treats valid_from and expired_at as string, not date?
public function getDates()
{
return ['valid_from','expired_at'];
}

Laravel Collections counting result

On a User model (table with 4 records), when I do:
$coll = User::all();
echo $coll->count();
I get the amount of records found (4).
But when I do:
$coll = User::find(2);
echo $coll->count();
I do not get 1 (As I expect) but the amount of attributes in the resulting collection (23 in this case).
How can I check if more then one records are found?
UPDATE:
OK, thanks to you all I now see the difference in result between collection and model.
But my real problem is that I have to detect if I am having a model or a collection as a result. Depending on this result I perform some changes on the contents of the fields in the items (with map()) or model. How can I detect if the result is a model or a collection?
if(count($coll) > 1)
Works, but is this the right approach?
Here's what's going on with the code you have there:
1. When calling User::all() you'll get a Illuminate\Database\Eloquent\Collection on which you can call count which counts the elements in the collection like so:
public function count()
{
return count($this->items);
}
This will return the number of items in the collection as you correctly expected.
2. When calling User::find(2) however, the Eloquent Query Builder will not return a Collection, because it will check to see how many results there are, and since you passed one ID you'll get at most one result, so it will return an Eloquent Model instead. The Model does not have a count() method, so when you try to call $coll->count(); it will go to the magic __call method that the class has implemented which looks like this:
public function __call($method, $parameters)
{
if (in_array($method, array('increment', 'decrement')))
{
return call_user_func_array(array($this, $method), $parameters);
}
$query = $this->newQuery();
return call_user_func_array(array($query, $method), $parameters);
}
As you can see the method tries to see if it should call a couple of hardcoded methods (increment and decrement), which of course don't match in this case because $method = 'count', so it continues to create a new Query on which it will call the count method.
The bottom line is that both the first and second code samples end up doing the same thing: counting all the entries in the users table.
And since, as I pointed our above, one ID cannot match more than one row (since IDs are unique), the answer to your question is that there's no need or way to count the results of find(2), since it can only be 0 (if null is returned) or 1 (if a Model is returned).
UPDATE
First of all, for future reference you can use the PHP get_class to determine the class name of an object or get_parent_class to determine the class it is extending. In your case the second function get_parent_class might be useful for determining the model class since the User class extends a Laravel abstract Model class.
So if you have a model get_class($coll) will report User, but get_parent_class($coll) will report \Illuminate\Database\Eloquent\Model.
Now to check if the result is a Collection or a Model you can use instanceof:
instanceof is used to determine whether a PHP variable is an instantiated object of a certain class
Your checks should look something like this:
// Check if it's a Collection
if ($coll instanceof \Illuminate\Support\Collection)
// Check if it's a Model
if ($coll instanceof \Illuminate\Database\Eloquent\Model)
You might also want to check if the result is null, since find will return null if no entry is found with the given ID:
if (is_null($coll))
It seems you are expecting the find()-method to behave differently. From the docs
Find a model by its primary key.
If you problem is by checking if its from collection. Why don't you check it if its from Illuminate\Database\Eloquent\Collection.
if (get_class($coll) == 'Illuminate\Database\Eloquent\Collection') {
your code...
}
or
if ($coll instanceof \Illuminate\Database\Eloquent\Collection) {
your code...
}

Find model by a string

I'm having a hard time finding the problem in this repository method:
public function findByString($string)
{
return User::where('string', $string)->get();
}
It generates the correct SQL statement which I can run in Sequel Pro returning the right result.
However, when this method is called in Laravel (4.2), the result is always empty:
$this->user->findByString('something');
On the other side
$this->user->findOrFail(1);
Always returns the correct model.
Any ideas what could be wrong?
return User::where('string', $string)->get();
Will return a collection that you can iterate through.
If you want the first instance back as an Eloquent model you can do this:
return User::where('string', $string)->first();

Categories