Laravel Best Practices

Principle of Single Responsibility

A class and a method should have only one responsibility.

For example:

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;
    }
}

Better writing:

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

public function isVerifiedClient()
{
    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;
}

Keep the controller concise

If you are using a query generator or raw SQL query, put all database-related logic into an Eloquent model or a Repository class.

For example:

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

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

Better writing:

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();
    }
}

Use custom Request classes for validation

Place the validation rules in the Request class.

Example:

public function store(Request $request)
{
    $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
        'publish_at' => 'nullable|date',
    ]);

    ....
}

Better writing:

public function store(PostRequest $request)
{    
    ....
}

class PostRequest extends Request
{
    public function rules()
    {
        return [
            'title' => 'required|unique:posts|max:255',
            'body' => 'required',
            'publish_at' => 'nullable|date',
        ];
    }
}

Business code to be placed in the service layer

Controllers must follow the single responsibility principle, so it is best to move business code from the controller to the service layer.

Example:

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

Better writing:

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');
        }
    }
}

DRY Principle Don't Repeat Yourself

Reuse code as much as possible, and SRP can help you avoid duplicating wheels. In addition, try to reuse the Blade template and use Eloquent's scopes method to implement the code.

Example:

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();
}

Better writing:

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();
}

Use ORM instead of sql statements, use collections instead of arrays

Using Eloquent can help you write readable and maintainable code. Eloquent also has very elegant built-in tools, such as soft deletion, events, scopes, etc.

Example:

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

Better writing:

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

Centralized data processing

Example:

$article = new Article;
$article->title = $request->title;
$article->content = $request->content;
$article->verified = $request->verified;
// Add category to article
$article->category_id = $category->id;
$article->save();

Better writing:

$category->article()->create($request->validated());

Do not query in templates, try to use lazy loading

Example (101 DB queries will be executed for 100 users):

@foreach (User::all() as $user)
    {{ $user->profile->name }}
@endforeach

Better Writing (For 100 users, only two DB queries are executed with the following Writing):

$users = User::with('profile')->get();

...

@foreach ($users as $user)
    {{ $user->profile->name }}
@endforeach

Comment on your code, but it's more elegant to write your code in a descriptive language

Example:

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

Add notes:

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

Better writing:

if ($this->hasJoins())

Don't put JS and CSS in Blade templates or any HTML code in PHP code

Example:

let article = `{{ json_encode($article) }}`;

Better writing:

<input id="article" type="hidden" value="@json($article)">

Or

<button class="js-fav-article" data-article="@json($article)">{{ $article->name }}<button>

Add:

let article = $('#article').val();

Of course, the best way is to use professional PHP JS package to transfer data.

Use configuration, language packages, and constants instead of hard coding in your code

Example:

public function isNormal()
{
    return $article->type === 'normal';
}

return back()->with('message', 'Your article has been added!');

Better writing:

public function isNormal()
{
    return $article->type === Article::TYPE_NORMAL;
}

return back()->with('message', __('app.article_added'));

Using the standard Laravel tool for community acceptance

It is strongly recommended to use built-in Lavel functions and extension packages rather than third-party extension packages and tools.
If your project is taken over by other developers, they will have to relearn the use of these third-party tools.
In addition, when you use third-party extension packages or tools, it's hard to get any help from the Laravel community. Don't let your customers pay for extra problems.

Functions to be implemented Standard tools Third-party tools
Jurisdiction Policies Entrust, Sentinel, or other extension packages
Resource Compilation Tool Laravel Mix Grunt, Gulp, or other third-party packages
development environment Homestead Docker
deploy Laravel Forge Deployer or other solutions
automated testing PHPUnit, Mockery Phpspec
Page preview test Laravel Dusk Codeception
DB Manipulation Eloquent SQL, Doctrine
Template Blade Twig
Data manipulation Laravel set array
Form Validation Request classes His third-party packages are even validated in the controller
Jurisdiction Built-in His third-party package or your own solution
API authentication Laravel Passport Third-party JWT or OAuth extensions
Create API Built-in Dingo API or similar extension packages
Create a database structure Migrations Create directly with DB statements
localization Built-in Third party package
Real-time message queue Laravel Echo, Pusher Use third-party packages or use WebSockets directly
Create test data Seeder classes, Model Factories, Faker Manual creation of test data
task scheduling Laravel Task Scheduler Scripts and third-party packages
data base MySQL, PostgreSQL, SQLite, SQL Server MongoDB

Follow the laravel naming convention

source PSR standards.

In addition, follow the Laravel community-approved naming convention:

object rule Better Writing Avoidable Writing
Controller Singular ArticleController ArticlesController
Route complex articles/1 article/1
Route naming Serpentine naming with dots users.show_active users.show-active, show-active-users
Model Singular User Users
hasOne or belongsTo relationships Singular articleComment articleComments, article_comment
All other relationships complex articleComments articleComment, article_comments
form complex article_comments article_comment, articleComments
Perspective table Alphabetical Arrangement Model article_user user_article, articles_users
Data table field Use snake shape without table name meta_title MetaTitle; article_meta_title
model parameter Serpentine nomenclature $model->created_at $model->createdAt
foreign key Singular model name with _id suffix article_id ArticleId, id_article, articles_id
Primary key - id custom_id
transfer - 2017_01_01_000000_create_articles_table 2017_01_01_000000_articles
Method Naming Hump getAll get_all
Resource Controller table store saveArticle
Test class Naming Hump testGuestCannotSeeArticle test_guest_cannot_see_article
variable Naming Hump $articlesWithAuthor $articles_with_author
aggregate Descriptive, complex $activeUsers = User::active()->get() $active, $data
object Descriptive, singular $activeUser = User::active()->first() $users, $obj
Configuration and Language File Index Serpentine nomenclature articles_enabled ArticlesEnabled; articles-enabled
view Naming of Short Horizontal Line show-filtered.blade.php showFiltered.blade.php, show_filtered.blade.php
To configure Serpentine nomenclature google_calendar.php googleCalendar.php, google-calendar.php
Content (interface) Adjectives or nouns Authenticatable AuthenticationInterface, IAuthentication
Trait Use adjectives Notifiable NotificationTrait

Use as short and readable a grammar as possible

Example:

$request->session()->get('cart');
$request->input('name');

Better writing:

session('cart');
$request->name;

More examples:

Routine Writing More elegant writing
Session::get('cart') session('cart')
$request->session()->get('cart') session('cart')
Session::put('cart', $data) session(['cart' => $data])
$request->input('name'), Request::get('name') $request->name, request('name')
return Redirect::back() return back()
is_null($object->relation) ? null : $object->relation->id optional($object->relation)->id
return view('index')->with('title', $title)->with('client', $client) return view('index', compact('title', 'client'))
$request->has('value') ? $request->value : 'default'; $request->get('value', 'default')
Carbon::now(), Carbon::today() now(), today()
App::make('Class') app('Class')
->where('column', '=', 1) ->where('column', 1)
->orderBy('created_at', 'desc') ->latest()
->orderBy('age', 'desc') ->latest('age')
->orderBy('created_at', 'asc') ->oldest()
->select('id', 'name')->get() ->get(['id', 'name'])
->first()->name ->value('name')

Use IOC containers to create instances instead of directly creating a new instance

Creating new classes makes the coupling between classes more complex and makes testing more complex. Change to IoC container or injection.

Example:

$user = new User;
$user->create($request->validated());

Better writing:

public function __construct(User $user)
{
    $this->user = $user;
}

....

$this->user->create($request->validated());

Avoid getting data directly from. env files

Pass the data to the configuration file, and then use the config () help function to call the data

Example:

$apiKey = env('API_KEY');

Better writing:

// config/api.php
'key' => env('API_KEY'),

// Use the data
$apiKey = config('api.key');

Use standard format to store dates, and use accessors and modifiers to modify date formats

Example:

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

Better writing:

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

// View
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->some_date }}

Other good suggestions

Never put any logic code in the routing file.

Try not to write the original PHP code in the Blade template.

Author: ikidnapmyself

Link to the original: https://github.com/alexeymezenin/laravel-best-practices/

Keywords: PHP Laravel Session SQL

Added by jonemo on Fri, 23 Aug 2019 09:20:43 +0300