Returning the result may be a repeating part of various Controllers, especially in API controllers. Why don't we refactor it to have less repeatable code?
For example, if you want to return a specific format in your API responses:
Any API Controller:
public function store(StoreUserRequest $request){ // ... Store operation return response()->json([ 'result' => 'success', ], 200);} public function update(StoreUserRequest $request, User $user){ // ... Update operation return response()->json([ 'result' => 'success', ], 200);}
See the repeating part? Of course, they don't necessarily have to be identical, but some responses may be the same. So, what are our options? There are at least two ways how you can do that.
First, you can create the logic in the Base Controller, which is app/Http/Controllers/Controller.php
:
abstract class Controller{ public function respondOk($data) { return response()->json([ 'result' => 'success', 'data' => $data, ], 200); }}
And then, in your "regular" Controller, which extends that Base Controller, the return would be changed to $this->respondOk()
.
public function store(StoreUserRequest $request){ $user = (new CreateUserAction())->execute($request->validated()); NewUserDataJob::dispatch($user); NewUserRegistered::dispatch($user); return response()->json([ 'result' => 'success', 'data' => $user, ], 200); return $this->respondOk($user); }
This means that all Controllers will have access to that respondOk()
method. But what if you want to pick and choose which Controllers would have this behavior?
Another option is to use Traits. This is useful when you want only some Controllers to have that response logic, and you would manually choose which ones by adding Trait to them.
For example, make the app/Traits/APIResponsesTrait.php
file.
php artisan make:trait Traits/APIResponsesTrait
Inside this Trait, create the same method respondOk()
:
namespace App\Traits; trait APIResponsesTrait{ public function respondOk($data) { return response()->json([ 'result' => 'success', 'data' => $data, ], 200); }}
Now, you just need to add this Trait either to each Controller or the Base Controller.
abstract class Controller{ use APIResponsesTrait; }
After adding a trait, you can use the $this->respondOk()
method the same way we did above.
Generally, Traits are very useful if you want to add a specific behavior to multiple classes. Let's take a look at a few more examples.
Let's look at the examples of Trait usage.
The first example will be from laravelio/laravel.io, an open-source project.
This project has multiple Traits, but we will examine the HasSlug
trait. It is responsible for operations with the slug
field:
slug
field value with a generated unique slug (Trait has a few private methods to help with it)trait HasSlug{ public function slug(): string { return $this->slug; } public function setSlugAttribute(string $slug) { $this->attributes['slug'] = $this->generateUniqueSlug($slug); } public static function findBySlug(string $slug): self { return static::where('slug', $slug)->firstOrFail(); } private function generateUniqueSlug(string $value): string { $slug = $originalSlug = Str::slug($value) ?: Str::random(5); $counter = 0; while ($this->slugExists($slug, $this->exists ? $this->id() : null)) { $counter++; $slug = $originalSlug.'-'.$counter; } return $slug; } private function slugExists(string $slug, ?int $ignoreId = null): bool { $query = $this->where('slug', $slug); if ($ignoreId) { $query->where('id', '!=', $ignoreId); } return $query->exists(); }}
Then, this Trait is used in three Models.
final class Tag extends Model{ use HasFactory; use HasSlug; // ...}
final class Article extends Model{ use HasFactory; use HasSlug; // ...}
final class Thread extends Model{ use HasFactory; use HasSlug; // ...}
Then, for example, the findBySlug()
method from the Trait is used for binding routes.
// ... Route::bind('tag', function (string $slug) { return App\Models\Tag::findBySlug($slug);});Route::bind('thread', function (string $slug) { return App\Models\Thread::findBySlug($slug);}); Route::bind('username', function (string $username) { return App\Models\User::findByUsername($username);});Route::bind('article', function (string $slug) { return App\Models\Article::findBySlug($slug);});
This is an excellent example of a Trait being reused in multiple classes.
The following example is from academico-sis/academico, an open-source project.
This project uses the Trait to convert the price from/to cents.
trait PriceTrait{ public function price(): Attribute { return new Attribute( get: fn ($value) => $value / 100, set: fn ($value) => $value * 100, ); } public function getPriceWithCurrencyAttribute(): string { if (config('academico.currency_position') === 'before') { return config('academico.currency_symbol').' '.$this->price; } return $this->price.' '.config('academico.currency_symbol'); }}
Then, the Trait is used in more than one Model.
class Course extends Model{ use CrudTrait; use LogsActivity; use PriceTrait; // ...}
class Coupon extends Model{ use CrudTrait; use PriceTrait; // ...}
When the price field is used, the Mutator from the trait will be used.