Laravel Best Practices You Should Follow

To make Laravel development a breeze, you need to follow the best practices from time to time. Here in this article, I will be taking up the opportunity to discuss some of such best practices that you should follow in your Laravel project.

Best Practices for Laravel Development

Single responsibility principle

A class and a method should have only one responsibility.

Wrong approach:

public function getFullNameAttribute()
{
if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) {
return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' $this->last_name;
} else {
return $this->first_name[0] . '. ' . $this->last_name;
}
}

Recommended practices:

public function getFullNameAttribute()
{
return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
}

public function isVerfiedClient()
{
return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified();
}

public function getFullNameLong()
{
return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
}

public function getFullNameShort()
{
return $this->first_name[0] . '. ' . $this->last_name;
}

Powerful model & simple controller

If you use a query constructor or raw SQL to query, put all database related logic into the eloquent model or repository class.

Bad approach:

public function index()
{
$clients = Client::verified()
->with(['orders' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();

return view('index', ['clients' => $clients]);
}

Good:

public function index()
{
return view('index', ['clients' => $this->client->getWithNewOrders()]);
}

Class Client extends Model
{
public function getWithNewOrders()
{
return $this->verified()
->with(['orders' => function ($q) {
$q->where('created_at', '>', Carbon::today()->subWeek());
}])
->get();
}
}

The business logic should be in the service class

A controller must have only one responsibility, so the business logic should be moved from the controller to the service class.

Bad:

public function store(Request $request)
{
if ($request->hasFile('image')) {
$request->file('image')->move(public_path('images') . 'temp');
}

....
}

Good:

public function store(Request $request)
{
$this->articleService->handleUploadedImage($request->file('image'));
....
}
class ArticleService
{
public function handleUploadedImage($image)
{
if (!is_null($image)) {
$image->move(public_path('images') . 'temp');
}
}
}

Don’t repeat yourself (dry)

Reuse code as much as possible. SRP (single responsibility principle) is helping you avoid duplication. Of course, this also includes the blade template, the scope of eloquent, etc.

Bad:

public function getActive()
{
return $this->where('verified', 1)->whereNotNull('deleted_at')->get();
}
public function getArticles()
{
return $this->whereHas('user', function ($q) {
$q->where('verified', 1)->whereNotNull('deleted_at');
})->get();
}

Good:

public function scopeActive($q)
{
return $q->where('verified', 1)->whereNotNull('deleted_at');
}
public function getActive()
{
return $this->active()->get();
}
public function getArticles()
{
return $this->whereHas('user', function ($q) {
$q->active();
})->get();
}

It’s best to use eloquent instead of query builder and native SQL queries.

Eloquent can write readable and maintainable code. In addition, eloquent also has great built-in tools, such as soft deletion, event, scope, etc.

For example, you write:

SELECT *
FROM `articles`
WHERE EXISTS (SELECT *
FROM `users`
WHERE `articles`.`user_id` = `users`.`id`
AND EXISTS (SELECT *
FROM `profiles`
WHERE `profiles`.`user_id` = `users`.`id`)
AND `users`.`deleted_at` IS NULL)
AND `verified` = '1'
AND `active` = '1'
ORDER BY `created_at` DESC

Might as well write:

Article::has('user.profile')->verified()->latest()->get();

Instead of trying to annotate your code, write a descriptive name for a method or variable

Bad:

if (count((array) $builder->getQuery()->joins) > 0)

Good:

//Determine if there are any connections.
if (count((array) $builder->getQuery()->joins) > 0)

best:

if ($this->hasJoins())

Use configuration and language files and constants in your code instead of writing it dead

Bad:

public function isNormal()
{
return $article->type === 'normal';
}
return back()->with('message', 'Your article has been added!');

Good:

public function isNormal()
{
return $article->type === Article::TYPE_NORMAL;
}
return back()->with('message', __('app.article_added'));

Don’t directly from.envGet data from file

Pass the data to the configuration file, and then use auxiliary functions config() Use data in your application.

Bad:

$apiKey = env('API_KEY');

Good:

// config/api.php
'key' => env('API_KEY'),
// Use the data
$apiKey = config('api.key');

Store the date in a standard format and use accessors and modifiers to modify the date format if necessary

Bad:

{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}

Good:

// Model
protected $dates = ['ordered_at', 'created_at', 'updated_at']
public function getMonthDayAttribute($date)
{
return $date->format('m-d');
}
// View
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->monthDay }}

I hope these best practices are going to help you avoid any potential problems while building your application and you benefit from these tips that I discussed in this post. Thank you for reading!

If you have any Laravel-related questions or projects, feel free to reach me on my LinkedIn account & email address.

I’m a professional and passionate Full Stack Developer with ability to write Test-Driven and Clean code from scratch through Laravel and Vue.JS/ React.Js.