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? (36 h 00 min)

You also get:

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

Already a member? Login here

Gavin Kimpson avatar

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

Povilas Korop avatar

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

👍 6
Gavin Kimpson avatar

perfect thank you!

Huy Nguyen avatar

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?

Povilas Korop avatar

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.

andywong31 avatar

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.

👍 2
Bless Darah avatar

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.

👍 2
Povilas Korop avatar

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.

👍 2
andywong31 avatar

thank you both for your inputs Darah and Povilas!

dcxweb avatar

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.

augusto-dmh avatar

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
Usman Latif avatar

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.

👍 3
Usman Latif avatar

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;
}
👍 8
Povilas Korop avatar

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

👍 2
Zar Ni Phyoe avatar

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 :) )

abphaiboon avatar

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?

Povilas Korop avatar

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

👍 1
Chris McGee avatar

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.

Povilas Korop avatar

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

Chris McGee avatar

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."

hirenjoshi avatar

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.

Povilas Korop avatar

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

👍 1
Zar Ni Phyoe avatar

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

Povilas Korop avatar

Probably forgot to do it, sorry.

👍 1
Chris McGee avatar

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.

Ikram Khizer avatar

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

auth()->user()->vehicles()->create($request->validated());
Modestas avatar

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.