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/