├── README.md ├── chinese.md ├── french.md ├── german.md ├── images ├── logo-chinese.png ├── logo-english.png ├── logo-french.png ├── logo-japanese.png ├── logo-persian.png ├── logo-russian.png ├── logo-spanish.png ├── logo-thai.png └── logo-turkish.png ├── italian.md ├── japanese.md ├── persian.md ├── russian.md ├── spanish.md ├── thai.md └── turkish.md /README.md: -------------------------------------------------------------------------------- 1 | ![Laravel best practices](/images/logo-english.png?raw=true) 2 | 3 | Translations: 4 | 5 | [Nederlands](https://github.com/Protoqol/Beste-Laravel-Praktijken) (by [Protoqol](https://github.com/Protoqol)) 6 | 7 | [한국어](https://github.com/xotrs/laravel-best-practices) (by [cherrypick](https://github.com/xotrs)) 8 | 9 | [日本語](japanese.md) (by [2bo](https://github.com/2bo)) 10 | 11 | [漢語](chinese.md) (by [xiaoyi](https://github.com/Shiloh520)) 12 | 13 | [ภาษาไทย](thai.md) (by [kongvut sangkla](https://github.com/kongvut)) 14 | 15 | [فارسی](persian.md) (by [amirhossein baghaie](https://github.com/amirbagh75)) 16 | 17 | [Português](https://github.com/jonaselan/laravel-best-practices) (by [jonaselan](https://github.com/jonaselan)) 18 | 19 | [Русский](russian.md) 20 | 21 | [Tiếng Việt](https://chungnguyen.xyz/posts/code-laravel-lam-sao-cho-chuan) (by [Chung Nguyễn](https://github.com/nguyentranchung)) 22 | 23 | [Español](spanish.md) (by [César Escudero](https://github.com/cedaesca)) 24 | 25 | [Français](french.md) (by [Mikayil S.](https://github.com/mikayilsrt)) 26 | 27 | [Polski](https://github.com/maciejjeziorski/laravel-best-practices-pl) (by [Maciej Jeziorski](https://github.com/maciejjeziorski)) 28 | 29 | [Türkçe](turkish.md) (by [Burak](https://github.com/ikidnapmyself)) 30 | 31 | [Deutsche](german.md) (by [Sujal Patel](https://github.com/sujalpatel2209)) 32 | 33 | [Italiana](italian.md) (by [Sujal Patel](https://github.com/sujalpatel2209)) 34 | 35 | It's not a Laravel adaptation of SOLID principles, patterns etc. Here you'll find the best practices which are usually ignored in real life Laravel projects. 36 | 37 | ## Contents 38 | 39 | [Single responsibility principle](#single-responsibility-principle) 40 | 41 | [Fat models, skinny controllers](#fat-models-skinny-controllers) 42 | 43 | [Validation](#validation) 44 | 45 | [Business logic should be in service class](#business-logic-should-be-in-service-class) 46 | 47 | [Don't repeat yourself (DRY)](#dont-repeat-yourself-dry) 48 | 49 | [Prefer to use Eloquent over using Query Builder and raw SQL queries. Prefer collections over arrays](#prefer-to-use-eloquent-over-using-query-builder-and-raw-sql-queries-prefer-collections-over-arrays) 50 | 51 | [Mass assignment](#mass-assignment) 52 | 53 | [Do not execute queries in Blade templates and use eager loading (N + 1 problem)](#do-not-execute-queries-in-blade-templates-and-use-eager-loading-n--1-problem) 54 | 55 | [Comment your code, but prefer descriptive method and variable names over comments](#comment-your-code-but-prefer-descriptive-method-and-variable-names-over-comments) 56 | 57 | [Do not put JS and CSS in Blade templates and do not put any HTML in PHP classes](#do-not-put-js-and-css-in-blade-templates-and-do-not-put-any-html-in-php-classes) 58 | 59 | [Use config and language files, constants instead of text in the code](#use-config-and-language-files-constants-instead-of-text-in-the-code) 60 | 61 | [Use standard Laravel tools accepted by community](#use-standard-laravel-tools-accepted-by-community) 62 | 63 | [Follow Laravel naming conventions](#follow-laravel-naming-conventions) 64 | 65 | [Use shorter and more readable syntax where possible](#use-shorter-and-more-readable-syntax-where-possible) 66 | 67 | [Use IoC container or facades instead of new Class](#use-ioc-container-or-facades-instead-of-new-class) 68 | 69 | [Do not get data from the `.env` file directly](#do-not-get-data-from-the-env-file-directly) 70 | 71 | [Store dates in the standard format. Use accessors and mutators to modify date format](#store-dates-in-the-standard-format-use-accessors-and-mutators-to-modify-date-format) 72 | 73 | [Other good practices](#other-good-practices) 74 | 75 | ### **Single responsibility principle** 76 | 77 | A class and a method should have only one responsibility. 78 | 79 | Bad: 80 | 81 | ```php 82 | public function getFullNameAttribute() 83 | { 84 | if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) { 85 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 86 | } else { 87 | return $this->first_name[0] . '. ' . $this->last_name; 88 | } 89 | } 90 | ``` 91 | 92 | Good: 93 | 94 | ```php 95 | public function getFullNameAttribute() 96 | { 97 | return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort(); 98 | } 99 | 100 | public function isVerifiedClient() 101 | { 102 | return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified(); 103 | } 104 | 105 | public function getFullNameLong() 106 | { 107 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 108 | } 109 | 110 | public function getFullNameShort() 111 | { 112 | return $this->first_name[0] . '. ' . $this->last_name; 113 | } 114 | ``` 115 | 116 | [🔝 Back to contents](#contents) 117 | 118 | ### **Fat models, skinny controllers** 119 | 120 | Put all DB related logic into Eloquent models or into Repository classes if you're using Query Builder or raw SQL queries. 121 | 122 | Bad: 123 | 124 | ```php 125 | public function index() 126 | { 127 | $clients = Client::verified() 128 | ->with(['orders' => function ($q) { 129 | $q->where('created_at', '>', Carbon::today()->subWeek()); 130 | }]) 131 | ->get(); 132 | 133 | return view('index', ['clients' => $clients]); 134 | } 135 | ``` 136 | 137 | Good: 138 | 139 | ```php 140 | public function index() 141 | { 142 | return view('index', ['clients' => $this->client->getWithNewOrders()]); 143 | } 144 | 145 | class Client extends Model 146 | { 147 | public function getWithNewOrders() 148 | { 149 | return $this->verified() 150 | ->with(['orders' => function ($q) { 151 | $q->where('created_at', '>', Carbon::today()->subWeek()); 152 | }]) 153 | ->get(); 154 | } 155 | } 156 | ``` 157 | 158 | [🔝 Back to contents](#contents) 159 | 160 | ### **Validation** 161 | 162 | Move validation from controllers to Request classes. 163 | 164 | Bad: 165 | 166 | ```php 167 | public function store(Request $request) 168 | { 169 | $request->validate([ 170 | 'title' => 'required|unique:posts|max:255', 171 | 'body' => 'required', 172 | 'publish_at' => 'nullable|date', 173 | ]); 174 | 175 | .... 176 | } 177 | ``` 178 | 179 | Good: 180 | 181 | ```php 182 | public function store(PostRequest $request) 183 | { 184 | .... 185 | } 186 | 187 | class PostRequest extends Request 188 | { 189 | public function rules() 190 | { 191 | return [ 192 | 'title' => 'required|unique:posts|max:255', 193 | 'body' => 'required', 194 | 'publish_at' => 'nullable|date', 195 | ]; 196 | } 197 | } 198 | ``` 199 | 200 | [🔝 Back to contents](#contents) 201 | 202 | ### **Business logic should be in service class** 203 | 204 | A controller must have only one responsibility, so move business logic from controllers to service classes. 205 | 206 | Bad: 207 | 208 | ```php 209 | public function store(Request $request) 210 | { 211 | if ($request->hasFile('image')) { 212 | $request->file('image')->move(public_path('images') . 'temp'); 213 | } 214 | 215 | .... 216 | } 217 | ``` 218 | 219 | Good: 220 | 221 | ```php 222 | public function store(Request $request) 223 | { 224 | $this->articleService->handleUploadedImage($request->file('image')); 225 | 226 | .... 227 | } 228 | 229 | class ArticleService 230 | { 231 | public function handleUploadedImage($image) 232 | { 233 | if (!is_null($image)) { 234 | $image->move(public_path('images') . 'temp'); 235 | } 236 | } 237 | } 238 | ``` 239 | 240 | [🔝 Back to contents](#contents) 241 | 242 | ### **Don't repeat yourself (DRY)** 243 | 244 | Reuse code when you can. SRP is helping you to avoid duplication. Also, reuse Blade templates, use Eloquent scopes etc. 245 | 246 | Bad: 247 | 248 | ```php 249 | public function getActive() 250 | { 251 | return $this->where('verified', 1)->whereNotNull('deleted_at')->get(); 252 | } 253 | 254 | public function getArticles() 255 | { 256 | return $this->whereHas('user', function ($q) { 257 | $q->where('verified', 1)->whereNotNull('deleted_at'); 258 | })->get(); 259 | } 260 | ``` 261 | 262 | Good: 263 | 264 | ```php 265 | public function scopeActive($q) 266 | { 267 | return $q->where('verified', 1)->whereNotNull('deleted_at'); 268 | } 269 | 270 | public function getActive() 271 | { 272 | return $this->active()->get(); 273 | } 274 | 275 | public function getArticles() 276 | { 277 | return $this->whereHas('user', function ($q) { 278 | $q->active(); 279 | })->get(); 280 | } 281 | ``` 282 | 283 | [🔝 Back to contents](#contents) 284 | 285 | ### **Prefer to use Eloquent over using Query Builder and raw SQL queries. Prefer collections over arrays** 286 | 287 | Eloquent allows you to write readable and maintainable code. Also, Eloquent has great built-in tools like soft deletes, events, scopes etc. 288 | 289 | Bad: 290 | 291 | ```sql 292 | SELECT * 293 | FROM `articles` 294 | WHERE EXISTS (SELECT * 295 | FROM `users` 296 | WHERE `articles`.`user_id` = `users`.`id` 297 | AND EXISTS (SELECT * 298 | FROM `profiles` 299 | WHERE `profiles`.`user_id` = `users`.`id`) 300 | AND `users`.`deleted_at` IS NULL) 301 | AND `verified` = '1' 302 | AND `active` = '1' 303 | ORDER BY `created_at` DESC 304 | ``` 305 | 306 | Good: 307 | 308 | ```php 309 | Article::has('user.profile')->verified()->latest()->get(); 310 | ``` 311 | 312 | [🔝 Back to contents](#contents) 313 | 314 | ### **Mass assignment** 315 | 316 | Bad: 317 | 318 | ```php 319 | $article = new Article; 320 | $article->title = $request->title; 321 | $article->content = $request->content; 322 | $article->verified = $request->verified; 323 | // Add category to article 324 | $article->category_id = $category->id; 325 | $article->save(); 326 | ``` 327 | 328 | Good: 329 | 330 | ```php 331 | $category->article()->create($request->validated()); 332 | ``` 333 | 334 | [🔝 Back to contents](#contents) 335 | 336 | ### **Do not execute queries in Blade templates and use eager loading (N + 1 problem)** 337 | 338 | Bad (for 100 users, 101 DB queries will be executed): 339 | 340 | ```php 341 | @foreach (User::all() as $user) 342 | {{ $user->profile->name }} 343 | @endforeach 344 | ``` 345 | 346 | Good (for 100 users, 2 DB queries will be executed): 347 | 348 | ```php 349 | $users = User::with('profile')->get(); 350 | 351 | ... 352 | 353 | @foreach ($users as $user) 354 | {{ $user->profile->name }} 355 | @endforeach 356 | ``` 357 | 358 | [🔝 Back to contents](#contents) 359 | 360 | ### **Comment your code, but prefer descriptive method and variable names over comments** 361 | 362 | Bad: 363 | 364 | ```php 365 | if (count((array) $builder->getQuery()->joins) > 0) 366 | ``` 367 | 368 | Better: 369 | 370 | ```php 371 | // Determine if there are any joins. 372 | if (count((array) $builder->getQuery()->joins) > 0) 373 | ``` 374 | 375 | Good: 376 | 377 | ```php 378 | if ($this->hasJoins()) 379 | ``` 380 | 381 | [🔝 Back to contents](#contents) 382 | 383 | ### **Do not put JS and CSS in Blade templates and do not put any HTML in PHP classes** 384 | 385 | Bad: 386 | 387 | ```php 388 | let article = `{{ json_encode($article) }}`; 389 | ``` 390 | 391 | Better: 392 | 393 | ```php 394 | 395 | 396 | Or 397 | 398 |