Dealing with data not found in Laravel - laravel

Laravel has a built-in method called findOrfail() described here:
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();
But what if you want to write your own code which deals with no result found. So in my controller I want something like:
public function show($id)
{
$data = JobCardHead::where('JobCardNum', $id)->first();
If no data found then
some code
else
some code

What does Eloquent return in case no model is found for the methods that do not throw an exception? I'm guessing it's either null or false. So, you could check the return value of the first() call:
$data = JobCardHead::where('JobCardNum', $id)->first();
if ($data) {
// found it
} else {
// not found
}
Or, knowing that firstOrFail() throws an exception, you could wrap the call in a try/catch block:
use Illuminate\Database\Eloquent\ModelNotFoundException;
//..
try {
$data = JobCardHead::where('JobCardNum', $id)->first();
} catch (ModelNotFoundException $e) {
// Data not found. Here, you should make sure that the absence of $data won't break anything
}
// Here $data is an instance of the model you wanted
The exact approach you should pick actually depends on your usecase a lot.
UPD. By the way, if $id here is your primary key here, you should simply use find($id) or findOrFail($id), as described here

Laravel returns null on first() calls to your model when no records are returned.
https://github.com/laravel/framework/blob/5.2/src/Illuminate/Database/Query/Builder.php#L1548
So for example you could do this as an even cleaner solution:
public function show($id)
{
if ($job = JobCardHead::where('JobCardNum', $id)->first()) {
// Record was found!
return view('job.show', compact('job'));
}
return view('404');
}

Related

How can I make the find-method work in this Laravel-code?

I'm a noob in this but I get different results from the similair code below
In this case $id=12
public function edit($id)
{
$post = Post::find($id)->with(['comments'])->first();
return $post;
}
This returns the post with id=1
public function edit($id)
{
$post = Post::where('id', $id)->with(['autor'])->first();
return $post;
}
This returns the post with id=12
How can I make find method work?
You need to know that find($id) is equivalent to where('id', $id)->first(). Both trigger the request call like for example the methods get(), count(), ...
So, once you call one of them, you get a result.
In your case, while on the model instance (find returned a model instance) , you called ->with(['comments']) which did the same as Post::query()->with(['comments']).
With a new query, first() did exactly what it is supposed to do, get the first entry ID = 1.
Basicly, you did run 2 query, and got the second query result.
For your first example, to make it work, call find() after ->with(['comments']) without the first.
public function edit($id)
{
$post = Post::with(['comments'])->find($id);
return $post;
}
This will either and instance of Post or null

Laravel 5.5 find($id) returns collection instead of single object

Laravel 5.5 find($id) returns collection instead of single object
Any idea why, and how to prevent this? I'm having to use ->first() as workaround
public function destroy(client $client)
{
$item = Client::findOrFail($client)->first();
$item->delete();
session()->flash('message', 'Client deleted');
return redirect('/clients');
}
find() and findOrFail() need an integer to return one element. If you pass something else, you will get a collection.
Since you are asking for a Client object as a parameter, you don't have to check it. Laravel will never fire this function when the object does not exists, so you don't have to check it.
public function destroy(Client $client)
{
$client->delete();
session()->flash('message', 'Client deleted');
return redirect('/clients');
}
For more information read https://laravel.com/docs/5.5/eloquent#retrieving-single-models and the following part with not found exception
$client is already an instance of Client as you typehint it. You shouldn't need to "find" it again. Checking if it exists and then run delete should be enough.
public function destroy(client $client)
{
if ($client->exists)
$client->delete();
session()->flash('message', 'Client deleted');
return redirect('/clients');
}

Call to undefined method Illuminate\Database\Query\Builder::intersect()

I want to make Laravel Authorization using gate..Where In user model
User.php
public function hasPermission($name)
{
$permission = Permission::where('name','=', $name)->first();
$permissions = \DB::table('role_permission')
->join('permissions', 'permissions.id', '=', 'role_permission.permission_id')
->select('role_permission.*')
->where('role_permission.permission_id', '=',$permission->id)
->get();
if(! $permissions) {
return false;
}
return !! $permission->intersect($this->$permission)->count();
}
In AuthserviceProvider
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
$gate->before(function($user, $ability) {
return $user->hasPermission($ability);
});
}
My Table structure like.
User has name,email,password,id
permission has name,id
role has name,id
role_permission has role_id,permission_id
can anyone help me to find out what's the error here?
intersect method belongs to Collection class. You can't use intersect method on Model. You may try by:
return !! collect([$permission])->intersect($this->$permission)->count();
$this->$permission should be an array or collection
I guess that's because you can apply intersect to a collection, while you're getting an error saying you that you're calling it over Illuminate\Database\Query\Builder.
I see you're calling it on $permission, that's currently a record (a model) of Permission. Probably that's just a typo, and you want to use $permissions instead of $permission.
Anyway, try to explain better what's the behaviour you're looking for, because it's not clear.

Laravel 5 Override Eloquent Save Method

I have composite primary keys so Laravel's save() method didn't work. Therefore I need to override the save method. For example, my primary key is a composite key consisting of column_a and column_b. Can you give me example how to override the save method and where I put it?
My Primary Key: column_a, column_b
I've tried the followings:
protected function setKeysForSaveQuery(Builder $query)
{
parent::setKeysForSaveQuery($query);
$query->where('locale', '=', $this->locale);
return $query;
}
I found above code, but it causes 500 Internal Server Error even I call a method that only reads value from database. I have set $primaryKey = 'column_a' and 'locale' (inside where) to column_b. I didn't know what $this->locale refers to?
Here is another way I found, but it failed too
protected $secondaryKey = 'column_b';
function newQuery()
{
$query = parent::newQuery();
$query->where($this->secondaryKey, '=', $this->type);
return $query;
}
Again, I didn't know what $this->type refers to.
I have found the solution. In the model, add primaryKey variable and the following function
protected $primaryKey = array('column1','column2');
protected function setKeysForSaveQuery(\Illuminate\Database\Eloquent\Builder $query) {
if (is_array($this->primaryKey)) {
foreach ($this->primaryKey as $pk) {
$query->where($pk, '=', $this->original[$pk]);
}
return $query;
}else{
return parent::setKeysForSaveQuery($query);
}
}
Source: https://github.com/laravel/framework/issues/5517#issuecomment-113655441
You can override it. You can put it in your model, or you can put it in a trait and use this trate in multiple models if you want.
The original save() method is here (line 832), maybe this will help.

Laravel 5 Eager Loading with parameters

I'm working on a project with a bit of a complex model that has joins in its relations and also requires a parameter. It all works pretty well, except for when I need to eager load the relationship, as I couldn't figure out if there is a way to pass a parameter/variable to it.
The Controller
$template = Template::find($request->input('id'));
$this->output = $template->zones()->with('widgets_with_selected')->get();
The Model
public function widgets_with_selected($banner_id)
{
return $this->belongsToMany('App\Models\Widget', 'zone_has_widgets')
->leftJoin('banner_has_widgets', function($join) use($banner_id) {
$join->on('widgets.id', '=', 'banner_has_widgets.widget_id')
->where('banner_has_widgets.banner_id', '=', $banner_id);
})
->select('widgets.*', 'banner_has_widgets.banner_id');
}
This is returning a Missing argument error as the variable is not being passed.
I have resolved the issue by moving the logic to the controller, but I want to know if there is a way to keep the relationship in the model and just call it with a parameter.
Looking at the laravel code I dont think this is possible as you'd like to do it. You simply cant pass parameters to a with() call.
A possible workaround is to have an attribute on your model for $banner_id.
$template = Template::find($request->input('id'));
$template->banner_id = 1;
$this->output = $template->zones()->with('widgets_with_selected')->get();
Then change your relationship
public function widgets_with_selected()
{
return $this>belongsToMany('App\Models\Widget','zone_has_widgets')
->leftJoin('banner_has_widgets', function($join) use($this->banner_id) {
$join->on('widgets.id', '=', 'banner_has_widgets.widget_id')
->where('banner_has_widgets.banner_id', '=', $banner_id);
})
->select('widgets.*', 'banner_has_widgets.banner_id');
}
You could perhaps alter it a bit by passing the banner_id through a method. Sortof like this in your model:
public function setBanner($id) {
$this->banner_id = $id;
return $this;
}
Then you can do:
$template->setBanner($banner_id)->zones()->with('widgets_with_selected')->get();
Not sure if this works, and it's not really a clean solution but a hack.

Resources