├── README.md ├── images ├── logo-english.png └── logo-russian.png └── russian.md /README.md: -------------------------------------------------------------------------------- 1 | ![Laravel best practices](/images/logo-english.png?raw=true) 2 | 3 | 切换语言: 4 | 5 | + [English](https://github.com/alexeymezenin/laravel-best-practices) 6 | + [Русский](russian.md) 7 | 8 | 我们这里要讨论的并不是 Laravel 版的 SOLID 原则(想要了解更多 SOLID 原则细节查看这篇文章)亦或是设计模式,而是 Laravel 实际开发中容易被忽略的最佳实践。 9 |

内容概览

10 | 30 |

单一职责原则

31 | 一个类和方法只负责一项职责。 32 | 33 | 坏代码: 34 |
public function getFullNameAttribute()
 35 | {
 36 |     if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) {
 37 |         return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' $this->last_name;
 38 |     } else {
 39 |         return $this->first_name[0] . '. ' . $this->last_name;
 40 |     }
 41 | }
 42 | 
43 | 好代码: 44 |
public function getFullNameAttribute()
 45 | {
 46 |     return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
 47 | }
 48 | 
 49 | public function isVerfiedClient()
 50 | {
 51 |     return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified();
 52 | }
 53 | 
 54 | public function getFullNameLong()
 55 | {
 56 |     return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
 57 | }
 58 | 
 59 | public function getFullNameShort()
 60 | {
 61 |     return $this->first_name[0] . '. ' . $this->last_name;
 62 | }
 63 | 
64 |

胖模型、瘦控制器

65 | 如果你使用的是查询构建器或原生 SQL 查询的话将所有 DB 相关逻辑都放到 Eloquent 模型或 Repository 类。 66 | 67 | 坏代码: 68 |
public function index()
 69 | {
 70 |     $clients = Client::verified()
 71 |         ->with(['orders' => function ($q) {
 72 |             $q->where('created_at', '>', Carbon::today()->subWeek());
 73 |         }])
 74 |         ->get();
 75 | 
 76 |     return view('index', ['clients' => $clients]);
 77 | }
 78 | 
79 | 好代码: 80 |
public function index()
 81 | {
 82 |     return view('index', ['clients' => $this->client->getWithNewOrders()]);
 83 | }
 84 | 
 85 | Class Client extends Model
 86 | {
 87 |     public function getWithNewOrders()
 88 |     {
 89 |         return $this->verified()
 90 |             ->with(['orders' => function ($q) {
 91 |                 $q->where('created_at', '>', Carbon::today()->subWeek());
 92 |             }])
 93 |             ->get();
 94 |     }
 95 | }
 96 | 
97 |

验证

98 | 将验证逻辑从控制器转移到请求类。 99 | 100 | 坏代码: 101 |
public function store(Request $request)
102 | {
103 |     $request->validate([
104 |         'title' => 'required|unique:posts|max:255',
105 |         'body' => 'required',
106 |         'publish_at' => 'nullable|date',
107 |     ]);
108 | 
109 |     ....
110 | }
111 | 
112 | 好代码: 113 |
public function store(PostRequest $request)
114 | {    
115 |     ....
116 | }
117 | 
118 | class PostRequest extends Request
119 | {
120 |     public function rules()
121 |     {
122 |         return [
123 |             'title' => 'required|unique:posts|max:255',
124 |             'body' => 'required',
125 |             'publish_at' => 'nullable|date',
126 |         ];
127 |     }
128 | }
129 | 
130 |

业务逻辑需要放到服务类

131 | 一个控制器只负责一项职责,所以需要把业务逻辑都转移到服务类中。 132 | 133 | 坏代码: 134 |
public function store(Request $request)
135 | {
136 |     if ($request->hasFile('image')) {
137 |         $request->file('image')->move(public_path('images') . 'temp');
138 |     }
139 | 
140 |     ....
141 | }
142 | 
143 | 好代码: 144 |
public function store(Request $request)
145 | {
146 |     $this->articleService->handleUploadedImage($request->file('image'));
147 | 
148 |     ....
149 | }
150 | 
151 | class ArticleService
152 | {
153 |     public function handleUploadedImage($image)
154 |     {
155 |         if (!is_null($image)) {
156 |             $image->move(public_path('images') . 'temp');
157 |         }
158 |     }
159 | }
160 | 
161 |

DRY

162 | 尽可能复用代码,单一职责原则可以帮助你避免重复,此外,尽可能复用 Blade 模板,使用 Eloquent 作用域。 163 | 164 | 坏代码: 165 |
public function getActive()
166 | {
167 |     return $this->where('verified', 1)->whereNotNull('deleted_at')->get();
168 | }
169 | 
170 | public function getArticles()
171 | {
172 |     return $this->whereHas('user', function ($q) {
173 |             $q->where('verified', 1)->whereNotNull('deleted_at');
174 |         })->get();
175 | }
176 | 
177 | 好代码: 178 |
public function scopeActive($q)
179 | {
180 |     return $q->where('verified', 1)->whereNotNull('deleted_at');
181 | }
182 | 
183 | public function getActive()
184 | {
185 |     return $this->active()->get();
186 | }
187 | 
188 | public function getArticles()
189 | {
190 |     return $this->whereHas('user', function ($q) {
191 |             $q->active();
192 |         })->get();
193 | }
194 | 
195 |

优先使用 Eloquent 和 集合

196 | 通过 Eloquent 可以编写出可读性和可维护性更好的代码,此外,Eloquent 还提供了强大的内置工具如软删除、事件、作用域等。 197 | 198 | 坏代码: 199 |
SELECT *
200 | FROM `articles`
201 | WHERE EXISTS (SELECT *
202 |               FROM `users`
203 |               WHERE `articles`.`user_id` = `users`.`id`
204 |               AND EXISTS (SELECT *
205 |                           FROM `profiles`
206 |                           WHERE `profiles`.`user_id` = `users`.`id`) 
207 |               AND `users`.`deleted_at` IS NULL)
208 | AND `verified` = '1'
209 | AND `active` = '1'
210 | ORDER BY `created_at` DESC
211 | 
212 | 好代码: 213 |
 Article::has('user.profile')->verified()->latest()->get();
214 | 
215 |

批量赋值

216 | 关于批量赋值细节可查看对应文档。 217 | 218 | 坏代码: 219 |
$article = new Article;
220 | $article->title = $request->title;
221 | $article->content = $request->content;
222 | $article->verified = $request->verified;
223 | // Add category to article
224 | $article->category_id = $category->id;
225 | $article->save();
226 | 
227 | 好代码: 228 |
$category->article()->create($request->all());
229 | 
230 |

不要在 Blade 执行查询 & 使用渴求式加载

231 | 坏代码: 232 |
@foreach (User::all() as $user)
233 |     {{ $user->profile->name }}
234 | @endforeach
235 | 
236 | 好代码: 237 |
$users = User::with('profile')->get();
238 | 
239 | ...
240 | 
241 | @foreach ($users as $user)
242 |     {{ $user->profile->name }}
243 | @endforeach
244 | 
245 |

注释你的代码

246 | 坏代码: 247 |
if (count((array) $builder->getQuery()->joins) > 0)
248 | 
249 | 好代码: 250 |
// Determine if there are any joins.
251 | if (count((array) $builder->getQuery()->joins) > 0)
252 | 
253 | 最佳: 254 |
if ($this->hasJoins())
255 | 
256 |

将前端代码和 PHP 代码分离:

257 | 不要把 JS 和 CSS 代码写到 Blade 模板里,也不要在 PHP 类中编写 HTML 代码。 258 | 259 | 坏代码: 260 |
let article = `{{ json_encode($article) }}`;
261 | 
262 | 好代码: 263 |
<input id="article" type="hidden" value="{{ json_encode($article) }}">
264 | 
265 | 或者
266 | 
267 | <button class="js-fav-article" data-article="{{ json_encode($article) }}">{{ $article->name }}<button>
268 | 
269 | 在 JavaScript 文件里: 270 |
let article = $('#article').val();
271 | 
272 |

使用配置、语言文件和常量取代硬编码

273 | 坏代码: 274 |
public function isNormal()
275 | {
276 |     return $article->type === 'normal';
277 | }
278 | 
279 | return back()->with('message', 'Your article has been added!');
280 | 
281 | 好代码: 282 |
public function isNormal()
283 | {
284 |     return $article->type === Article::TYPE_NORMAL;
285 | }
286 | 
287 | return back()->with('message', __('app.article_added'));
288 | 
289 |

使用被社区接受的标准 Laravel 工具

290 | 优先使用 Laravel 内置功能和社区版扩展包,其次才是第三方扩展包和工具。这样做的好处是降低以后的学习和维护成本。 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 |
任务标准工具第三方工具
授权策略类Entrust、Sentinel等
编译资源Laravel MixGrunt、Gulp等
开发环境HomesteadDocker
部署Laravel ForgeDeployer等
单元测试PHPUnit、MockeryPhpspec
浏览器测试Laravel DuskCodeception
DBEloquentSQL、Doctrine
模板BladeTwig
处理数据Laravel集合数组
表单验证请求类第三方扩展包、控制器中验证
认证内置功能第三方扩展包、你自己的解决方案
API认证Laravel Passport第三方 JWT 和 OAuth 扩展包
创建API内置功能Dingo API和类似扩展包
处理DB结构迁移直接操作DB
本地化内置功能第三方工具
实时用户接口Laravel Echo、Pusher第三方直接处理 WebSocket的扩展包
生成测试数据填充类、模型工厂、Faker手动创建测试数据
任务调度Laravel Task Scheduler脚本或第三方扩展包
DBMySQL、PostgreSQL、SQLite、SQL ServerMongoDB
397 |

遵循 Laravel 命名约定

398 | 遵循 PSR 标准。此外,还要遵循 Laravel 社区版的命名约定: 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 |
WhatHowGoodBad
控制器单数ArticleControllerArticlesController
路由复数articles/1article/1
命名路由下划线+'.'号分隔users.show_activeusers.show-active,show-active-users
模型单数UserUsers
一对一关联单数articleCommentarticleComments,article_comment
其他关联关系复数articleCommentsarticleComment,article_comments
数据表复数article_commentsarticle_comment,articleComments
中间表按字母表排序的单数格式article_useruser_article,article_users
表字段下划线,不带模型名meta_titleMetaTitle; article_meta_title
外键单数、带_id后缀article_idArticleId, id_article, articles_id
主键-idcustom_id
迁移-2017_01_01_000000_create_articles_table2017_01_01_000000_articles
方法驼峰getAllget_all
资源类方法文档storesaveArticle
测试类方法驼峰testGuestCannotSeeArticletest_guest_cannot_see_article
变量驼峰$articlesWithAuthor$articles_with_author
集合复数$activeUsers = User::active()->get()$active, $data
对象单数$activeUser = User::active()->first()$users, $obj
配置和语言文件索引下划线articles_enabledArticlesEnabled; articles-enabled
视图下划线show_filtered.blade.phpshowFiltered.blade.php, show-filtered.blade.php
配置下划线google_calendar.phpgoogleCalendar.php, google-calendar.php
契约(接口)形容词或名词AuthenticatableAuthenticationInterface, IAuthentication
Trait形容词NotifiableNotificationTrait
549 |

使用缩写或可读性更好的语法

550 | 坏代码: 551 |
$request->session()->get('cart');
552 | $request->input('name');
553 | 
554 | 好代码: 555 |
session('cart');
556 | $request->name;
557 | 
558 | 更多示例: 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 |
通用语法可读性更好的
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) ? $object->relation->id : null }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')
633 |

使用 IoC 容器或门面

634 | 自己创建新的类会导致代码耦合度高,且难于测试,取而代之地,我们可以使用 IoC 容器或门面。 635 | 636 | 坏代码: 637 |
$user = new User;
638 | $user->create($request->all());
639 | 
640 | 好代码: 641 |
public function __construct(User $user)
642 | {
643 |     $this->user = $user;
644 | }
645 | 
646 | ....
647 | 
648 | $this->user->create($request->all());   
649 | 
650 |

不要从直接从 .env 获取数据

651 | 传递数据到配置文件然后使用 config 辅助函数获取数据。 652 | 653 | 坏代码: 654 |
$apiKey = env('API_KEY');
655 | 
656 | 好代码: 657 |
// config/api.php
658 | 'key' => env('API_KEY'),
659 | 
660 | // Use the data
661 | $apiKey = config('api.key');
662 | 
663 |

以标准格式存储日期

664 | 使用访问器和修改器来编辑日期格式。 665 | 666 | 坏代码: 667 |
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
668 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}
669 | 
670 | 好代码: 671 |
// Model
672 | protected $dates = ['ordered_at', 'created_at', 'updated_at']
673 | public function getMonthDayAttribute($date)
674 | {
675 |     return $date->format('m-d');
676 | }
677 | 
678 | // View
679 | {{ $object->ordered_at->toDateString() }}
680 | {{ $object->ordered_at->monthDay }}
681 | 
682 |

其他好的实践

683 | 不要把任何业务逻辑写到路由文件中。 684 | 685 | 在 Blade 模板中尽量不要编写原生 PHP。 686 | -------------------------------------------------------------------------------- /images/logo-english.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonfu/laravel-best-practices/65afcf5d8dd565c133b840db7f941ccbbe51382c/images/logo-english.png -------------------------------------------------------------------------------- /images/logo-russian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonfu/laravel-best-practices/65afcf5d8dd565c133b840db7f941ccbbe51382c/images/logo-russian.png -------------------------------------------------------------------------------- /russian.md: -------------------------------------------------------------------------------- 1 | ![Хорошие практики Laravel](/images/logo-russian.png?raw=true) 2 | 3 | Это не пересказ лучших практик вроде SOLID, паттернов и пр. с адаптацией под Laravel. Здесь собраны именно практики, которые игнорируются в реальных Laravel проектах. Также, рекомендую ознакомитсья с [хорошими практиками в контексте PHP](https://github.com/jupeter/clean-code-php). Смотрите также [обсуждение хороших практик Laravel](https://laravel.ru/forum/viewforum.php?id=17). 4 | 5 | ## Содержание 6 | 7 | [Принцип единственной ответственности (Single responsibility principle)](#Принцип-единственной-ответственности-single-responsibility-principle) 8 | 9 | [Тонкие контроллеры, толстые модели](#Тонкие-контроллеры-толстые-модели) 10 | 11 | [Валидация](#Валидация) 12 | 13 | [Бизнес логика в сервис-классах](#Бизнес-логика-в-сервис-классах) 14 | 15 | [Не повторяйся (DRY)](#Не-повторяйся-dry) 16 | 17 | [Предпочитайте Eloquent конструктору запросов (query builder) и сырым запросам в БД. Предпочитайте работу с коллекциями работе с массивами](#Предпочитайте-eloquent-конструктору-запросов-query-builder-и-сырым-запросам-в-БД-Предпочитайте-работу-с-коллекциями-работе-с-массивами) 18 | 19 | [Используйте массовое заполнение (mass assignment)](#Используйте-массовое-заполнение-mass-assignment) 20 | 21 | [Не выполняйте запросы в представлениях и используйте нетерпеливую загрузку (проблема N + 1)](#Не-выполняйте-запросы-в-представлениях-и-используйте-нетерпеливую-загрузку-проблема-n--1) 22 | 23 | [Комментируйте код, предпочитайте читаемые имена методов комментариям](#Комментируйте-код-предпочитайте-читаемые-имена-методов-комментариям) 24 | 25 | [Выносите JS и CSS из шаблонов Blade и HTML из PHP кода](#Выносите-js-и-css-из-шаблонов-blade-и-html-из-php-кода) 26 | 27 | [Используйте инструменты и практики принятые сообществом](#Используйте-инструменты-и-практики-принятые-сообществом) 28 | 29 | [Соблюдайте соглашения сообщества об именовании](#Соблюдайте-соглашения-сообщества-об-именовании) 30 | 31 | [Конфиги, языковые файлы и константы вместо текста в коде](#Конфиги-языковые-файлы-и-константы-вместо-текста-в-коде) 32 | 33 | [Короткий и читаемый синтаксис там, где это возможно](#Короткий-и-читаемый-синтаксис-там-где-это-возможно) 34 | 35 | [Используйте IoC или фасады вместо new Class](#Используйте-ioc-или-фасады-вместо-new-class) 36 | 37 | [Не работайте с данными из файла `.env` напрямую](#Не-работайте-с-данными-из-файла-env-напрямую) 38 | 39 | [Храните даты в стандартном формате. Используйте читатели и преобразователи для преобразования формата](#Храните-даты-в-стандартном-формате-Используйте-читатели-и-преобразователи-для-преобразования-формата) 40 | 41 | [Другие советы и практики](#Другие-советы-и-практики) 42 | 43 | ### **Принцип единственной ответственности (Single responsibility principle)** 44 | 45 | Каждый класс и метод должны выполнять лишь одну функцию. 46 | 47 | Плохо: 48 | 49 | ``` 50 | public function getFullNameAttribute() 51 | { 52 | if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) { 53 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' $this->last_name; 54 | } else { 55 | return $this->first_name[0] . '. ' . $this->last_name; 56 | } 57 | } 58 | ``` 59 | 60 | Хорошо: 61 | 62 | ``` 63 | public function getFullNameAttribute() 64 | { 65 | return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort(); 66 | } 67 | 68 | public function isVerfiedClient() 69 | { 70 | return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified(); 71 | } 72 | 73 | public function getFullNameLong() 74 | { 75 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 76 | } 77 | 78 | public function getFullNameShort() 79 | { 80 | return $this->first_name[0] . '. ' . $this->last_name; 81 | } 82 | ``` 83 | 84 | [🔝 Наверх](#Содержание) 85 | 86 | ### **Тонкие контроллеры, толстые модели** 87 | 88 | По своей сути, это лишь один из частных случаев принципа единой ответственности. Выносите работу с данными в модели при работе с Eloquent или в репозитории при работе с Query Builder или "сырыми" SQL запросами. 89 | 90 | Плохо: 91 | 92 | ``` 93 | public function index() 94 | { 95 | $clients = Client::verified() 96 | ->with(['orders' => function ($q) { 97 | $q->where('created_at', '>', Carbon::today()->subWeek()); 98 | }]) 99 | ->get(); 100 | 101 | return view('index', ['clients' => $clients]); 102 | } 103 | ``` 104 | 105 | Хорошо: 106 | 107 | ``` 108 | public function index() 109 | { 110 | return view('index', ['clients' => $this->client->getWithNewOrders()]); 111 | } 112 | 113 | Class Client extends Model 114 | { 115 | public function getWithNewOrders() 116 | { 117 | return $this->verified() 118 | ->with(['orders' => function ($q) { 119 | $q->where('created_at', '>', Carbon::today()->subWeek()); 120 | }]) 121 | ->get(); 122 | } 123 | } 124 | ``` 125 | 126 | [🔝 Наверх](#Содержание) 127 | 128 | ### **Валидация** 129 | 130 | Следуя принципам тонкого контроллера и SRP, выносите валидацию из контроллера в Request классы. 131 | 132 | Плохо: 133 | 134 | ``` 135 | public function store(Request $request) 136 | { 137 | $request->validate([ 138 | 'title' => 'required|unique:posts|max:255', 139 | 'body' => 'required', 140 | 'publish_at' => 'nullable|date', 141 | ]); 142 | 143 | .... 144 | } 145 | ``` 146 | 147 | Хорошо: 148 | 149 | ``` 150 | public function store(PostRequest $request) 151 | { 152 | .... 153 | } 154 | 155 | class PostRequest extends Request 156 | { 157 | public function rules() 158 | { 159 | return [ 160 | 'title' => 'required|unique:posts|max:255', 161 | 'body' => 'required', 162 | 'publish_at' => 'nullable|date', 163 | ]; 164 | } 165 | } 166 | ``` 167 | 168 | [🔝 Наверх](#Содержание) 169 | 170 | ### **Бизнес логика в сервис-классах** 171 | 172 | Контроллер должен выполнять только свои прямые обязанности, поэтому выносите всю бизнес логику в отдельные классы и сервис классы. 173 | 174 | Плохо: 175 | 176 | ``` 177 | public function store(Request $request) 178 | { 179 | if ($request->hasFile('image')) { 180 | $request->file('image')->move(public_path('images') . 'temp'); 181 | } 182 | 183 | .... 184 | } 185 | ``` 186 | 187 | Хорошо: 188 | 189 | ``` 190 | public function store(Request $request) 191 | { 192 | $this->articleService->handleUploadedImage($request->file('image')); 193 | 194 | .... 195 | } 196 | 197 | class ArticleService 198 | { 199 | public function handleUploadedImage($image) 200 | { 201 | if (!is_null($image)) { 202 | $image->move(public_path('images') . 'temp'); 203 | } 204 | } 205 | } 206 | ``` 207 | 208 | [🔝 Наверх](#Содержание) 209 | 210 | ### **Не повторяйся (DRY)** 211 | 212 | Этот принцип призывает вас переиспользовать код везде, где это возможно. Если вы следуете принципу SRP, вы уже избегаете повторений, но Laravel позволяет вам также переиспользовать представления, части Eloquent запросов и т.д. 213 | 214 | Плохо: 215 | 216 | ``` 217 | public function getActive() 218 | { 219 | return $this->where('verified', 1)->whereNotNull('deleted_at')->get(); 220 | } 221 | 222 | public function getArticles() 223 | { 224 | return $this->whereHas('user', function ($q) { 225 | $q->where('verified', 1)->whereNotNull('deleted_at'); 226 | })->get(); 227 | } 228 | ``` 229 | 230 | Хорошо: 231 | 232 | ``` 233 | public function scopeActive($q) 234 | { 235 | return $q->where('verified', 1)->whereNotNull('deleted_at'); 236 | } 237 | 238 | public function getActive() 239 | { 240 | return $this->active()->get(); 241 | } 242 | 243 | public function getArticles() 244 | { 245 | return $this->whereHas('user', function ($q) { 246 | $q->active(); 247 | })->get(); 248 | } 249 | ``` 250 | 251 | [🔝 Наверх](#Содержание) 252 | 253 | ### **Предпочитайте Eloquent конструктору запросов (query builder) и сырым запросам в БД. Предпочитайте работу с коллекциями работе с массивами** 254 | 255 | Eloquent позволяет писать максимально читаемый код, а изменять функционал приложения несоизмеримо легче. У Eloquent также есть ряд удобных и мощных инструментов. 256 | 257 | Плохо: 258 | 259 | ``` 260 | SELECT * 261 | FROM `articles` 262 | WHERE EXISTS (SELECT * 263 | FROM `users` 264 | WHERE `articles`.`user_id` = `users`.`id` 265 | AND EXISTS (SELECT * 266 | FROM `profiles` 267 | WHERE `profiles`.`user_id` = `users`.`id`) 268 | AND `users`.`deleted_at` IS NULL) 269 | AND `verified` = '1' 270 | AND `active` = '1' 271 | ORDER BY `created_at` DESC 272 | ``` 273 | 274 | Хорошо: 275 | 276 | ``` 277 | Article::has('user.profile')->verified()->latest()->get(); 278 | ``` 279 | 280 | [🔝 Наверх](#Содержание) 281 | 282 | ### **Используйте массовое заполнение (mass assignment)** 283 | 284 | Плохо: 285 | 286 | ``` 287 | $article = new Article; 288 | $article->title = $request->title; 289 | $article->content = $request->content; 290 | $article->verified = $request->verified; 291 | // Привязать статью к категории. 292 | $article->category_id = $category->id; 293 | $article->save(); 294 | ``` 295 | 296 | Хорошо: 297 | 298 | ``` 299 | $category->article()->create($request->all()); 300 | ``` 301 | 302 | [🔝 Наверх](#Содержание) 303 | 304 | ### **Не выполняйте запросы в представлениях и используйте нетерпеливую загрузку (проблема N + 1)** 305 | 306 | Плохо (будет выполнен 101 запрос в БД для 100 пользователей): 307 | 308 | ``` 309 | @foreach (User::all() as $user) 310 | {{ $user->profile->name }} 311 | @endforeach 312 | ``` 313 | 314 | Хорошо (будет выполнено 2 запроса в БД для 100 пользователей): 315 | 316 | ``` 317 | $users = User::with('profile')->get(); 318 | 319 | ... 320 | 321 | @foreach ($users as $user) 322 | {{ $user->profile->name }} 323 | @endforeach 324 | ``` 325 | 326 | [🔝 Наверх](#Содержание) 327 | 328 | ### **Комментируйте код, предпочитайте читаемые имена методов комментариям** 329 | 330 | Плохо: 331 | 332 | ``` 333 | if (count((array) $builder->getQuery()->joins) > 0) 334 | ``` 335 | 336 | Лучше: 337 | 338 | ``` 339 | // Determine if there are any joins. 340 | if (count((array) $builder->getQuery()->joins) > 0) 341 | ``` 342 | 343 | Хорошо: 344 | 345 | ``` 346 | if ($this->hasJoins()) 347 | ``` 348 | 349 | [🔝 Наверх](#Содержание) 350 | 351 | ### **Выносите JS и CSS из шаблонов Blade и HTML из PHP кода** 352 | 353 | Плохо: 354 | 355 | ``` 356 | let article = `{{ json_encode($article) }}`; 357 | ``` 358 | 359 | Лучше: 360 | 361 | ``` 362 | 363 | 364 | Или 365 | 366 |