Giter Site home page Giter Site logo

laravel-best-practices's Introduction

Laravel best practices

Contents

Single responsibility principle

Methods should do just one thing

Fat models, skinny controllers

Validation

Business logic should be in service class

Don't repeat yourself (DRY)

Prefer to use Eloquent over using Query Builder and raw SQL queries. Prefer collections over arrays

Mass assignment

Use eager loading to prevent (N + 1) problem

Chunk data for data-heavy tasks

Comment your code, but prefer descriptive method and variable names over comments

Use config and language files, constants instead of text in the code

Follow Laravel naming conventions

Convention over configuration

Use shorter and more readable syntax where possible

Use IoC container or facades instead of new Class

Do not get data from the .env file directly

Store dates in the standard format. Use accessors and mutators to modify date format

Do not use DocBlocks

Return JSON Response only from Controller

Dedicated Exception Class

Other good practices

Single responsibility principle

A class should have only one responsibility.

Bad:

public function update(Request $request): string
{
    $validated = $request->validate([
        'title' => 'required|max:255',
        'events' => 'required|array:date,type'
    ]);

    foreach ($request->events as $event) {
        $date = $this->carbon->parse($event['date'])->toString();

        $this->logger->log('Update event ' . $date . ' :: ' . $);
    }

    $this->event->updateGeneralEvent($request->validated());

    return back();
}

Good:

public function update(UpdateRequest $request): string
{
    $this->logService->logEvents($request->events);

    $this->event->updateGeneralEvent($request->validated());

    return back();
}

๐Ÿ” Back to contents

Methods should do just one thing

A function should do just one thing and do it well.

Bad:

public function getFullNameAttribute(): string
{
    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;
    }
}

Good:

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

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

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

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

๐Ÿ” Back to contents

Fat models, skinny controllers

Put all DB related logic into Eloquent models.

Bad:

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

    return $clients;
}

Good:

public function index()
{
    return $this->client->getWithNewOrders();
}

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

๐Ÿ” Back to contents

Validation

Move validation from controllers to Request classes.

Bad:

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

    ...
}

Good:

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

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

๐Ÿ” Back to contents

Business logic should be in service class

A controller must have only one responsibility, so move business logic from controllers to service classes.

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): void
    {
        if (!is_null($image)) {
            $image->move(public_path('images') . 'temp');
        }
    }
}

๐Ÿ” Back to contents

Don't repeat yourself (DRY)

Reuse code when you can. SRP is helping you to avoid duplication. Also, use Eloquent scopes 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', true)->whereNotNull('deleted_at');
}

public function getActive(): Collection
{
    return $this->active()->get();
}

public function getArticles(): Collection
{
    return $this->whereHas('user', function ($q) {
            $q->active();
        })->get();
}

๐Ÿ” Back to contents

Prefer to use Eloquent over using Query Builder and raw SQL queries. Prefer collections over arrays

Eloquent allows you to write readable and maintainable code. Also, Eloquent has great built-in tools like soft deletes, events, scopes etc. You may want to check out Eloquent to SQL reference

Bad:

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

Good:

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

๐Ÿ” Back to contents

Mass assignment

Bad:

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

Good:

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

๐Ÿ” Back to contents

Use eager loading to prevent (N + 1) problem

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

$users = User::all();
foreach ($users as $user) {
    $name = $user->profile->name;
}

Good (for 100 users, 2 DB queries will be executed):

  • $users = User::with('profile')->get(); or
  • Add $with = ['profile'] in User model
foreach ($users as $user) {
    $name = $user->profile->name;
}

๐Ÿ” Back to contents

Chunk data for data-heavy tasks

Bad:

$users = $this->get();

foreach ($users as $user) {
    ...
}

Good:

$this->chunk(500, function ($users) {
    foreach ($users as $user) {
        ...
    }
});

๐Ÿ” Back to contents

Prefer descriptive method and variable names over comments

Bad:

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

Good:

if ($this->hasJoins())

๐Ÿ” Back to contents

Use config and language files, constants instead of text in the code

Bad:

public function isNormal(): bool
{
    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'));

๐Ÿ” Back to contents

Follow Laravel naming conventions

Follow PSR standards.

Also, follow naming conventions accepted by Laravel community:

What How Good Bad
Controller singular ArticleController ArticlesController
Route plural articles/1 article/1
Route name snake_case with dot notation users.show_active users.show-active, show-active-users
Model singular User Users
hasOne or belongsTo relationship singular articleComment articleComments, article_comment
All other relationships plural articleComments articleComment, article_comments
Table plural article_comments article_comment, articleComments
Pivot table singular model names in alphabetical order article_user user_article, articles_users
Table column snake_case without model name meta_title MetaTitle; article_meta_title
Model property snake_case $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
Migration - 2017_01_01_000000_create_articles_table 2017_01_01_000000_articles
Method camelCase getAll get_all
Method in resource controller table store saveArticle
Method in test class camelCase testGuestCannotSeeArticle test_guest_cannot_see_article
Variable camelCase $articlesWithAuthor $articles_with_author
Collection descriptive, plural $activeUsers = User::active()->get() $active, $data
Object descriptive, singular $activeUser = User::active()->first() $users, $obj
Config and language files index snake_case articles_enabled ArticlesEnabled; articles-enabled
View kebab-case show-filtered.blade.php showFiltered.blade.php, show_filtered.blade.php
Config snake_case google_calendar.php googleCalendar.php, google-calendar.php
Contract (interface) adjective or noun AuthenticationInterface Authenticatable, IAuthentication
Trait adjective Notifiable NotificationTrait
Trait (PSR) adjective NotifiableTrait Notification
Enum singular UserType UserTypes, UserTypeEnum
FormRequest singular UpdateUserRequest UpdateUserFormRequest, UserFormRequest, UserRequest
Seeder singular UserSeeder UsersSeeder

๐Ÿ” Back to contents

Convention over configuration

As long as you follow certain conventions, you do not need to add additional configuration.

Bad:

// Table name 'Customer'
// Primary key 'customer_id'
class Customer extends Model
{
    const CREATED_AT = 'created_at';
    const UPDATED_AT = 'updated_at';

    protected $table = 'Customer';
    protected $primaryKey = 'customer_id';

    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class, 'role_customer', 'customer_id', 'role_id');
    }
}

Good:

// Table name 'customers'
// Primary key 'id'
class Customer extends Model
{
    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class);
    }
}

๐Ÿ” Back to contents

Use shorter and more readable syntax where possible

Bad:

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

Good:

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

More examples:

Common syntax Shorter and more readable syntax
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')
is_null($object->relation) ? null : $object->relation->id optional($object->relation)->id (in PHP 8: $object->relation?->id)
$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')

๐Ÿ” Back to contents

Use IoC / Service container instead of new Class

new Class syntax creates tight coupling between classes and complicates testing. Use IoC container or facades instead.

Bad:

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

Good:

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

...

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

๐Ÿ” Back to contents

Do not get data from the .env file directly

Pass the data to config files instead and then use the config() helper function to use the data in an application.

Bad:

$apiKey = env('API_KEY');

Good:

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

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

๐Ÿ” Back to contents

Store dates in the standard format. Use accessors and mutators to modify date format

A date as a string is less reliable than an object instance, e.g. a Carbon-instance. It's recommended to pass Carbon objects between classes instead of date strings. Rendering should be done in the display layer (templates):

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 $casts = [
    'ordered_at' => 'datetime',
];

// Blade view
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->format('m-d') }}

๐Ÿ” Back to contents

Do not use DocBlocks

DocBlocks reduce readability. Use a descriptive method name and modern PHP features like return type hints instead.

Bad:

/**
 * The function checks if given string is a valid ASCII string
 *
 * @param string $string String we get from frontend which might contain
 *                       illegal characters. Returns True is the string
 *                       is valid.
 *
 * @return bool
 * @author  John Smith
 *
 * @license GPL
 */

public function checkString($string)
{
}

Good:

public function isValidAsciiString(string $string): bool
{
}

๐Ÿ” Back to contents

Return JSON Response only from Controller

Use JSON Response return only in controller

Bad:

public function UserController()
{
    $user = $this->userService->getUser();

    return $user;
}

class UserService
{
    public function getUser()
    {
        return Response::json($user);
    }
}

Good:

public function UserController()
{
    $user = $this->getUser();

    return Response::json($user);
}

class UserService
{
    public function getUser()
    {
        return $user;
    }
}

๐Ÿ” Back to contents

Dedicated exception class

Define and throw a dedicated exception instead of using a generic one

For more information

Bad:

public function UserService()
{
    try {
     user = $this->getUser();
    } catch(Exception $e) {
        throw new Exception($message, $statusCode);
    }
}

Good:

public function UserService()
{
    try {
         user = $this->getUser();
    } catch(Exception $e) {
        throw new UserException($e);
    }
}

class UserException extends Exception
{
    $message = 'message';

    public function __construct($message)
    {
        Log::write($message, $code);
    }
}

๐Ÿ” Back to contents

Other good practices

Never put any logic in routes files.

Do not override standard framework features to avoid problems related to updating the framework version and many other issues.

Use modern PHP syntax where possible, but don't forget about readability.

Migrate code that will be used by multiple apps to support repositories.

๐Ÿ” Back to contents

laravel-best-practices's People

Contributors

alexeymezenin avatar ahmedsaoud31 avatar spekulatius avatar marianomoreyra avatar rizwanashraf1 avatar ohmydevops avatar kongvut avatar ikidnapmyself avatar pietrushek avatar kintumiku avatar anowarcst avatar mikayilsrt avatar atefbb avatar shabbir642 avatar binotaliu avatar cedaesca avatar jalallinux avatar ichtrojan avatar muratcakmaksoftware avatar datlechin avatar olsza avatar lpheller avatar 777vasya77 avatar jankrb avatar franciscollanquipichun avatar trippo avatar 2bo avatar noirhusky avatar rizzello avatar chapeupreto avatar

Forkers

shabbir642

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.