Skip to main content

Remember, in the very beginning, we had created a structure for the Vehicle model? Let me remind you:

Migration file:

Schema::create('vehicles', function (Blueprint $table) {
$table->id();
 
$table->foreignId('user_id')->constrained();
$table->string('plate_number');
 
$table->timestamps();
$table->softDeletes();
});

app/Models/Vehicle.php:

use Illuminate\Database\Eloquent\SoftDeletes;
 
class Vehicle extends Model
{
use HasFactory;
use SoftDeletes;
 
protected $fillable = ['user_id', 'plate_number'];
}

So now we need API endpoints for a user to manage their vehicles. This should be a typical CRUD, with these 5 methods in the Controller:

  • index
  • store
  • show
  • update
  • delete

So, let's generate it. This is our first Controller without the "Auth" namespace, and let's add a few Artisan flags to generate some skeleton for us...

The Full Lesson is Only for Premium Members

Want to access all of our courses? (30 h 09 min)

You also get:

55 courses
Premium tutorials
Access to repositories
Private Discord
Get Premium for $129/year or $29/month

Already a member? Login here

Comments & Discussion

GK
Gavin Kimpson ✓ Link copied!

What is the best way to write a custom message when there are no query results for model [App\\Models\\Vehicle] 3 for instance - I'd prefer to have a more friendly message like 'No vehicles found' without exposing names of my models etc

PK
Povilas Korop ✓ Link copied!

Exactly the article that I've published this week while answering a question from someone else: https://laraveldaily.com/post/laravel-api-override-404-error-message-route-model-binding

GK
Gavin Kimpson ✓ Link copied!

perfect thank you!

HN
Huy Nguyen ✓ Link copied!

With the implementation in the article, in case we call the route which was not defined, the message will still be "Vehicle record not found.", therefore it is quite confusing because the root cause is the route could not be found in the routes file. Do you have any suggestions to solve this issue?

PK
Povilas Korop ✓ Link copied!

Well, you choose what you want to return - either a model default error, or global 404 default error. Not sure if any other way is logical.

A
andywong31 ✓ Link copied!

Povilas question here, is there an alternative to using observers? i noticed observers tend to hide away the code and makes it harder when working with a team especially for new developers as they have to search around the codebase.

BD
Bless Darah ✓ Link copied!

I don't know anything for now but I suggest that when working with a team, it may be good to outline that this app is using observers to simulate multi-tentancy. That's just my thought.

PK
Povilas Korop ✓ Link copied!

Good question, and it has always been a debate and a personal preference: which you prefer more - full clarity but bloated controllers, or hide the code in some Laravel specific layer?

Alternative is the same booted() method of the Model and put the logic there. But wouldn't it violate the same thing you're asking against?

My personal opinion: better educate the developers about observers than "dumb down" the full codebase to be less maintainable.

A
andywong31 ✓ Link copied!

thank you both for your inputs Darah and Povilas!

D
dcxweb ✓ Link copied!

its a year old but I do agree it's always a debate on observers...I mean for a team member literally saying "we use observers" seems better to me in the long run than all the extra risk that comes by not having this handled in one place...for something like this use case anyway.

A
augusto-dmh ✓ Link copied!

To reduce the "hideness" of observers, instead of activating them in boot method from app.php, you can use in the newer versions of Laravel an attribute to the model class:

use App\Observers\VehicleObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
 
#[ObservedBy([VehicleObserver::class])]
class Vehicle extends Model
UL
Usman Latif ✓ Link copied!

W don't need to provide --resource or -r flag separately with --model flag while creating resource controller. When we provide --model flag it automatically create a resource controller.

UL
Usman Latif ✓ Link copied!

Instead of adding global scope to all model with declaring booted function separatley we can create a traits and call the trait in our model. I think it will be simple , easier and cleaner.

App\Scopes\UserRecordScope.php:

namespace App\Scopes;
 
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
 
class UserRecordScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function apply(Builder $builder, Model $model)
{
$builder->where('user_id', auth()->id());
}
}

App\Traits\UserRecord.php:

<?php
 
namespace App\Traits;
 
use App\Scopes\UserRecordScope;
 
trait UserRecord
{
public static function bootUserRecord()
{
static::addGlobalScope(new UserRecordScope);
}
}

If you don't want to have scope file separately you can do it directly in trait function:

 
namespace App\Traits;
use Illuminate\Database\Eloquent\Builder;
 
trait UserRecord
{
public static function bootUserRecord()
{
statis::addGlobalScope('user', function (Builder $builder) {
$builder->where('user_id', auth()->id());
});
}
}

App\Models\Vehicle.php:

use App\Traits\UserRecord;
 
class Vehicle extends Model {
use UserRecord;
}
PK
Povilas Korop ✓ Link copied!

Also an option, yes, thanks for the comment and the code attached!

ZN
Zar Ni Phyoe ✓ Link copied!

in my opinion, building seperate scope is fine cause it is a scope that we query specific record , but to use as a trait which is not really a trait (my point of view) , it is more confusing and the boot method is responsible for overriding the model boot method (laravel document said) and it should be inside model for getting more clear meaning that we are overriding it for specific global scope usage. ( just making a discussion comment :) )

A
abphaiboon ✓ Link copied!

Hi! I was curious, you organize the Controllers into API folders, V1, etc.

Is there a reason why we dont organize requests that way either? Dumb question, but I know Login ideally should never change.....

But maybe registration, or vehicle may change in the future revisions of API?

Im very new, so im curious what would be the standard?

PK
Povilas Korop ✓ Link copied!

Yes, you could structure those as well. But there's no "standard", it's your personal choice.

CM
Chris McGee ✓ Link copied!

You might want to amend these lessons to add the description column to the vehicles table since it is needed in the Vue.js front-end course. Unless, of course, you want to leave that as a surprise exercise for the student.

This includes updating other files, naturally, including VehicleFactory.php, VehicleResource.php, StoreVehicleRequest.php, and, of course, the model itself, Vehicle.php.

PK
Povilas Korop ✓ Link copied!

Thanks, will think about it, since, as you said, it's quite a lot of work and actually doesn't add much direct value to understanding Laravel/Vue

CM
Chris McGee ✓ Link copied!

Instead, you could just add a note to the Vue lesson in question, stating something like, "In order for the description to be saved, you'll need to add that column to the vehicles table, making sure to take into account all that will entail in the API."

H
hirenjoshi ✓ Link copied!

When we add global scope in booted method and access all vehicles it will filter and provide the vehicles belong to logged in user but what if I want to list down all the vehicles to admin and only the relavant vehicles to user when they do login. Is there any work around for that in global scope? I know that I can add condition inside closure but I am looking for much pretty idea.

PK
Povilas Korop ✓ Link copied!

Yes, you can use withoutGlobalScopes() in the queries where you want those disabled. See the docs.

ZN
Zar Ni Phyoe ✓ Link copied!

in Vehicle resource controller , you didn't add http:created response to that store function as for it is created , did it on purpose for some reason or forgot to do it cause you explained we are going use specific response for this tutorial

PK
Povilas Korop ✓ Link copied!

Probably forgot to do it, sorry.

CM
Chris McGee ✓ Link copied!

As it turns out, it's probably not necessary, since you should get a 201 (created) status code from calling that endpoint if it was successful. Maybe the make() method of a Json resource automatically makes it a 201.

IK
Ikram Khizer ✓ Link copied!

Instead of creating an observer, can't we just rely on the Eloquent relationship and do something like:

auth()->user()->vehicles()->create($request->validated());
M
Modestas ✓ Link copied!

You can, but this is manual work, where with an observer - this information would get created automatically.

The biggest difference is really automating some actions with observers (as soon as some model action happens - something else is triggered), and manually adding this. Manual addition can lead to errors if you forget to add it, while observers will still do the action

We'd Love Your Feedback

Tell us what you like or what we can improve

Feel free to share anything you like or dislike about this page or the platform in general.