Eloquent findOrFail()->get() - php

Bit of a rookie error I know, but can I ask...
Why does
findOrFail()->get();
or
findOrFail()->first();
Return the whole collection, opposed to just failing? The correct syntax I know is just:
findOrFail();
However an accidental ->get() on the end has caused me a nightmare!

The findOrFail($id) method returns a single model by finding through id column and throws an exception - ModelNotFoundException, if model is not found. The get() method return the collection of models/rows.
If you need to find and expect only one model in return by using id, use findOrFail() method only. You dont have to use get() in the end. You can catch the exception and show the respective message in response. Also, you don't have to use first() method in this case, because findOrFail() method will return only one model result.
If you expect a collection of models, use get() method in the end. If there is no result, you'll get an empty collection or array and no exception will be thrown in this case, as the result will be an empty collection/array.

Both findOrFail and firstOrFail throws an exception if the model is not found. This is the default behaviour : https://laravel.com/docs/5.6/eloquent#retrieving-single-models

Related

Is it possible to use eager loading with the querybuilder in Laravel?

As one can see above, there is a 'eagerLoad' section and the querybuilder accepts calling $query->with('relation') but produces the following error: Method addEagerConstraints does not exist.
I've tried to find some documentation on this matter but didn't find much. Is it possible at all to use eager loading in this case? If so, could anyone tell how?
Update
As some people pointed out it is possible and the error is caused by another error in my code. Here are some samples:
// Querybuilder
$query->select([
'persons.id as alumni_id',
...
]);
$query->where('...'); // Based on search parameters
$query->groupBy('alumni_id');
$query->with('relation');
$result = collect($query->get());
// Model
public function relation()
{
// Note: relation does have a column person_id
return $this->hasMany(Relation::class, 'person_id', 'alumni_id')->get();
}
Produces
BadMethodCallException in Macroable.php line 81: Method addEagerConstraints does not exist.
As far as I've learned, It is not possible to use eager loading to load Model relations with the querybuilder. (as the querybuilder is not leveraging Eloquent, it doesn't know about the relations)
If anyone knows this to be (partially) incorrect, please let me know.
This should work
// Querybuilder
$query->select([
'persons.id as alumni_id',
...
]);
$query->where('...'); // Based on search parameters
$query->groupBy('alumni_id');
$query->with('relation');
$result = $query->get(); // already returns a collection
// Model
public function relation()
{
// Note: relation does have a column person_id
// A relation should return an Eloquent object (HasMany in this case), not a collection
return $this->hasMany(Relation::class, 'person_id', 'alumni_id');
}

Eloquent: find() and where() usage laravel

I am trying to get a record from a posts database table using its id. I've been banging my head on the find() method for quite sometime now, confused as to why it wasn't working. Here is my query that looks correct to me but didn't work:
$post = Post::find($id);
$post->delete();
Reluctantly i did this:
$post = Post::where('id', $id);
$post->delete();
and surprisingly enough, it worked but I have no idea how.
I also know that unlike find(), where() is a query builder and so I could also use it like this:
Post::where('id', $id)->first()
Any ideas about the difference in the way the methods work?
Your code looks fine, but there are a couple of things to be aware of:
Post::find($id); acts upon the primary key, if you have set your primary key in your model to something other than id by doing:
protected $primaryKey = 'slug';
then find will search by that key instead.
Laravel also expects the id to be an integer, if you are using something other than an integer (such as a string) you need to set the incrementing property on your model to false:
public $incrementing = false;
Not Found Exceptions
Sometimes you may wish to throw an exception if a model is not found. This is particularly useful in routes or controllers. The findOrFail and firstOrFail methods will retrieve the first result of the query. However, if no result is found, a Illuminate\Database\Eloquent\ModelNotFoundException will be thrown:
$model = App\Flight::findOrFail(1);
$model = App\Flight::where('legs', '>', 100)->firstOrFail();
If the exception is not caught, a 404 HTTP response is automatically sent back to the user. It is not necessary to write explicit checks to return 404 responses when using these methods:
Route::get('/api/flights/{id}', function ($id) {
return App\Flight::findOrFail($id);
});
To add to craig_h's comment above (I currently don't have enough rep to add this as a comment to his answer, sorry), if your primary key is not an integer, you'll also want to tell your model what data type it is, by setting keyType at the top of the model definition.
public $keyType = 'string'
Eloquent understands any of the types defined in the castAttribute() function, which as of Laravel 5.4 are: int, float, string, bool, object, array, collection, date and timestamp.
This will ensure that your primary key is correctly cast into the equivalent PHP data type.

Check If Model Exists

When searching for what should be a very basic and common test in Laravel, there seems to be much confusion on how to properly check weather or not a model exists and then do something with the model if it does. When searching through stackoverflow, laracasts, and the laravel documentation itself, it does not become anymore clear. If I for example run this query,
$restaurant = Restaurant::find($input["restaurant_id"]);
There are various stack overflow posts that would have me check the count(), use the exists() method which does not seem consistent, or use firstOrFail() which throws an exception. All I want to do is run a call like the one above, check if $restaurant is a valid model, and then do something if it is. There is no need for an exception in my case and I don't want to have to have to run the query again after using something like count() or exists(). The documentation has no useful information on this either which allows 4 different variable types to be returned without any mention of which case will trigger which return. Does anyone have a good handle on this topic?
Laravel checking if record exists
Eloquent ->first() if ->exists()
https://laravel.com/api/5.2/Illuminate/Database/Eloquent/Builder.html#method_find
You don't need to run any additional queries. If the record does not exist, find() will return null. You can just use a simple if to check:
if($restaurant = Restaurant::find($input["restaurant_id"]) {
// Do stuff to $restaurant here
}
You can also use
$restaurant = Restaurant::findOrFail($input["restaurant_id"]);
Sometimes you may wish to throw an exception if a model is not found. This is particularly useful in routes or controllers. The findOrFail and firstOrFail methods will retrieve the first result of the query. However, if no result is found, a Illuminate\Database\Eloquent\ModelNotFoundException will be thrown:
From: https://laravel.com/docs/5.1/eloquent
Its very clear in laravel docs about your question, find(),first(),get(), all return null if the model not exist,
$model = Restaurant::find(111); // or
$model = Restaurant::where('id',111)->first();
if(!$model){ //if model not exist, it means the model variable is null
}

BadMethodCallException with message 'Call to undefined method Illuminate\Database\Query\Builder::toArray()'

I am working along with #Jeffrey_way series of Laracasts
Many to Many Relations (With Tags)
Below is the code I have written in CMD using Laravel Tinker:
After executing the last line of code ($article->tags()->toArray();
Although everything seems to be OK with my code but still I get following error:
BadMethodCallException with message 'Call to undefined method Illuminate\Database\Query\Builder::toArray()'
If you want to actually "get" relational data, you don't put parenthesis arount tags. This will work just fine:
$article->tags->toArray();
You put parenthesis when you need to "query" to that collection (Ex. sync, save, attach).
Reference: https://laravel.com/docs/5.1/eloquent-relationships#many-to-many
I had the same problem and solved it by adding get()
For example:
$article->tags()->get()->toArray();
Try this instead:
$article->tags()->all()->toArray();
Underlying the tags() is probably a Query\Builder object which represents a query that has not yet run. Instead you need a Collection object which is a query that has run, on which to call toArray(). ->all() is one such call that converts a query builder into a collection by actually running the query.

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'];
}

Categories