Back to Course |
How to Structure Laravel 11 Projects

Repeating Responses: BaseController or Traits?

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.


Option 1. Base Controller.

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?


Option 2. Traits.

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.


Open-Source Examples

Example Project 1. laravelio/laravel.io

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:

  • Finding the record by slug
  • Setting the slug field value with a generated unique slug (Trait has a few private methods to help with it)

app/Concerns/HasSlug.php:

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.

app/Models/Tag.php:

final class Tag extends Model
{
use HasFactory;
use HasSlug;
 
// ...
}

app/Models/Article.php:

final class Article extends Model
{
use HasFactory;
use HasSlug;
 
// ...
}

app/Models/Thread.php:

final class Thread extends Model
{
use HasFactory;
use HasSlug;
 
// ...
}

Then, for example, the findBySlug() method from the Trait is used for binding routes.

routes/bindings.php:

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


Example Project 2. academico-sis/academico

The following example is from academico-sis/academico, an open-source project.

This project uses the Trait to convert the price from/to cents.

app/Traits/PriceTrait.php:

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.

app/Models/Course.php:

class Course extends Model
{
use CrudTrait;
use LogsActivity;
use PriceTrait;
 
// ...
}

app/Models/Coupon.php:

class Coupon extends Model
{
use CrudTrait;
use PriceTrait;
 
// ...
}

When the price field is used, the Mutator from the trait will be used.