├── 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 |  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 | {{ $article->name }} 399 | ``` 400 | 401 | In a Javascript file: 402 | 403 | ```javascript 404 | let article = $('#article').val(); 405 | ``` 406 | 407 | The best way is to use specialized PHP to JS package to transfer the data. 408 | 409 | [🔝 Back to contents](#contents) 410 | 411 | ### **Use config and language files, constants instead of text in the code** 412 | 413 | Bad: 414 | 415 | ```php 416 | public function isNormal() 417 | { 418 | return $article->type === 'normal'; 419 | } 420 | 421 | return back()->with('message', 'Your article has been added!'); 422 | ``` 423 | 424 | Good: 425 | 426 | ```php 427 | public function isNormal() 428 | { 429 | return $article->type === Article::TYPE_NORMAL; 430 | } 431 | 432 | return back()->with('message', __('app.article_added')); 433 | ``` 434 | 435 | [🔝 Back to contents](#contents) 436 | 437 | ### **Use standard Laravel tools accepted by community** 438 | 439 | Prefer to use built-in Laravel functionality and community packages instead of using 3rd party packages and tools. Any developer who will work with your app in the future will need to learn new tools. Also, chances to get help from the Laravel community are significantly lower when you're using a 3rd party package or tool. Do not make your client pay for that. 440 | 441 | Task | Standard tools | 3rd party tools 442 | ------------ | ------------- | ------------- 443 | Authorization | Policies | Entrust, Sentinel and other packages 444 | Compiling assets | Laravel Mix | Grunt, Gulp, 3rd party packages 445 | Development Environment | Homestead | Docker 446 | Deployment | Laravel Forge | Deployer and other solutions 447 | Unit testing | PHPUnit, Mockery | Phpspec 448 | Browser testing | Laravel Dusk | Codeception 449 | DB | Eloquent | SQL, Doctrine 450 | Templates | Blade | Twig 451 | Working with data | Laravel collections | Arrays 452 | Form validation | Request classes | 3rd party packages, validation in controller 453 | Authentication | Built-in | 3rd party packages, your own solution 454 | API authentication | Laravel Passport | 3rd party JWT and OAuth packages 455 | Creating API | Built-in | Dingo API and similar packages 456 | Working with DB structure | Migrations | Working with DB structure directly 457 | Localization | Built-in | 3rd party packages 458 | Realtime user interfaces | Laravel Echo, Pusher | 3rd party packages and working with WebSockets directly 459 | Generating testing data | Seeder classes, Model Factories, Faker | Creating testing data manually 460 | Task scheduling | Laravel Task Scheduler | Scripts and 3rd party packages 461 | DB | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB 462 | 463 | [🔝 Back to contents](#contents) 464 | 465 | ### **Follow Laravel naming conventions** 466 | 467 | Follow [PSR standards](http://www.php-fig.org/psr/psr-2/). 468 | 469 | Also, follow naming conventions accepted by Laravel community: 470 | 471 | What | How | Good | Bad 472 | ------------ | ------------- | ------------- | ------------- 473 | Controller | singular | ArticleController | ~~ArticlesController~~ 474 | Route | plural | articles/1 | ~~article/1~~ 475 | Named route | snake_case with dot notation | users.show_active | ~~users.show-active, show-active-users~~ 476 | Model | singular | User | ~~Users~~ 477 | hasOne or belongsTo relationship | singular | articleComment | ~~articleComments, article_comment~~ 478 | All other relationships | plural | articleComments | ~~articleComment, article_comments~~ 479 | Table | plural | article_comments | ~~article_comment, articleComments~~ 480 | Pivot table | singular model names in alphabetical order | article_user | ~~user_article, articles_users~~ 481 | Table column | snake_case without model name | meta_title | ~~MetaTitle; article_meta_title~~ 482 | Model property | snake_case | $model->created_at | ~~$model->createdAt~~ 483 | Foreign key | singular model name with _id suffix | article_id | ~~ArticleId, id_article, articles_id~~ 484 | Primary key | - | id | ~~custom_id~~ 485 | Migration | - | 2017_01_01_000000_create_articles_table | ~~2017_01_01_000000_articles~~ 486 | Method | camelCase | getAll | ~~get_all~~ 487 | Method in resource controller | [table](https://laravel.com/docs/master/controllers#resource-controllers) | store | ~~saveArticle~~ 488 | Method in test class | camelCase | testGuestCannotSeeArticle | ~~test_guest_cannot_see_article~~ 489 | Variable | camelCase | $articlesWithAuthor | ~~$articles_with_author~~ 490 | Collection | descriptive, plural | $activeUsers = User::active()->get() | ~~$active, $data~~ 491 | Object | descriptive, singular | $activeUser = User::active()->first() | ~~$users, $obj~~ 492 | Config and language files index | snake_case | articles_enabled | ~~ArticlesEnabled; articles-enabled~~ 493 | View | kebab-case | show-filtered.blade.php | ~~showFiltered.blade.php, show_filtered.blade.php~~ 494 | Config | snake_case | google_calendar.php | ~~googleCalendar.php, google-calendar.php~~ 495 | Contract (interface) | adjective or noun | Authenticatable | ~~AuthenticationInterface, IAuthentication~~ 496 | Trait | adjective | Notifiable | ~~NotificationTrait~~ 497 | 498 | [🔝 Back to contents](#contents) 499 | 500 | ### **Use shorter and more readable syntax where possible** 501 | 502 | Bad: 503 | 504 | ```php 505 | $request->session()->get('cart'); 506 | $request->input('name'); 507 | ``` 508 | 509 | Good: 510 | 511 | ```php 512 | session('cart'); 513 | $request->name; 514 | ``` 515 | 516 | More examples: 517 | 518 | Common syntax | Shorter and more readable syntax 519 | ------------ | ------------- 520 | `Session::get('cart')` | `session('cart')` 521 | `$request->session()->get('cart')` | `session('cart')` 522 | `Session::put('cart', $data)` | `session(['cart' => $data])` 523 | `$request->input('name'), Request::get('name')` | `$request->name, request('name')` 524 | `return Redirect::back()` | `return back()` 525 | `is_null($object->relation) ? null : $object->relation->id` | `optional($object->relation)->id` 526 | `return view('index')->with('title', $title)->with('client', $client)` | `return view('index', compact('title', 'client'))` 527 | `$request->has('value') ? $request->value : 'default';` | `$request->get('value', 'default')` 528 | `Carbon::now(), Carbon::today()` | `now(), today()` 529 | `App::make('Class')` | `app('Class')` 530 | `->where('column', '=', 1)` | `->where('column', 1)` 531 | `->orderBy('created_at', 'desc')` | `->latest()` 532 | `->orderBy('age', 'desc')` | `->latest('age')` 533 | `->orderBy('created_at', 'asc')` | `->oldest()` 534 | `->select('id', 'name')->get()` | `->get(['id', 'name'])` 535 | `->first()->name` | `->value('name')` 536 | 537 | [🔝 Back to contents](#contents) 538 | 539 | ### **Use IoC container or facades instead of new Class** 540 | 541 | new Class syntax creates tight coupling between classes and complicates testing. Use IoC container or facades instead. 542 | 543 | Bad: 544 | 545 | ```php 546 | $user = new User; 547 | $user->create($request->validated()); 548 | ``` 549 | 550 | Good: 551 | 552 | ```php 553 | public function __construct(User $user) 554 | { 555 | $this->user = $user; 556 | } 557 | 558 | .... 559 | 560 | $this->user->create($request->validated()); 561 | ``` 562 | 563 | [🔝 Back to contents](#contents) 564 | 565 | ### **Do not get data from the `.env` file directly** 566 | 567 | Pass the data to config files instead and then use the `config()` helper function to use the data in an application. 568 | 569 | Bad: 570 | 571 | ```php 572 | $apiKey = env('API_KEY'); 573 | ``` 574 | 575 | Good: 576 | 577 | ```php 578 | // config/api.php 579 | 'key' => env('API_KEY'), 580 | 581 | // Use the data 582 | $apiKey = config('api.key'); 583 | ``` 584 | 585 | [🔝 Back to contents](#contents) 586 | 587 | ### **Store dates in the standard format. Use accessors and mutators to modify date format** 588 | 589 | Bad: 590 | 591 | ```php 592 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }} 593 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }} 594 | ``` 595 | 596 | Good: 597 | 598 | ```php 599 | // Model 600 | protected $dates = ['ordered_at', 'created_at', 'updated_at']; 601 | public function getSomeDateAttribute($date) 602 | { 603 | return $date->format('m-d'); 604 | } 605 | 606 | // View 607 | {{ $object->ordered_at->toDateString() }} 608 | {{ $object->ordered_at->some_date }} 609 | ``` 610 | 611 | [🔝 Back to contents](#contents) 612 | 613 | ### **Other good practices** 614 | 615 | Never put any logic in routes files. 616 | 617 | Minimize usage of vanilla PHP in Blade templates. 618 | 619 | [🔝 Back to contents](#contents) 620 | -------------------------------------------------------------------------------- /chinese.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | 多国语言列表: 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 | [Русский](russian.md) 10 | 11 | [فارسی](persian.md) (by [amirhossein baghaie](https://github.com/amirbagh75)) 12 | 13 | [Português](https://github.com/jonaselan/laravel-best-practices) (by [jonaselan](https://github.com/jonaselan)) 14 | 15 | [Tiếng Việt](https://chungnguyen.xyz/posts/code-laravel-lam-sao-cho-chuan) (by [Chung Nguyễn](https://github.com/nguyentranchung)) 16 | 17 | [Español](spanish.md) (by [César Escudero](https://github.com/cedaesca)) 18 | 19 | [Français](french.md) (by [Mikayil S.](https://github.com/mikayilsrt)) 20 | 21 | [Polski](https://github.com/maciejjeziorski/laravel-best-practices-pl) (by [Maciej Jeziorski](https://github.com/maciejjeziorski)) 22 | 23 | [Türkçe](turkish.md) (by [Burak](https://github.com/ikidnapmyself)) 24 | 25 | [Deutsche](german.md) (by [Sujal Patel](https://github.com/sujalpatel2209)) 26 | 27 | [Italiana](italian.md) (by [Sujal Patel](https://github.com/sujalpatel2209)) 28 | 29 | 这并非laravel官方强制要求的规范,而是我们在日常开发过程中遇到的一些容易忽视的优秀实现方式。 30 | 31 | ## 内容 32 | 33 | [单一职责原则](#single-responsibility-principle) 34 | 35 | [保持控制器的简洁](#fat-models-skinny-controllers) 36 | 37 | [使用自定义Request类来进行验证](#validation) 38 | 39 | [业务代码要放到服务层中](#business-logic-should-be-in-service-class) 40 | 41 | [DRY原则 不要重复自己](#dont-repeat-yourself-dry) 42 | 43 | [使用ORM而不是纯sql语句,使用集合而不是数组](#prefer-to-use-eloquent-over-using-query-builder-and-raw-sql-queries-prefer-collections-over-arrays) 44 | 45 | [集中处理数据](#mass-assignment) 46 | 47 | [不要在模板中查询,尽量使用惰性加载](#do-not-execute-queries-in-blade-templates-and-use-eager-loading-n--1-problem) 48 | 49 | [注释你的代码,但是更优雅的做法是使用描述性的语言来编写你的代码](#comment-your-code-but-prefer-descriptive-method-and-variable-names-over-comments) 50 | 51 | [不要把 JS 和 CSS 放到 Blade 模板中,也不要把任何 HTML 代码放到 PHP 代码里](#do-not-put-js-and-css-in-blade-templates-and-do-not-put-any-html-in-php-classes) 52 | 53 | [在代码中使用配置、语言包和常量,而不是使用硬编码](#use-config-and-language-files-constants-instead-of-text-in-the-code) 54 | 55 | [使用社区认可的标准Laravel工具](#use-standard-laravel-tools-accepted-by-community) 56 | 57 | [遵循laravel命名约定](#follow-laravel-naming-conventions) 58 | 59 | [尽可能使用简短且可读性更好的语法](#use-shorter-and-more-readable-syntax-where-possible) 60 | 61 | [使用IOC容器来创建实例 而不是直接new一个实例](#use-ioc-container-or-facades-instead-of-new-class) 62 | 63 | [避免直接从 `.env` 文件里获取数据](#do-not-get-data-from-the-env-file-directly) 64 | 65 | [使用标准格式来存储日期,用访问器和修改器来修改日期格式](#store-dates-in-the-standard-format-use-accessors-and-mutators-to-modify-date-format) 66 | 67 | [其他的好建议](#other-good-practices) 68 | 69 | ### **单一职责原则** 70 | 71 | 一个类和一个方法应该只有一个责任。 72 | 73 | 例如: 74 | 75 | ```php 76 | public function getFullNameAttribute() 77 | { 78 | if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) { 79 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 80 | } else { 81 | return $this->first_name[0] . '. ' . $this->last_name; 82 | } 83 | } 84 | ``` 85 | 86 | 更优的写法: 87 | 88 | ```php 89 | public function getFullNameAttribute() 90 | { 91 | return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort(); 92 | } 93 | 94 | public function isVerifiedClient() 95 | { 96 | return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified(); 97 | } 98 | 99 | public function getFullNameLong() 100 | { 101 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 102 | } 103 | 104 | public function getFullNameShort() 105 | { 106 | return $this->first_name[0] . '. ' . $this->last_name; 107 | } 108 | ``` 109 | 110 | [🔝 返回目录](#contents) 111 | 112 | ### **保持控制器的简洁** 113 | 114 | 如果您使用的是查询生成器或原始SQL查询,请将所有与数据库相关的逻辑放入Eloquent模型或Repository类中。 115 | 116 | 例如: 117 | 118 | ```php 119 | public function index() 120 | { 121 | $clients = Client::verified() 122 | ->with(['orders' => function ($q) { 123 | $q->where('created_at', '>', Carbon::today()->subWeek()); 124 | }]) 125 | ->get(); 126 | 127 | return view('index', ['clients' => $clients]); 128 | } 129 | ``` 130 | 131 | 更优的写法: 132 | 133 | ```php 134 | public function index() 135 | { 136 | return view('index', ['clients' => $this->client->getWithNewOrders()]); 137 | } 138 | 139 | class Client extends Model 140 | { 141 | public function getWithNewOrders() 142 | { 143 | return $this->verified() 144 | ->with(['orders' => function ($q) { 145 | $q->where('created_at', '>', Carbon::today()->subWeek()); 146 | }]) 147 | ->get(); 148 | } 149 | } 150 | ``` 151 | 152 | [🔝 返回目录](#contents) 153 | 154 | ### **使用自定义Request类来进行验证** 155 | 156 | 把验证规则放到 Request 类中. 157 | 158 | 例子: 159 | 160 | ```php 161 | public function store(Request $request) 162 | { 163 | $request->validate([ 164 | 'title' => 'required|unique:posts|max:255', 165 | 'body' => 'required', 166 | 'publish_at' => 'nullable|date', 167 | ]); 168 | 169 | .... 170 | } 171 | ``` 172 | 173 | 更优的写法: 174 | 175 | ```php 176 | public function store(PostRequest $request) 177 | { 178 | .... 179 | } 180 | 181 | class PostRequest extends Request 182 | { 183 | public function rules() 184 | { 185 | return [ 186 | 'title' => 'required|unique:posts|max:255', 187 | 'body' => 'required', 188 | 'publish_at' => 'nullable|date', 189 | ]; 190 | } 191 | } 192 | ``` 193 | 194 | [🔝 返回目录](#contents) 195 | 196 | ### **业务代码要放到服务层中** 197 | 198 | 控制器必须遵循单一职责原则,因此最好将业务代码从控制器移动到服务层中。 199 | 200 | 例子: 201 | 202 | ```php 203 | public function store(Request $request) 204 | { 205 | if ($request->hasFile('image')) { 206 | $request->file('image')->move(public_path('images') . 'temp'); 207 | } 208 | 209 | .... 210 | } 211 | ``` 212 | 213 | 更优的写法: 214 | 215 | ```php 216 | public function store(Request $request) 217 | { 218 | $this->articleService->handleUploadedImage($request->file('image')); 219 | 220 | .... 221 | } 222 | 223 | class ArticleService 224 | { 225 | public function handleUploadedImage($image) 226 | { 227 | if (!is_null($image)) { 228 | $image->move(public_path('images') . 'temp'); 229 | } 230 | } 231 | } 232 | ``` 233 | 234 | [🔝 返回目录](#contents) 235 | 236 | ### **DRY原则 不要重复自己** 237 | 238 | 尽可能重用代码,SRP可以帮助您避免重复造轮子。 此外尽量重复使用Blade模板,使用Eloquent的 scopes 方法来实现代码。 239 | 240 | 例子: 241 | 242 | ```php 243 | public function getActive() 244 | { 245 | return $this->where('verified', 1)->whereNotNull('deleted_at')->get(); 246 | } 247 | 248 | public function getArticles() 249 | { 250 | return $this->whereHas('user', function ($q) { 251 | $q->where('verified', 1)->whereNotNull('deleted_at'); 252 | })->get(); 253 | } 254 | ``` 255 | 256 | 更优的写法: 257 | 258 | ```php 259 | public function scopeActive($q) 260 | { 261 | return $q->where('verified', 1)->whereNotNull('deleted_at'); 262 | } 263 | 264 | public function getActive() 265 | { 266 | return $this->active()->get(); 267 | } 268 | 269 | public function getArticles() 270 | { 271 | return $this->whereHas('user', function ($q) { 272 | $q->active(); 273 | })->get(); 274 | } 275 | ``` 276 | 277 | [🔝 返回目录](#contents) 278 | 279 | ### **使用ORM而不是纯sql语句,使用集合而不是数组** 280 | 281 | 使用Eloquent可以帮您编写可读和可维护的代码。 此外Eloquent还有非常优雅的内置工具,如软删除,事件,范围等。 282 | 283 | 例子: 284 | 285 | ```sql 286 | SELECT * 287 | FROM `articles` 288 | WHERE EXISTS (SELECT * 289 | FROM `users` 290 | WHERE `articles`.`user_id` = `users`.`id` 291 | AND EXISTS (SELECT * 292 | FROM `profiles` 293 | WHERE `profiles`.`user_id` = `users`.`id`) 294 | AND `users`.`deleted_at` IS NULL) 295 | AND `verified` = '1' 296 | AND `active` = '1' 297 | ORDER BY `created_at` DESC 298 | ``` 299 | 300 | 更优的写法: 301 | 302 | ```php 303 | Article::has('user.profile')->verified()->latest()->get(); 304 | ``` 305 | 306 | [🔝 返回目录](#contents) 307 | 308 | ### **集中处理数据** 309 | 310 | 例子: 311 | 312 | ```php 313 | $article = new Article; 314 | $article->title = $request->title; 315 | $article->content = $request->content; 316 | $article->verified = $request->verified; 317 | // Add category to article 318 | $article->category_id = $category->id; 319 | $article->save(); 320 | ``` 321 | 322 | 更优的写法: 323 | 324 | ```php 325 | $category->article()->create($request->validated()); 326 | ``` 327 | 328 | [🔝 返回目录](#contents) 329 | 330 | ### **不要在模板中查询,尽量使用惰性加载** 331 | 332 | 例子 (对于100个用户,将执行101次DB查询): 333 | 334 | ```php 335 | @foreach (User::all() as $user) 336 | {{ $user->profile->name }} 337 | @endforeach 338 | ``` 339 | 340 | 更优的写法 (对于100个用户,使用以下写法只需执行2次DB查询): 341 | 342 | ```php 343 | $users = User::with('profile')->get(); 344 | 345 | ... 346 | 347 | @foreach ($users as $user) 348 | {{ $user->profile->name }} 349 | @endforeach 350 | ``` 351 | 352 | [🔝 返回目录](#contents) 353 | 354 | ### **注释你的代码,但是更优雅的做法是使用描述性的语言来编写你的代码** 355 | 356 | 例子: 357 | 358 | ```php 359 | if (count((array) $builder->getQuery()->joins) > 0) 360 | ``` 361 | 362 | 加上注释: 363 | 364 | ```php 365 | // 确定是否有任何连接 366 | if (count((array) $builder->getQuery()->joins) > 0) 367 | ``` 368 | 369 | 更优的写法: 370 | 371 | ```php 372 | if ($this->hasJoins()) 373 | ``` 374 | 375 | [🔝 返回目录](#contents) 376 | 377 | ### **不要把 JS 和 CSS 放到 Blade 模板中,也不要把任何 HTML 代码放到 PHP 代码里** 378 | 379 | 例子: 380 | 381 | ```php 382 | let article = `{{ json_encode($article) }}`; 383 | ``` 384 | 385 | 更好的写法: 386 | 387 | ```php 388 | 389 | 390 | Or 391 | 392 | {{ $article->name }} 393 | ``` 394 | 395 | 在Javascript文件中加上: 396 | 397 | ```javascript 398 | let article = $('#article').val(); 399 | ``` 400 | 401 | 当然最好的办法还是使用专业的PHP的JS包传输数据。 402 | 403 | [🔝 返回目录](#contents) 404 | 405 | ### **在代码中使用配置、语言包和常量,而不是使用硬编码** 406 | 407 | 例子: 408 | 409 | ```php 410 | public function isNormal() 411 | { 412 | return $article->type === 'normal'; 413 | } 414 | 415 | return back()->with('message', 'Your article has been added!'); 416 | ``` 417 | 418 | 更优的写法: 419 | 420 | ```php 421 | public function isNormal() 422 | { 423 | return $article->type === Article::TYPE_NORMAL; 424 | } 425 | 426 | return back()->with('message', __('app.article_added')); 427 | ``` 428 | 429 | [🔝 返回目录](#contents) 430 | 431 | ### **使用社区认可的标准Laravel工具** 432 | 433 | 434 | 强力推荐使用内置的Laravel功能和扩展包,而不是使用第三方的扩展包和工具。 435 | 如果你的项目被其他开发人员接手了,他们将不得不重新学习这些第三方工具的使用教程。 436 | 此外,当您使用第三方扩展包或工具时,你很难从Laravel社区获得什么帮助。 不要让你的客户为额外的问题付钱。 437 | 438 | 想要实现的功能 | 标准工具 | 第三方工具 439 | ------------ | ------------- | ------------- 440 | 权限 | Policies | Entrust, Sentinel 或者其他扩展包 441 | 资源编译工具| Laravel Mix | Grunt, Gulp, 或者其他第三方包 442 | 开发环境| Homestead | Docker 443 | 部署 | Laravel Forge | Deployer 或者其他解决方案 444 | 自动化测试 | PHPUnit, Mockery | Phpspec 445 | 页面预览测试 | Laravel Dusk | Codeception 446 | DB操纵 | Eloquent | SQL, Doctrine 447 | 模板 | Blade | Twig 448 | 数据操纵 | Laravel集合 | 数组 449 | 表单验证| Request classes | 他第三方包,甚至在控制器中做验证 450 | 权限 | Built-in | 他第三方包或者你自己解决 451 | API身份验证 | Laravel Passport | 第三方的JWT或者 OAuth 扩展包 452 | 创建 API | Built-in | Dingo API 或者类似的扩展包 453 | 创建数据库结构 | Migrations | 直接用 DB 语句创建 454 | 本土化 | Built-in |第三方包 455 | 实时消息队列 | Laravel Echo, Pusher | 使用第三方包或者直接使用WebSockets 456 | 创建测试数据| Seeder classes, Model Factories, Faker | 手动创建测试数据 457 | 任务调度| Laravel Task Scheduler | 脚本和第三方包 458 | 数据库 | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB 459 | 460 | [🔝 返回目录](#contents) 461 | 462 | ### **遵循laravel命名约定** 463 | 464 | 来源 [PSR standards](http://www.php-fig.org/psr/psr-2/). 465 | 466 | 另外,遵循Laravel社区认可的命名约定: 467 | 468 | 对象 | 规则 | 更优的写法 | 应避免的写法 469 | ------------ | ------------- | ------------- | ------------- 470 | 控制器 | 单数 | ArticleController | ~~ArticlesController~~ 471 | 路由 | 复数 | articles/1 | ~~article/1~~ 472 | 路由命名| 带点符号的蛇形命名 | users.show_active | ~~users.show-active, show-active-users~~ 473 | 模型 | 单数 | User | ~~Users~~ 474 | hasOne或belongsTo关系 | 单数 | articleComment | ~~articleComments, article_comment~~ 475 | 所有其他关系 | 复数 | articleComments | ~~articleComment, article_comments~~ 476 | 表单 | 复数 | article_comments | ~~article_comment, articleComments~~ 477 | 透视表| 按字母顺序排列模型 | article_user | ~~user_article, articles_users~~ 478 | 数据表字段| 使用蛇形并且不要带表名 | meta_title | ~~MetaTitle; article_meta_title~~ 479 | 模型参数 | 蛇形命名 | $model->created_at | ~~$model->createdAt~~ 480 | 外键 | 带有_id后缀的单数模型名称 | article_id | ~~ArticleId, id_article, articles_id~~ 481 | 主键 | - | id | ~~custom_id~~ 482 | 迁移 | - | 2017_01_01_000000_create_articles_table | ~~2017_01_01_000000_articles~~ 483 | 方法 | 驼峰命名 | getAll | ~~get_all~~ 484 | 资源控制器 | [table](https://laravel.com/docs/master/controllers#resource-controllers) | store | ~~saveArticle~~ 485 | 测试类| 驼峰命名 | testGuestCannotSeeArticle | ~~test_guest_cannot_see_article~~ 486 | 变量 | 驼峰命名 | $articlesWithAuthor | ~~$articles_with_author~~ 487 | 集合 | 描述性的, 复数的 | $activeUsers = User::active()->get() | ~~$active, $data~~ 488 | 对象 | 描述性的, 单数的 | $activeUser = User::active()->first() | ~~$users, $obj~~ 489 | 配置和语言文件索引 | 蛇形命名 | articles_enabled | ~~ArticlesEnabled; articles-enabled~~ 490 | 视图 | 短横线命名 | show-filtered.blade.php | ~~showFiltered.blade.php, show_filtered.blade.php~~ 491 | 配置 | 蛇形命名 | google_calendar.php | ~~googleCalendar.php, google-calendar.php~~ 492 | 内容 (interface) | 形容词或名词 | Authenticatable | ~~AuthenticationInterface, IAuthentication~~ 493 | Trait | 使用形容词 | Notifiable | ~~NotificationTrait~~ 494 | 495 | [🔝 返回目录](#contents) 496 | 497 | ### **尽可能使用简短且可读性更好的语法** 498 | 499 | 例子: 500 | 501 | ```php 502 | $request->session()->get('cart'); 503 | $request->input('name'); 504 | ``` 505 | 506 | 更优的写法: 507 | 508 | ```php 509 | session('cart'); 510 | $request->name; 511 | ``` 512 | 513 | 更多示例: 514 | 515 | 常规写法 | 更优雅的写法 516 | ------------ | ------------- 517 | `Session::get('cart')` | `session('cart')` 518 | `$request->session()->get('cart')` | `session('cart')` 519 | `Session::put('cart', $data)` | `session(['cart' => $data])` 520 | `$request->input('name'), Request::get('name')` | `$request->name, request('name')` 521 | `return Redirect::back()` | `return back()` 522 | `is_null($object->relation) ? null : $object->relation->id` | `optional($object->relation)->id` 523 | `return view('index')->with('title', $title)->with('client', $client)` | `return view('index', compact('title', 'client'))` 524 | `$request->has('value') ? $request->value : 'default';` | `$request->get('value', 'default')` 525 | `Carbon::now(), Carbon::today()` | `now(), today()` 526 | `App::make('Class')` | `app('Class')` 527 | `->where('column', '=', 1)` | `->where('column', 1)` 528 | `->orderBy('created_at', 'desc')` | `->latest()` 529 | `->orderBy('age', 'desc')` | `->latest('age')` 530 | `->orderBy('created_at', 'asc')` | `->oldest()` 531 | `->select('id', 'name')->get()` | `->get(['id', 'name'])` 532 | `->first()->name` | `->value('name')` 533 | 534 | [🔝 返回目录](#contents) 535 | 536 | ### **使用IOC容器来创建实例 而不是直接new一个实例** 537 | 538 | 创建新的类会让类之间的更加耦合,使得测试越发复杂。请改用IoC容器或注入来实现。 539 | 540 | 例子: 541 | 542 | ```php 543 | $user = new User; 544 | $user->create($request->validated()); 545 | ``` 546 | 547 | 更优的写法: 548 | 549 | ```php 550 | public function __construct(User $user) 551 | { 552 | $this->user = $user; 553 | } 554 | 555 | .... 556 | 557 | $this->user->create($request->validated()); 558 | ``` 559 | 560 | [🔝 返回目录](#contents) 561 | 562 | ### **避免直接从 `.env` 文件里获取数据** 563 | 564 | 将数据传递给配置文件,然后使用`config()`帮助函数来调用数据 565 | 566 | 例子: 567 | 568 | ```php 569 | $apiKey = env('API_KEY'); 570 | ``` 571 | 572 | 更优的写法: 573 | 574 | ```php 575 | // config/api.php 576 | 'key' => env('API_KEY'), 577 | 578 | // Use the data 579 | $apiKey = config('api.key'); 580 | ``` 581 | 582 | [🔝 返回目录](#contents) 583 | 584 | ### **使用标准格式来存储日期,用访问器和修改器来修改日期格式** 585 | 586 | 例子: 587 | 588 | ```php 589 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }} 590 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }} 591 | ``` 592 | 593 | 更优的写法: 594 | 595 | ```php 596 | // Model 597 | protected $dates = ['ordered_at', 'created_at', 'updated_at']; 598 | public function getSomeDateAttribute($date) 599 | { 600 | return $date->format('m-d'); 601 | } 602 | 603 | // View 604 | {{ $object->ordered_at->toDateString() }} 605 | {{ $object->ordered_at->some_date }} 606 | ``` 607 | 608 | [🔝 返回目录](#contents) 609 | 610 | ### **其他的一些好建议** 611 | 612 | 永远不要在路由文件中放任何的逻辑代码。 613 | 614 | 尽量不要在Blade模板中写原始 PHP 代码。 615 | 616 | [🔝 返回目录](#contents) 617 | 618 | 619 | -------------------------------------------------------------------------------- /french.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | Traductions: 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 | Ce n'est pas une adaptation Laravel des principes SOLID, des modèles, etc. Vous trouverez ici les meilleures pratiques qui sont généralement ignorées dans les projets réels de Laravel. 36 | 37 | ## Contenu 38 | 39 | [Principe de responsabilité unique](#principe-de-responsabilité-unique) 40 | 41 | [Modèles Fat, contrôleurs maigres](#modèles-Fat-contrôleurs-maigres) 42 | 43 | [Validation](#validation) 44 | 45 | [La logique métier doit être en classe de service](#la-logique-métier-doit-être-en-classe-de-service) 46 | 47 | [Ne te répète pas (DRY)](#ne-te-répète-pas-dry) 48 | 49 | [Préférez utiliser Eloquent à l’utilisation de Query Builder et de requêtes SQL brutes. Préférez les collections aux tableaux](#préférez-utiliser-eloquent-à-l-utilisation-de-Query-Builder-et-de-requêtes-SQL-brutes-Préférez-les-collections-aux-tableaux) 50 | 51 | [Mission de masse](#mission-de-masse) 52 | 53 | [N'exécutez pas de requêtes dans les modèles de blade et utilisez un chargement rapide (N + 1 problème)](#n-exécutez-pas-de-requêtes-dans-les-modèles-de-blade-et-utilisez-un-chargement-rapide-n--1-problem) 54 | 55 | [Commentez votre code, mais préférez la méthode descriptive et les noms de variables aux commentaires](#commentez-votre-code-mais-préférez-la-méthode-descriptive-et-les-noms-de-variables-aux-commentaires) 56 | 57 | [Ne mettez pas JS et CSS dans les templates Blade et ne mettez pas de HTML dans les classes PHP](#ne-mettez-pas-JS-et-CSS-dans-les-templates-Blade-et-ne-mettez-pas-de-HTML-dans-les-classes-PHP) 58 | 59 | [Utilisez des fichiers de configuration et de langue, des constantes au lieu du texte dans le code](#utilisez-des-fichiers-de-configuration-et-de-langue-des-constantes-au-lieu-du-texte-dans-le-code) 60 | 61 | [Utiliser les outils standard de Laravel acceptés par la communauté](#utiliser-les-outils-standard-de-Laravel-acceptés-par-la-communauté) 62 | 63 | [Suivre les conventions de nommage de Laravel](#suivre-les-conventions-de-nommage-de-Laravel) 64 | 65 | [Utilisez une syntaxe plus courte et plus lisible dans la mesure du possible](#utilisez-une-syntaxe-plus-courte-et-plus-lisible-dans-la-mesure-du-possible) 66 | 67 | [Utilisez un conteneur IoC ou des façades au lieu de la nouvelle classe](#utilisez-un-conteneur-IoC-ou-des-façades-au-lieu-de-la-nouvelle-classe) 68 | 69 | [Ne pas obtenir les données du fichier `.env` directement](#ne-pas-obtenir-les-données-du-fichier-env-directement) 70 | 71 | [Stocker les dates au format standard. Utiliser des accesseurs et des mutateurs pour modifier le format de date](#stocker-les-dates-au-format-standard-Utiliser-des-accesseurs-et-des-mutateurs-pour-modifier-le-format-de-date) 72 | 73 | [Autres bonnes pratiques](#autres-bonnes-pratiques) 74 | 75 | ### **Principe de responsabilité unique** 76 | 77 | Une classe et une méthode ne devraient avoir qu'une seule responsabilité. 78 | 79 | Mal: 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 | Bien: 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 | [🔝 Retour au contenu](#contents) 117 | 118 | ### **Gros modèles, maigres contrôleurs** 119 | 120 | Placez toute la logique liée à la base de données dans les modèles Eloquent ou dans les classes du référentiel si vous utilisez le générateur de requêtes ou des requêtes SQL brutes. 121 | 122 | Mal: 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 | Bien: 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 | [🔝 Retour au contenu](#contents) 159 | 160 | ### **Validation** 161 | 162 | Déplacer la validation des contrôleurs vers les classes Request. 163 | 164 | Mal: 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 | Bien: 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 | [🔝 Retour au contenu](#contents) 201 | 202 | ### **La logique métier doit être dans une classe de service** 203 | 204 | Un contrôleur ne doit avoir qu'une seule responsabilité. Par conséquent, déplacez la logique métier des contrôleurs vers les classes de service. 205 | 206 | Mal: 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 | Bien: 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 | [🔝 Retour au contenu](#contents) 241 | 242 | ### **Ne te répète pas (DRY)** 243 | 244 | Réutilisez le code quand vous le pouvez. SRP vous aide à éviter les doubles emplois. Réutilisez également les modèles de lame, utilisez les étendues Eloquent, etc. 245 | 246 | Mal: 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 | Bien: 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 | [🔝 Retour au contenu](#contents) 284 | 285 | ### **Préférez utiliser Eloquent à l’utilisation de Query Builder et de requêtes SQL brutes. Préférez les collections aux tableaux** 286 | 287 | Eloquent vous permet d’écrire du code lisible et maintenable. Eloquent dispose également d'excellents outils intégrés tels que les suppressions, les événements, les étendues, etc. 288 | 289 | Mal: 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 | Bien: 307 | 308 | ```php 309 | Article::has('user.profile')->verified()->latest()->get(); 310 | ``` 311 | 312 | [🔝 Retour au contenu](#contents) 313 | 314 | ### **Mission de masse** 315 | 316 | Mal: 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 | Bien: 329 | 330 | ```php 331 | $category->article()->create($request->validated()); 332 | ``` 333 | 334 | [🔝 Retour au contenu](#contents) 335 | 336 | ### **N'exécutez pas de requêtes dans les modèles de lames et utilisez un chargement rapide (N + 1 problem)** 337 | 338 | Mal (Pour 100 utilisateur, 101 requêtes DB seront exécutées): 339 | 340 | ```php 341 | @foreach (User::all() as $user) 342 | {{ $user->profile->name }} 343 | @endforeach 344 | ``` 345 | 346 | Bien (pour 100 utilisateurs, 2 requêtes de base de données seront exécutées): 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 | [🔝 Retour au contenu](#contents) 359 | 360 | ### **Commentez votre code, mais préférez la méthode descriptive et les noms de variables aux commentaires** 361 | 362 | Mal: 363 | 364 | ```php 365 | if (count((array) $builder->getQuery()->joins) > 0) 366 | ``` 367 | 368 | Meilleure: 369 | 370 | ```php 371 | // Determine if there are any joins. 372 | if (count((array) $builder->getQuery()->joins) > 0) 373 | ``` 374 | 375 | Bien: 376 | 377 | ```php 378 | if ($this->hasJoins()) 379 | ``` 380 | 381 | [🔝 Retour au contenu](#contents) 382 | 383 | ### **Ne mettez pas JS et CSS dans les templates Blade et ne mettez pas de HTML dans les classes PHP** 384 | 385 | Mal: 386 | 387 | ```php 388 | let article = `{{ json_encode($article) }}`; 389 | ``` 390 | 391 | Meilleure: 392 | 393 | ```php 394 | 395 | 396 | Or 397 | 398 | {{ $article->name }} 399 | ``` 400 | 401 | Dans un fichier Javascript: 402 | 403 | ```javascript 404 | let article = $('#article').val(); 405 | ``` 406 | 407 | Le meilleur moyen consiste à utiliser un package PHP vers JS spécialisé pour transférer les données. 408 | 409 | [🔝 Retour au contenu](#contents) 410 | 411 | ### **Utilisez des fichiers de configuration et de langue, des constantes au lieu du texte dans le code** 412 | 413 | Mal: 414 | 415 | ```php 416 | public function isNormal() 417 | { 418 | return $article->type === 'normal'; 419 | } 420 | 421 | return back()->with('message', 'Your article has been added!'); 422 | ``` 423 | 424 | Bien: 425 | 426 | ```php 427 | public function isNormal() 428 | { 429 | return $article->type === Article::TYPE_NORMAL; 430 | } 431 | 432 | return back()->with('message', __('app.article_added')); 433 | ``` 434 | 435 | [🔝 Retour au contenu](#contents) 436 | 437 | ### **Utiliser les outils standard de Laravel acceptés par la communauté** 438 | 439 | Préférez utiliser les fonctionnalités intégrées de Laravel et les packages de communauté au lieu d'utiliser des packages et des outils tiers. Tout développeur qui travaillera avec votre application à l'avenir devra apprendre de nouveaux outils. En outre, les chances d'obtenir de l'aide de la communauté Laravel sont considérablement réduites lorsque vous utilisez un package ou un outil tiers. Ne faites pas payer votre client pour cela. 440 | 441 | Tâche | Outils standard | Outils tiers 442 | ------------ | ------------- | ------------- 443 | Autorisation | Policies | Entrust, Sentinel et d'autres packages 444 | Compiler des assets | Laravel Mix | Grunt, Gulp, packages tiers 445 | Environnement de développement | Homestead | Docker 446 | Déploiement | Laravel Forge | Deployer et d'autre solutions 447 | Tests unitaires | PHPUnit, Mockery | Phpspec 448 | Test du navigateur | Laravel Dusk | Codeception 449 | DB | Eloquent | SQL, Doctrine 450 | Templates | Blade | Twig 451 | Travailler avec des données | Laravel collections | Arrays 452 | Validation du formulaire | Request classes | 3rd party packages, validation dans le contrôleur 453 | Authentification | Built-in | 3rd party packages, votre propre solution 454 | API D'authentification | Laravel Passport | 3rd party JWT et OAuth packages 455 | Création d'API | Built-in | Dingo API and similar packages 456 | Travailler avec une structure de base de données | Migrations | Travailler directement avec la structure de la base de données 457 | Localisation | Built-in | 3rd party packages 458 | Interfaces utilisateur en temps réel | Laravel Echo, Pusher | Packages tiers et utilisation directe de WebSockets 459 | Générer des données de test | Seeder classes, Model Factories, Faker | Création manuelle de données de test 460 | Planification des tâches | Laravel Task Scheduler | Scripts et packages tiers 461 | DB | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB 462 | 463 | [🔝 Retour au contenu](#contents) 464 | 465 | ### **Suivre les conventions de nommage de Laravel** 466 | 467 | Suivre [Normes PSR](http://www.php-fig.org/psr/psr-2/). 468 | 469 | Suivez également les conventions de nommage acceptées par la communauté Laravel: 470 | 471 | Quoi | Comment | Bien | Mal 472 | ------------ | ------------- | ------------- | ------------- 473 | Controller | singulière | ArticleController | ~~ArticlesController~~ 474 | Route | plurielle | articles/1 | ~~article/1~~ 475 | Route nommé | snake_case avec notation par points | users.show_active | ~~users.show-active, show-active-users~~ 476 | Model | singulière | User | ~~Users~~ 477 | hasOne or belongsTo relationship | singulière | articleComment | ~~articleComments, article_comment~~ 478 | All other relationships | plurielle | articleComments | ~~articleComment, article_comments~~ 479 | Table | plurielle | article_comments | ~~article_comment, articleComments~~ 480 | Pivot table | singulière model names in alphabetical order | article_user | ~~user_article, articles_users~~ 481 | Table column | snake_case sans nom de modèle | meta_title | ~~MetaTitle; article_meta_title~~ 482 | Model property | snake_case | $model->created_at | ~~$model->createdAt~~ 483 | Foreign key | Nom du modèle au singulier avec _id comme suffix | article_id | ~~ArticleId, id_article, articles_id~~ 484 | Primary key | - | id | ~~custom_id~~ 485 | Migration | - | 2017_01_01_000000_create_articles_table | ~~2017_01_01_000000_articles~~ 486 | Method | camelCase | getAll | ~~get_all~~ 487 | Method in resource controller | [table](https://laravel.com/docs/master/controllers#resource-controllers) | store | ~~saveArticle~~ 488 | Method in test class | camelCase | testGuestCannotSeeArticle | ~~test_guest_cannot_see_article~~ 489 | Variable | camelCase | $articlesWithAuthor | ~~$articles_with_author~~ 490 | Collection | descriptive, plurielle | $activeUsers = User::active()->get() | ~~$active, $data~~ 491 | Object | descriptive, singulière | $activeUser = User::active()->first() | ~~$users, $obj~~ 492 | Config and language files index | snake_case | articles_enabled | ~~ArticlesEnabled; articles-enabled~~ 493 | Vue | kebab-case | show-filtered.blade.php | ~~showFiltered.blade.php, show_filtered.blade.php~~ 494 | Config | snake_case | google_calendar.php | ~~googleCalendar.php, google-calendar.php~~ 495 | Contract (interface) | adjectif ou nom | Authenticatable | ~~AuthenticationInterface, IAuthentication~~ 496 | Traite | adjective | Notifiable | ~~NotificationTrait~~ 497 | 498 | [🔝 Retour au contenu](#contents) 499 | 500 | ### **Utilisez une syntaxe plus courte et plus lisible dans la mesure du possible** 501 | 502 | Mal: 503 | 504 | ```php 505 | $request->session()->get('cart'); 506 | $request->input('name'); 507 | ``` 508 | 509 | Bien: 510 | 511 | ```php 512 | session('cart'); 513 | $request->name; 514 | ``` 515 | 516 | Plus d'exemples: 517 | 518 | Syntaxe commune | Syntaxe plus courte et plus lisible 519 | ------------ | ------------- 520 | `Session::get('cart')` | `session('cart')` 521 | `$request->session()->get('cart')` | `session('cart')` 522 | `Session::put('cart', $data)` | `session(['cart' => $data])` 523 | `$request->input('name'), Request::get('name')` | `$request->name, request('name')` 524 | `return Redirect::back()` | `return back()` 525 | `is_null($object->relation) ? null : $object->relation->id` | `optional($object->relation)->id` 526 | `return view('index')->with('title', $title)->with('client', $client)` | `return view('index', compact('title', 'client'))` 527 | `$request->has('value') ? $request->value : 'default';` | `$request->get('value', 'default')` 528 | `Carbon::now(), Carbon::today()` | `now(), today()` 529 | `App::make('Class')` | `app('Class')` 530 | `->where('column', '=', 1)` | `->where('column', 1)` 531 | `->orderBy('created_at', 'desc')` | `->latest()` 532 | `->orderBy('age', 'desc')` | `->latest('age')` 533 | `->orderBy('created_at', 'asc')` | `->oldest()` 534 | `->select('id', 'name')->get()` | `->get(['id', 'name'])` 535 | `->first()->name` | `->value('name')` 536 | 537 | [🔝 Retour au contenu](#contents) 538 | 539 | ### **Utilisez un conteneur IoC ou des façades au lieu de la nouvelle classe** 540 | 541 | La nouvelle syntaxe de classe crée un couplage étroit entre les classes et complique les tests. Utilisez plutôt le conteneur IoC ou les façades. 542 | 543 | Mal: 544 | 545 | ```php 546 | $user = new User; 547 | $user->create($request->validated()); 548 | ``` 549 | 550 | Bien: 551 | 552 | ```php 553 | public function __construct(User $user) 554 | { 555 | $this->user = $user; 556 | } 557 | 558 | .... 559 | 560 | $this->user->create($request->validated()); 561 | ``` 562 | 563 | [🔝 Retour au contenu](#contents) 564 | 565 | ### **Ne pas obtenir les données du fichier `.env` directement** 566 | 567 | Passez les données aux fichiers de configuration à la place, puis utilisez la fonction d'assistance `config ()` pour utiliser les données dans une application. 568 | 569 | Mal: 570 | 571 | ```php 572 | $apiKey = env('API_KEY'); 573 | ``` 574 | 575 | Bien: 576 | 577 | ```php 578 | // config/api.php 579 | 'key' => env('API_KEY'), 580 | 581 | // Use the data 582 | $apiKey = config('api.key'); 583 | ``` 584 | 585 | [🔝 Retour au contenu](#contents) 586 | 587 | ### **Stocker les dates au format standard. Utiliser des accesseurs et des mutateurs pour modifier le format de date** 588 | 589 | Mal: 590 | 591 | ```php 592 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }} 593 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }} 594 | ``` 595 | 596 | Bien: 597 | 598 | ```php 599 | // Model 600 | protected $dates = ['ordered_at', 'created_at', 'updated_at']; 601 | public function getSomeDateAttribute($date) 602 | { 603 | return $date->format('m-d'); 604 | } 605 | 606 | // View 607 | {{ $object->ordered_at->toDateString() }} 608 | {{ $object->ordered_at->some_date }} 609 | ``` 610 | 611 | [🔝 Retour au contenu](#contents) 612 | 613 | ### **D'autres bonnes pratiques** 614 | 615 | Ne mettez jamais aucune logique dans les fichiers de routes. 616 | 617 | Minimisez l'utilisation de PHP vanilla dans les modèles de blade. 618 | 619 | [🔝 Retour au contenu](#contents) 620 | -------------------------------------------------------------------------------- /german.md: -------------------------------------------------------------------------------- 1 |  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 | Es handelt sich nicht um eine Laravel-Anpassung von SOLID-Prinzipien, Mustern usw. Hier finden Sie die Best Practices, die in echten Laravel-Projekten normalerweise ignoriert werden. 36 | 37 | ## Contents 38 | 39 | [Prinzip der Einzelverantwortung](#single-responsibility-principle) 40 | 41 | [Fette Models, dünne Controller](#fat-models-skinny-controllers) 42 | 43 | [Validierung](#validation) 44 | 45 | [Geschäftslogik sollte in der Serviceklasse sein](#business-logic-should-be-in-service-class) 46 | 47 | [Wiederhole dich nicht (DRY)](#dont-repeat-yourself-dry) 48 | 49 | [Ziehen Sie es vor, Eloquent anstelle von Query Builder und unformatierten SQL-Abfragen zu verwenden. Ziehen Sie Sammlungen Arrays vor](#prefer-to-use-eloquent-over-using-query-builder-and-raw-sql-queries-prefer-collections-over-arrays) 50 | 51 | [Massenzuordnung](#mass-assignment) 52 | 53 | [Führen Sie keine Abfragen in Blade-Vorlagen aus und verwenden Sie das eifrige Laden (N + 1-Problem).](#do-not-execute-queries-in-blade-templates-and-use-eager-loading-n--1-problem) 54 | 55 | [Kommentieren Sie Ihren Code, bevorzugen Sie jedoch beschreibende Methoden- und Variablennamen gegenüber Kommentaren](#comment-your-code-but-prefer-descriptive-method-and-variable-names-over-comments) 56 | 57 | [Setzen Sie JS und CSS nicht in Blade-Vorlagen und setzen Sie kein HTML in PHP-Klassen](#do-not-put-js-and-css-in-blade-templates-and-do-not-put-any-html-in-php-classes) 58 | 59 | [Verwenden Sie Konfigurations- und Sprachdateien, Konstanten anstelle von Text im Code 60 | ](#use-config-and-language-files-constants-instead-of-text-in-the-code) 61 | 62 | [Verwenden Sie Standard-Laravel-Tools, die von der Community akzeptiert werden 63 | ](#use-standard-laravel-tools-accepted-by-community) 64 | 65 | [Befolgen Sie die Namenskonventionen von Laravel](#follow-laravel-naming-conventions) 66 | 67 | [Verwenden Sie nach Möglichkeit eine kürzere und besser lesbare Syntax](#use-shorter-and-more-readable-syntax-where-possible) 68 | 69 | [Verwenden Sie IoC-Container oder -Fassaden anstelle der neuen Klasse](#use-ioc-container-or-facades-instead-of-new-class) 70 | 71 | [Rufen Sie keine Daten direkt aus der ENV-Datei ab](#do-not-get-data-from-the-env-file-directly) 72 | 73 | [Speichern Sie Daten im Standardformat. Verwenden Sie Accessoren und Mutatoren, um das Datumsformat zu ändern](#store-dates-in-the-standard-format-use-accessors-and-mutators-to-modify-date-format) 74 | 75 | [Andere gute Praktiken](#other-good-practices) 76 | 77 | ### **Prinzip der Einzelverantwortung** 78 | 79 | Eine Klasse und eine Methode sollten nur eine Verantwortung haben. 80 | 81 | Schlecht: 82 | 83 | ```php 84 | public function getFullNameAttribute() 85 | { 86 | if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) { 87 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 88 | } else { 89 | return $this->first_name[0] . '. ' . $this->last_name; 90 | } 91 | } 92 | ``` 93 | 94 | Gut: 95 | 96 | ```php 97 | public function getFullNameAttribute() 98 | { 99 | return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort(); 100 | } 101 | 102 | public function isVerifiedClient() 103 | { 104 | return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified(); 105 | } 106 | 107 | public function getFullNameLong() 108 | { 109 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 110 | } 111 | 112 | public function getFullNameShort() 113 | { 114 | return $this->first_name[0] . '. ' . $this->last_name; 115 | } 116 | ``` 117 | 118 | [🔝Zurück zum Inhalt](#contents) 119 | 120 | ### **Fette Models, dünne Controller** 121 | 122 | Fügen Sie die gesamte DB-bezogene Logik in Eloquent-Modelle oder in Repository-Klassen ein, wenn Sie Query Builder oder Raw SQL-Abfragen verwenden. 123 | 124 | Schlecht: 125 | 126 | ```php 127 | public function index() 128 | { 129 | $clients = Client::verified() 130 | ->with(['orders' => function ($q) { 131 | $q->where('created_at', '>', Carbon::today()->subWeek()); 132 | }]) 133 | ->get(); 134 | 135 | return view('index', ['clients' => $clients]); 136 | } 137 | ``` 138 | 139 | Gut: 140 | 141 | ```php 142 | public function index() 143 | { 144 | return view('index', ['clients' => $this->client->getWithNewOrders()]); 145 | } 146 | 147 | class Client extends Model 148 | { 149 | public function getWithNewOrders() 150 | { 151 | return $this->verified() 152 | ->with(['orders' => function ($q) { 153 | $q->where('created_at', '>', Carbon::today()->subWeek()); 154 | }]) 155 | ->get(); 156 | } 157 | } 158 | ``` 159 | 160 | [🔝Zurück zum Inhalt](#contents) 161 | 162 | ### **Validierung** 163 | 164 | Verschieben Sie die Validierung von Controllern in Request-Klassen. 165 | 166 | Schlecht: 167 | 168 | ```php 169 | public function store(Request $request) 170 | { 171 | $request->validate([ 172 | 'title' => 'required|unique:posts|max:255', 173 | 'body' => 'required', 174 | 'publish_at' => 'nullable|date', 175 | ]); 176 | 177 | .... 178 | } 179 | ``` 180 | 181 | Gut: 182 | 183 | ```php 184 | public function store(PostRequest $request) 185 | { 186 | .... 187 | } 188 | 189 | class PostRequest extends Request 190 | { 191 | public function rules() 192 | { 193 | return [ 194 | 'title' => 'required|unique:posts|max:255', 195 | 'body' => 'required', 196 | 'publish_at' => 'nullable|date', 197 | ]; 198 | } 199 | } 200 | ``` 201 | 202 | [🔝Zurück zum Inhalt](#contents) 203 | 204 | ### **Geschäftslogik sollte in der Serviceklasse sein** 205 | 206 | Ein Controller muss nur eine Verantwortung haben. Verschieben Sie die Geschäftslogik von Controllern in Serviceklassen. 207 | 208 | Schlecht: 209 | 210 | ```php 211 | public function store(Request $request) 212 | { 213 | if ($request->hasFile('image')) { 214 | $request->file('image')->move(public_path('images') . 'temp'); 215 | } 216 | 217 | .... 218 | } 219 | ``` 220 | 221 | Gut: 222 | 223 | ```php 224 | public function store(Request $request) 225 | { 226 | $this->articleService->handleUploadedImage($request->file('image')); 227 | 228 | .... 229 | } 230 | 231 | class ArticleService 232 | { 233 | public function handleUploadedImage($image) 234 | { 235 | if (!is_null($image)) { 236 | $image->move(public_path('images') . 'temp'); 237 | } 238 | } 239 | } 240 | ``` 241 | 242 | [🔝Zurück zum Inhalt](#contents) 243 | 244 | ### **Wiederhole dich nicht (DRY)** 245 | 246 | Code wiederverwenden, wenn Sie können. SRP hilft Ihnen, Doppelarbeit zu vermeiden. Verwenden Sie auch Blade-Vorlagen, eloquente Bereiche usw. 247 | 248 | Schlecht: 249 | 250 | ```php 251 | public function getActive() 252 | { 253 | return $this->where('verified', 1)->whereNotNull('deleted_at')->get(); 254 | } 255 | 256 | public function getArticles() 257 | { 258 | return $this->whereHas('user', function ($q) { 259 | $q->where('verified', 1)->whereNotNull('deleted_at'); 260 | })->get(); 261 | } 262 | ``` 263 | 264 | Gut: 265 | 266 | ```php 267 | public function scopeActive($q) 268 | { 269 | return $q->where('verified', 1)->whereNotNull('deleted_at'); 270 | } 271 | 272 | public function getActive() 273 | { 274 | return $this->active()->get(); 275 | } 276 | 277 | public function getArticles() 278 | { 279 | return $this->whereHas('user', function ($q) { 280 | $q->active(); 281 | })->get(); 282 | } 283 | ``` 284 | 285 | [🔝Zurück zum Inhalt](#contents) 286 | 287 | ### **Verwenden Sie lieber Eloquent als Query Builder und SQL-Rohdatenabfragen. Ziehen Sie Sammlungen Arrays vor** 288 | 289 | Mit Eloquent können Sie lesbaren und wartbaren Code schreiben. Außerdem verfügt Eloquent über großartige integrierte Tools wie Soft Deletes, Events, Scopes usw. 290 | 291 | Schlecht: 292 | 293 | ```sql 294 | SELECT * 295 | FROM `articles` 296 | WHERE EXISTS (SELECT * 297 | FROM `users` 298 | WHERE `articles`.`user_id` = `users`.`id` 299 | AND EXISTS (SELECT * 300 | FROM `profiles` 301 | WHERE `profiles`.`user_id` = `users`.`id`) 302 | AND `users`.`deleted_at` IS NULL) 303 | AND `verified` = '1' 304 | AND `active` = '1' 305 | ORDER BY `created_at` DESC 306 | ``` 307 | 308 | Gut: 309 | 310 | ```php 311 | Article::has('user.profile')->verified()->latest()->get(); 312 | ``` 313 | 314 | [🔝🔝Zurück zum Inhalt](#contents) 315 | 316 | ### **Massenzuordnung** 317 | 318 | Schlecht: 319 | 320 | ```php 321 | $article = new Article; 322 | $article->title = $request->title; 323 | $article->content = $request->content; 324 | $article->verified = $request->verified; 325 | // Add category to article 326 | $article->category_id = $category->id; 327 | $article->save(); 328 | ``` 329 | 330 | Gut: 331 | 332 | ```php 333 | $category->article()->create($request->validated()); 334 | ``` 335 | 336 | [🔝🔝Zurück zum Inhalt](#contents) 337 | 338 | ### **Führen Sie keine Abfragen in Blade-Vorlagen aus und verwenden Sie das eifrige Laden (N + 1-Problem).** 339 | 340 | Schlecht (for 100 users, 101 DB queries will be executed): 341 | 342 | ```php 343 | @foreach (User::all() as $user) 344 | {{ $user->profile->name }} 345 | @endforeach 346 | ``` 347 | 348 | Gut (for 100 users, 2 DB queries will be executed): 349 | 350 | ```php 351 | $users = User::with('profile')->get(); 352 | 353 | ... 354 | 355 | @foreach ($users as $user) 356 | {{ $user->profile->name }} 357 | @endforeach 358 | ``` 359 | 360 | [🔝🔝Zurück zum Inhalt](#contents) 361 | 362 | ### **Kommentieren Sie Ihren Code, bevorzugen Sie jedoch beschreibende Methoden- und Variablennamen gegenüber Kommentaren** 363 | 364 | Schlecht: 365 | 366 | ```php 367 | if (count((array) $builder->getQuery()->joins) > 0) 368 | ``` 369 | 370 | Better: 371 | 372 | ```php 373 | // Determine if there are any joins. 374 | if (count((array) $builder->getQuery()->joins) > 0) 375 | ``` 376 | 377 | Gut: 378 | 379 | ```php 380 | if ($this->hasJoins()) 381 | ``` 382 | 383 | [🔝🔝Zurück zum Inhalt](#contents) 384 | 385 | ### **Setzen Sie JS und CSS nicht in Blade-Vorlagen und setzen Sie kein HTML in PHP-Klassen** 386 | 387 | Schlecht: 388 | 389 | ```php 390 | let article = `{{ json_encode($article) }}`; 391 | ``` 392 | 393 | Besser: 394 | 395 | ```php 396 | 397 | 398 | Oder 399 | 400 | {{ $article->name }} 401 | ``` 402 | 403 | In einer Javascript-Datei: 404 | 405 | ```javascript 406 | let article = $('#article').val(); 407 | ``` 408 | 409 | Am besten verwenden Sie ein spezielles PHP-zu-JS-Paket, um die Daten zu übertragen. 410 | 411 | [🔝🔝Zurück zum Inhalt](#contents) 412 | 413 | ### **Verwenden Sie Konfigurations- und Sprachdateien, Konstanten anstelle von Text im Code** 414 | 415 | Schlecht: 416 | 417 | ```php 418 | public function isNormal() 419 | { 420 | return $article->type === 'normal'; 421 | } 422 | 423 | return back()->with('message', 'Your article has been added!'); 424 | ``` 425 | 426 | Gut: 427 | 428 | ```php 429 | public function isNormal() 430 | { 431 | return $article->type === Article::TYPE_NORMAL; 432 | } 433 | 434 | return back()->with('message', __('app.article_added')); 435 | ``` 436 | 437 | [🔝Zurück zum Inhalt](#contents) 438 | 439 | ### **Verwenden Sie Standard-Laravel-Tools, die von der Community akzeptiert werden** 440 | 441 | Verwenden Sie vorzugsweise integrierte Laravel-Funktionen und Community-Pakete, anstatt Pakete und Tools von Drittanbietern zu verwenden. Jeder Entwickler, der in Zukunft mit Ihrer App arbeitet, muss neue Tools erlernen. Außerdem sind die Chancen, Hilfe von der Laravel-Community zu erhalten, erheblich geringer, wenn Sie ein Paket oder Tool eines Drittanbieters verwenden. Lassen Sie Ihren Kunden nicht dafür bezahlen. 442 | 443 | Aufgabe | Standardwerkzeuge | Tools von Drittanbietern 444 | ------------ | ------------- | ------------- 445 | Genehmigung | Richtlinien | Entrust, Sentinel und andere Pakete 446 | Assets zusammenstellen Laravel Mix | Grunzen, Schlucken, 3rd-Party-Pakete 447 | Entwicklungsumgebung | Gehöft | Docker 448 | Bereitstellung | Laravel Forge | Deployer und andere Lösungen 449 | Einzelprüfung | PHPUnit, Spott | Phpspec 450 | Browsertests | Laravel Dämmerung | Codezeption 451 | DB | Eloquent | SQL, Lehre 452 | Vorlagen | Klinge | Zweig 453 | Mit Daten arbeiten | Laravel Sammlungen | Arrays 454 | Formularvalidierung | Klassen anfordern | Pakete von Drittanbietern, Validierung im Controller 455 | Authentifizierung | Eingebaut | Pakete von Drittanbietern, Ihre eigene Lösung 456 | API-Authentifizierung | Laravel Passport | JWT- und OAuth-Pakete von Drittanbietern 457 | API erstellen | Eingebaut | Dingo API und ähnliche Pakete 458 | Mit DB-Struktur arbeiten | Migrationen | Direkt mit der DB-Struktur arbeiten 459 | Lokalisierung | Eingebaut | Pakete von Drittanbietern 460 | Echtzeit-Benutzeroberflächen | Laravel Echo, Drücker | Pakete von Drittanbietern und direktes Arbeiten mit WebSockets 461 | Testdaten generieren | Sämaschinenklassen, Modellfabriken, Faker | Testdaten manuell erstellen 462 | Aufgabenplanung | Laravel Task Scheduler | Skripte und Pakete von Drittanbietern 463 | DB | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB 464 | 465 | [🔝Zurück zum Inhalt](#contents) 466 | 467 | ### **Befolgen Sie die Namenskonventionen von Laravel** 468 | 469 | Folgen [PSR standards](http://www.php-fig.org/psr/psr-2/). 470 | 471 | Befolgen Sie außerdem die von der Laravel-Community akzeptierten Namenskonventionen: 472 | 473 | Was | Wie | Gut | Schlecht 474 | ------------ | ------------- | ------------- | ------------- 475 | Controller | Singular | ArticleController | ~~ArticlesController~~ 476 | Route | plural | Artikel / 1 |~~Artikel/1~~ 477 | Benannte Route | snake_case mit Punktnotation | users.show_active |~~users.show-active, show-active-users~~ 478 | Modell | Singular | Benutzer |~~Benutzer~~ 479 | hasOne oder Zugehörigkeit zu einer Beziehung Singular | articleComment |~~articleComments, article_comment~~ 480 | Alle anderen Beziehungen plural | articleComments | ~~articleComment, article_comments~~ 481 | Tisch | plural | article_comments |~~article_comment, articleComments~~ 482 | Schwenktisch | Singuläre Modellnamen in alphabetischer Reihenfolge article_user |~~user_article, articles_users~~ 483 | Tabellenspalte | snake_case ohne Modellname | meta_title |~~MetTitle? article_title_title~~ 484 | Modelleigenschaft | snake_case | $ model-> created_at | ~~$ model-> createdAt~~ 485 | Fremdschlüssel | singulärer Modellname mit Suffix _id | article_id | ~~ArticleId, id_article, articles_id~~ 486 | Primärschlüssel | - | id | ~~custom_id~~ 487 | Migration | - | 2017_01_01_000000_create_articles_table | ~~2017_01_01_000000_articles~~ 488 | Methode | camelCase | getAll | ~~Nimm alle~~ 489 | Methode im Ressourcencontroller | [table](https://laravel.com/docs/master/controllers#resource-controllers) | Geschäft | ~~saveArticle~~ 490 | Methode in der Testklasse camelCase | testGuestCannotSeeArticle | ~~test_guest_cannot_see_article~~ 491 | Variable | camelCase | $ articlesWithAuthor | ~~$ articles_with_author~~ 492 | Sammlung | beschreibend, Plural | $ activeUsers = User :: active () -> get () | ~~$ active, $ data~~ 493 | Objekt | beschreibend, einzigartig $ activeUser = User :: active ()->first() | ~~$users, $obj~~ 494 | Konfigurations- und Sprachdateien index | snake_case | articles_enabled | ~~ArticlesEnabled; Artikel-fähig~~ 495 | Ansicht | Döner-Etui | show-filtered.blade.php | ~~showFiltered.blade.php, show_filtered.blade.php~~ 496 | Config | snake_case | google_calendar.php | ~~googleCalendar.php, google-calendar.php~~ 497 | Vertrag (Schnittstelle) | Adjektiv oder Substantiv Authentifizierbar | ~~AuthenticationInterface, IAuthentication~~ 498 | Merkmal | Adjektiv | Meldepflichtig | ~~NotificationTrait~~ 499 | 500 | [🔝🔝Zurück zum Inhalt](#contents) 501 | 502 | ### **Verwenden Sie nach Möglichkeit eine kürzere und besser lesbare Syntax** 503 | 504 | Schlecht: 505 | 506 | ```php 507 | $request->session()->get('cart'); 508 | $request->input('name'); 509 | ``` 510 | 511 | Gut: 512 | 513 | ```php 514 | session('cart'); 515 | $request->name; 516 | ``` 517 | 518 | Mehr Beispiele: 519 | 520 | Gemeinsame Syntax | Kürzere und lesbarere Syntax 521 | ------------ | ------------- 522 | `Session::get('cart')` | `session('cart')` 523 | `$request->session()->get('cart')` | `session('cart')` 524 | `Session::put('cart', $data)` | `session(['cart' => $data])` 525 | `$request->input('name'), Request::get('name')` | `$request->name, request('name')` 526 | `return Redirect::back()` | `return back()` 527 | `is_null($object->relation) ? null : $object->relation->id` | `optional($object->relation)->id` 528 | `return view('index')->with('title', $title)->with('client', $client)` | `return view('index', compact('title', 'client'))` 529 | `$request->has('value') ? $request->value : 'default';` | `$request->get('value', 'default')` 530 | `Carbon::now(), Carbon::today()` | `now(), today()` 531 | `App::make('Class')` | `app('Class')` 532 | `->where('column', '=', 1)` | `->where('column', 1)` 533 | `->orderBy('created_at', 'desc')` | `->latest()` 534 | `->orderBy('age', 'desc')` | `->latest('age')` 535 | `->orderBy('created_at', 'asc')` | `->oldest()` 536 | `->select('id', 'name')->get()` | `->get(['id', 'name'])` 537 | `->first()->name` | `->value('name')` 538 | 539 | [🔝🔝Zurück zum Inhalt](#contents) 540 | 541 | ### **Verwenden Sie IoC-Container oder -Fassaden anstelle der neuen Klasse** 542 | 543 | Die neue Klassensyntax sorgt für eine enge Kopplung zwischen den Klassen und erschwert das Testen. Verwenden Sie stattdessen einen IoC-Container oder Fassaden. 544 | 545 | Schlecht: 546 | 547 | ```php 548 | $user = new User; 549 | $user->create($request->validated()); 550 | ``` 551 | 552 | Gut: 553 | 554 | ```php 555 | public function __construct(User $user) 556 | { 557 | $this->user = $user; 558 | } 559 | 560 | .... 561 | 562 | $this->user->create($request->validated()); 563 | ``` 564 | 565 | [🔝🔝Zurück zum Inhalt](#contents) 566 | 567 | ### **Rufen Sie keine Daten direkt aus der ENV-Datei ab** 568 | 569 | Übergeben Sie die Daten stattdessen an Konfigurationsdateien und verwenden Sie dann die Hilfefunktion `config ()`, um die Daten in einer Anwendung zu verwenden. 570 | 571 | Schlecht: 572 | 573 | ```php 574 | $apiKey = env('API_KEY'); 575 | ``` 576 | 577 | Gut: 578 | 579 | ```php 580 | // config/api.php 581 | 'key' => env('API_KEY'), 582 | 583 | // Use the data 584 | $apiKey = config('api.key'); 585 | ``` 586 | 587 | [🔝Zurück zum Inhalt](#contents) 588 | 589 | ### **Speichern Sie Daten im Standardformat. Verwenden Sie Accessoren und Mutatoren, um das Datumsformat zu ändern** 590 | 591 | Schlecht: 592 | 593 | ```php 594 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }} 595 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }} 596 | ``` 597 | 598 | Gut: 599 | 600 | ```php 601 | // Model 602 | protected $dates = ['ordered_at', 'created_at', 'updated_at']; 603 | public function getSomeDateAttribute($date) 604 | { 605 | return $date->format('m-d'); 606 | } 607 | 608 | // View 609 | {{ $object->ordered_at->toDateString() }} 610 | {{ $object->ordered_at->some_date }} 611 | ``` 612 | 613 | [🔝Zurück zum Inhalt](#contents) 614 | 615 | ### **Andere gute Praktiken** 616 | 617 | Fügen Sie niemals Logik in Routendateien ein. 618 | 619 | Minimieren Sie die Verwendung von Vanille-PHP in Blade-Vorlagen. 620 | 621 | [🔝Zurück zum Inhalt](#contents) 622 | -------------------------------------------------------------------------------- /images/logo-chinese.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frank505/laravel-best-practices/52d439849da13ff62b2d30c9cdd33fd0ba6baf73/images/logo-chinese.png -------------------------------------------------------------------------------- /images/logo-english.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frank505/laravel-best-practices/52d439849da13ff62b2d30c9cdd33fd0ba6baf73/images/logo-english.png -------------------------------------------------------------------------------- /images/logo-french.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frank505/laravel-best-practices/52d439849da13ff62b2d30c9cdd33fd0ba6baf73/images/logo-french.png -------------------------------------------------------------------------------- /images/logo-japanese.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frank505/laravel-best-practices/52d439849da13ff62b2d30c9cdd33fd0ba6baf73/images/logo-japanese.png -------------------------------------------------------------------------------- /images/logo-persian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frank505/laravel-best-practices/52d439849da13ff62b2d30c9cdd33fd0ba6baf73/images/logo-persian.png -------------------------------------------------------------------------------- /images/logo-russian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frank505/laravel-best-practices/52d439849da13ff62b2d30c9cdd33fd0ba6baf73/images/logo-russian.png -------------------------------------------------------------------------------- /images/logo-spanish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frank505/laravel-best-practices/52d439849da13ff62b2d30c9cdd33fd0ba6baf73/images/logo-spanish.png -------------------------------------------------------------------------------- /images/logo-thai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frank505/laravel-best-practices/52d439849da13ff62b2d30c9cdd33fd0ba6baf73/images/logo-thai.png -------------------------------------------------------------------------------- /images/logo-turkish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frank505/laravel-best-practices/52d439849da13ff62b2d30c9cdd33fd0ba6baf73/images/logo-turkish.png -------------------------------------------------------------------------------- /italian.md: -------------------------------------------------------------------------------- 1 |  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 | Non è un adattamento Laravel di principi, schemi, ecc. SOLID. Qui troverai le migliori pratiche che di solito vengono ignorate nei progetti Laravel nella vita reale. 36 | 37 | ## Contenuto 38 | 39 | [Principio della sola responsabilità](#single-responsibility-principle) 40 | 41 | [Modelli grassi, controller skinny](#fat-models-skinny-controllers) 42 | 43 | [Validazione](#validation) 44 | 45 | [La logica aziendale dovrebbe essere nella classe di servizio](#business-logic-should-be-in-service-class) 46 | 47 | [Non ripeterti (SECCO)](#dont-repeat-yourself-dry) 48 | 49 | [Preferisco usare Eloquent rispetto a Query Builder e query SQL non elaborate. Preferisce raccolte su array](#prefer-to-use-eloquent-over-using-query-builder-and-raw-sql-queries-prefer-collections-over-arrays) 50 | 51 | [Assegnazione di massa](#mass-assignment) 52 | 53 | [Non eseguire query nei modelli Blade e utilizzare il caricamento desideroso (problema N + 1)](#do-not-execute-queries-in-blade-templates-and-use-eager-loading-n--1-problem) 54 | 55 | [Commenta il tuo codice, ma preferisci il metodo descrittivo e i nomi delle variabili rispetto ai commenti](#comment-your-code-but-prefer-descriptive-method-and-variable-names-over-comments) 56 | 57 | [Non inserire JS e CSS nei modelli Blade e non inserire HTML nelle classi PHP](#do-not-put-js-and-css-in-blade-templates-and-do-not-put-any-html-in-php-classes) 58 | 59 | [Usa file di configurazione e lingua, costanti anziché testo nel codice](#use-config-and-language-files-constants-instead-of-text-in-the-code) 60 | 61 | [Utilizzare gli strumenti standard Laravel accettati dalla community](#use-standard-laravel-tools-accepted-by-community) 62 | 63 | [Segui le convenzioni di denominazione di Laravel](#follow-laravel-naming-conventions) 64 | 65 | [Utilizzare la sintassi più breve e più leggibile ove possibile](#use-shorter-and-more-readable-syntax-where-possible) 66 | 67 | [Utilizzare il contenitore o le facciate IoC anziché la nuova classe](#use-ioc-container-or-facades-instead-of-new-class) 68 | 69 | [Non ottiene direttamente i dati dal file `.env`](#do-not-get-data-from-the-env-file-directly) 70 | 71 | [Memorizza le date nel formato standard. Utilizzare accessori e mutatori per modificare il formato della data](#store-dates-in-the-standard-format-use-accessors-and-mutators-to-modify-date-format) 72 | 73 | [Altre buone pratiche](#other-good-practices) 74 | 75 | ### **Principio della sola responsabilità** 76 | 77 | Una classe e un metodo dovrebbero avere una sola responsabilità. 78 | 79 | Male: 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 | Buono: 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 | [🔝Torna ai contenuti](#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 | Male: 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 | Buono: 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 | [Torna ai contenuti](#contents) 159 | 160 | ### **Validazione** 161 | 162 | Sposta la convalida dai controller alle classi di richiesta. 163 | 164 | Male: 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 | Buono: 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 | [Torna ai contenuti](#contents) 201 | 202 | ### **La logica aziendale dovrebbe essere nella classe di servizio** 203 | 204 | Un controller deve avere una sola responsabilità, quindi sposta la logica aziendale dai controller alle classi di servizio. 205 | 206 | Male: 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 | Buono: 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 | [Torna ai contenuti](#contents) 241 | 242 | ### **Non ripeterti (SECCO)** 243 | 244 | Riutilizzare il codice quando è possibile. SRP ti aiuta a evitare la duplicazione. Inoltre, riutilizza i modelli di blade, usa gli ambiti eloquenti ecc. 245 | 246 | Male: 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 | Buono: 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 | [Torna ai contenuti](#contents) 284 | 285 | ### **Preferisco usare Eloquent rispetto a Query Builder e query SQL non elaborate. Preferisce raccolte su array** 286 | 287 | Eloquent ti consente di scrivere codice leggibile e gestibile. Inoltre, Eloquent ha ottimi strumenti integrati come eliminazioni soft, eventi, ambiti ecc. 288 | 289 | Male: 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 | Buono: 307 | 308 | ```php 309 | Article::has('user.profile')->verified()->latest()->get(); 310 | ``` 311 | 312 | [Torna ai contenuti](#contents) 313 | 314 | ### **Assegnazione di massa** 315 | 316 | Male: 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 | Buono: 329 | 330 | ```php 331 | $category->article()->create($request->validated()); 332 | ``` 333 | 334 | [Torna ai contenuti](#contents) 335 | 336 | ### **Non eseguire query nei modelli Blade e utilizzare il caricamento desideroso (problema N + 1)** 337 | 338 | Male (fo 100 utenti, verranno eseguite 101 query DB): 339 | 340 | ```php 341 | @foreach (User::all() as $user) 342 | {{ $user->profile->name }} 343 | @endforeach 344 | ``` 345 | 346 | Buono (per 100 utenti, verranno eseguite 2 query DB): 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 | [Torna ai contenuti](#contents) 359 | 360 | ### **Commenta il tuo codice, ma preferisci il metodo descrittivo e i nomi delle variabili rispetto ai commenti** 361 | 362 | Male: 363 | 364 | ```php 365 | if (count((array) $builder->getQuery()->joins) > 0) 366 | ``` 367 | 368 | Meglio: 369 | 370 | ```php 371 | // Determine if there are any joins. 372 | if (count((array) $builder->getQuery()->joins) > 0) 373 | ``` 374 | 375 | Buono: 376 | 377 | ```php 378 | if ($this->hasJoins()) 379 | ``` 380 | 381 | [Torna ai contenuti](#contents) 382 | 383 | ### **Non inserire JS e CSS nei modelli Blade e non inserire HTML nelle classi PHP** 384 | 385 | Male: 386 | 387 | ```php 388 | let article = `{{ json_encode($article) }}`; 389 | ``` 390 | 391 | Meglio: 392 | 393 | ```php 394 | 395 | 396 | Or 397 | 398 | {{ $article->name }} 399 | ``` 400 | 401 | In un file Javascript: 402 | 403 | ```javascript 404 | let article = $('#article').val(); 405 | ``` 406 | 407 | Il modo migliore è utilizzare il pacchetto PHP-JS specializzato per trasferire i dati. 408 | 409 | [Torna ai contenuti](#contents) 410 | 411 | ### **Usa file di configurazione e lingua, costanti anziché testo nel codice** 412 | 413 | Male: 414 | 415 | ```php 416 | public function isNormal() 417 | { 418 | return $article->type === 'normal'; 419 | } 420 | 421 | return back()->with('message', 'Your article has been added!'); 422 | ``` 423 | 424 | Buono: 425 | 426 | ```php 427 | public function isNormal() 428 | { 429 | return $article->type === Article::TYPE_NORMAL; 430 | } 431 | 432 | return back()->with('message', __('app.article_added')); 433 | ``` 434 | 435 | [Torna ai contenuti](#contents) 436 | 437 | ### **Utilizzare gli strumenti standard Laravel accettati dalla community** 438 | 439 | Preferisci utilizzare la funzionalità Laravel integrata e i pacchetti della community anziché utilizzare pacchetti e strumenti di terze parti. Qualsiasi sviluppatore che lavorerà con la tua app in futuro dovrà imparare nuovi strumenti. Inoltre, le possibilità di ottenere aiuto dalla comunità Laravel sono significativamente inferiori quando si utilizza un pacchetto o uno strumento di terze parti. Non far pagare il tuo cliente per quello. 440 | 441 | Compito | Strumenti standard | Strumenti di terze parti 442 | ------------ | ------------- | ------------- 443 | Autorizzazione | Politiche | Affida, Sentinel e altri pacchetti 444 | Compiling assets | Laravel Mix | Grunt, Gulp, 3rd party packages 445 | Ambiente di sviluppo | Fattoria | docker 446 | Distribuzione | Laravel Forge | Deployer e altre soluzioni 447 | Test unitari | PHPUnit, Mockery | Phpspec 448 | Test del browser | Laravel Dusk | Codeception 449 | DB | Eloquente | SQL, Doctrine 450 | Modelli | Lama | Ramoscello 451 | Lavorare con i dati | Collezioni Laravel | Array 452 | Convalida del modulo | Richiedi classi | Pacchetti di terze parti, convalida nel controller 453 | Autenticazione | Incorporato | Pacchetti di terze parti, la tua soluzione 454 | Autenticazione API | Passaporto Laravel | Pacchetti JWT e OAuth di terze parti 455 | Creazione dell'API | Incorporato | API Dingo e pacchetti simili 456 | Lavorare con la struttura DB | Migrazioni | Lavorare direttamente con la struttura DB 457 | Localizzazione | Incorporato | Pacchetti di terze parti 458 | Interfacce utente in tempo reale | Laravel Echo, Pusher | Pacchetti di terze parti e funzionamento diretto con WebSocket 459 | Generazione di dati di test | Classi di seminatrice, Fabbriche modello, Faker | Creazione manuale dei dati di test 460 | Pianificazione delle attività | Utilità di pianificazione Laravel | Script e pacchetti di terze parti 461 | DB | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB 462 | 463 | [Torna ai contenuti](#contents) 464 | 465 | ### **Segui le convenzioni di denominazione di Laravel** 466 | 467 | Seguire [Standard PSR](http://www.php-fig.org/psr/psr-2/). 468 | 469 | Inoltre, segui le convenzioni di denominazione accettate dalla comunità Laravel: 470 | 471 | Cosa | Come | Buono | Male 472 | ------------ | ------------- | ------------- | ------------- 473 | Controller | singolare | Controllo articolo |~~ArticlesController~~ 474 | Rotta | plurale | articoli / 1 | ~~article/1~~ 475 | Percorso denominato | snake_case con notazione punto | users.show_active | ~~users.show-active, show-active-users~~ 476 | Modello | singolare | Utente | ~~Users~~ 477 | hasOne o appartiene alla relazione | singolare | articleComment |~~articleComments, article_comment~~ 478 | Tutte le altre relazioni | plurale | articleComments | ~~articleComment, article_comments~~ 479 | Tabella | plurale | commenti_articolo | ~~article_comment, articleComments~~ 480 | Tabella pivot | nomi di modelli singolari in ordine alfabetico | user_user | ~~user_article, articles_users~~ 481 | Colonna della tabella | snake_case senza nome modello | meta_title |~~Meta Title; articolo meta_title~~ 482 | Proprietà del modello | snake_case | $ model-> Created_at |~~$model->createdAt~~ 483 | Foreign key | singular model name with _id suffix | article_id | ~~ArticleId, id_article, articles_id~~ 484 | Chiave primaria | - | id |~~custom_id~~ 485 | Migrazione | - | 2017_01_01_000000_create_articles_table |~~2017_01_01_000000_articles~~ 486 | Metodo | camelCase | getAll | ~~get_all~~ 487 | Metodo nel controller delle risorse | [tavolo](https://laravel.com/docs/master/controllers#resource-controllers) | store | ~~saveArticle~~ 488 | Metodo nella classe di prova | camelCase | testGuestCannotSeeArticle |~~test_guest_cannot_see_article~~ 489 | Variabile | camelCase | $ articoliWithAuthor |~~$articles_with_author~~ 490 | Collezione | descrittivo, plurale | $ activeUsers = Utente :: active () -> get () | ~~$active, $data~~ 491 | Oggetto | descrittivo, singolare | $ activeUser = User :: active () -> first () | ~~$users, $obj~~ 492 | Indice file di configurazione e lingua | snake_case | articoli abilitati |~~ArticlesEnabled; articles-enabled~~ 493 | Visualizza | astuccio per kebab | show-filtered.blade.php | ~~showFiltered.blade.php, show_filtered.blade.php~~ 494 | Config | snake_case | google_calendar.php |~~googleCalendar.php, google-calendar.php~~ 495 | Contratto (interfaccia) | aggettivo o sostantivo | Autenticabile | ~~AuthenticationInterface, IAuthentication~~ 496 | Tratto | aggettivo | Notificabile | ~~NotificationTrait~~ 497 | 498 | [Torna ai contenuti](#contents) 499 | 500 | ### **Utilizzare la sintassi più breve e più leggibile ove possibile** 501 | 502 | Male: 503 | 504 | ```php 505 | $request->session()->get('cart'); 506 | $request->input('name'); 507 | ``` 508 | 509 | Buono: 510 | 511 | ```php 512 | session('cart'); 513 | $request->name; 514 | ``` 515 | 516 | Più esempi: 517 | 518 | Common syntax | Shorter and more readable syntax 519 | ------------ | ------------- 520 | `Session::get('cart')` | `session('cart')` 521 | `$request->session()->get('cart')` | `session('cart')` 522 | `Session::put('cart', $data)` | `session(['cart' => $data])` 523 | `$request->input('name'), Request::get('name')` | `$request->name, request('name')` 524 | `return Redirect::back()` | `return back()` 525 | `is_null($object->relation) ? null : $object->relation->id` | `optional($object->relation)->id` 526 | `return view('index')->with('title', $title)->with('client', $client)` | `return view('index', compact('title', 'client'))` 527 | `$request->has('value') ? $request->value : 'default';` | `$request->get('value', 'default')` 528 | `Carbon::now(), Carbon::today()` | `now(), today()` 529 | `App::make('Class')` | `app('Class')` 530 | `->where('column', '=', 1)` | `->where('column', 1)` 531 | `->orderBy('created_at', 'desc')` | `->latest()` 532 | `->orderBy('age', 'desc')` | `->latest('age')` 533 | `->orderBy('created_at', 'asc')` | `->oldest()` 534 | `->select('id', 'name')->get()` | `->get(['id', 'name'])` 535 | `->first()->name` | `->value('name')` 536 | 537 | [Torna ai contenuti](#contents) 538 | 539 | ### **Utilizzare il contenitore o le facciate IoC anziché la nuova classe** 540 | 541 | la nuova sintassi della classe crea un accoppiamento stretto tra le classi e complica i test. Utilizzare invece il contenitore o le facciate IoC. 542 | 543 | Male: 544 | 545 | ```php 546 | $user = new User; 547 | $user->create($request->validated()); 548 | ``` 549 | 550 | Buono: 551 | 552 | ```php 553 | public function __construct(User $user) 554 | { 555 | $this->user = $user; 556 | } 557 | 558 | .... 559 | 560 | $this->user->create($request->validated()); 561 | ``` 562 | 563 | [Torna ai contenuti](#contents) 564 | 565 | ### **Non ottiene direttamente i dati dal file `.env`** 566 | 567 | Passa invece i dati ai file di configurazione e quindi usa la funzione di supporto `config ()` per usare i dati in un'applicazione. 568 | 569 | Male: 570 | 571 | ```php 572 | $apiKey = env('API_KEY'); 573 | ``` 574 | 575 | Buono: 576 | 577 | ```php 578 | // config/api.php 579 | 'key' => env('API_KEY'), 580 | 581 | // Use the data 582 | $apiKey = config('api.key'); 583 | ``` 584 | 585 | [Torna ai contenuti](#contents) 586 | 587 | ### **Memorizza le date nel formato standard. Utilizzare accessori e mutatori per modificare il formato della data** 588 | 589 | Male: 590 | 591 | ```php 592 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }} 593 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }} 594 | ``` 595 | 596 | Buono: 597 | 598 | ```php 599 | // Model 600 | protected $dates = ['ordered_at', 'created_at', 'updated_at']; 601 | public function getSomeDateAttribute($date) 602 | { 603 | return $date->format('m-d'); 604 | } 605 | 606 | // View 607 | {{ $object->ordered_at->toDateString() }} 608 | {{ $object->ordered_at->some_date }} 609 | ``` 610 | 611 | [Torna ai contenuti](#contents) 612 | 613 | ### **Altre buone pratiche** 614 | 615 | Non inserire mai alcuna logica nei file di route. 616 | 617 | Ridurre al minimo l'utilizzo di PHP vaniglia nei modelli Blade. 618 | 619 | [Torna ai contenuti](#contents) 620 | -------------------------------------------------------------------------------- /japanese.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | 翻訳: 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 | [Русский](russian.md) 10 | 11 | [فارسی](persian.md) (by [amirhossein baghaie](https://github.com/amirbagh75)) 12 | 13 | [Português](https://github.com/jonaselan/laravel-best-practices) (by [jonaselan](https://github.com/jonaselan)) 14 | 15 | [Tiếng Việt](https://chungnguyen.xyz/posts/code-laravel-lam-sao-cho-chuan) (by [Chung Nguyễn](https://github.com/nguyentranchung)) 16 | 17 | [Español](spanish.md) (by [César Escudero](https://github.com/cedaesca)) 18 | 19 | [Français](french.md) (by [Mikayil S.](https://github.com/mikayilsrt)) 20 | 21 | [Polski](https://github.com/maciejjeziorski/laravel-best-practices-pl) (by [Maciej Jeziorski](https://github.com/maciejjeziorski)) 22 | 23 | [Türkçe](turkish.md) (by [Burak](https://github.com/ikidnapmyself)) 24 | 25 | [Deutsche](german.md) (by [Sujal Patel](https://github.com/sujalpatel2209)) 26 | 27 | [Italiana](italian.md) (by [Sujal Patel](https://github.com/sujalpatel2209)) 28 | 29 | これはSOLID原則やパターンなどをLavavelに適用させたものではありません。 30 | ここでは、実際のLaravelプロジェクトでは通常無視されるベストプラクティスを見つけることができます。 31 | 32 | ## コンテンツ 33 | 34 | [単一責任の原則](#単一責任の原則) 35 | 36 | [ファットモデル, スキニーコントローラ](#ファットモデル、スキニーコントローラ) 37 | 38 | [バリデーション](#バリデーション) 39 | 40 | [ビジネスロジックはサービスクラスの中に書く](#ビジネスロジックはサービスクラスの中に書く) 41 | 42 | [繰り返し書かない (DRY)](#繰り返し書かない-(DRY)) 43 | 44 | [クエリビルダや生のSQLクエリよりもEloquentを優先して使い、配列よりもコレクションを優先する](#クエリビルダや生のSQLクエリよりもEloquentを優先して使い、配列よりもコレクションを優先する) 45 | 46 | [マスアサインメント](#マスアサインメント) 47 | 48 | [Bladeテンプレート内でクエリを実行しない。Eager Lodingを使う(N + 1問題)](#Bladeテンプレート内でクエリを実行しない。Eager-Lodingを使う(N-+-1問題)) 49 | 50 | [コメントを書く。ただしコメントよりも説明的なメソッド名と変数名を付けるほうが良い](#コメントを書く。ただしコメントよりも説明的なメソッド名と変数名を付けるほうが良い) 51 | 52 | [JSとCSSをBladeテンプレートの中に入れない、PHPクラスの中にHTMLを入れない](#JSとCSSをBladeテンプレートの中に入れない、PHPクラスの中にHTMLを入れない) 53 | 54 | [コード内の文字列の代わりにconfigファイルとlanguageのファイル、定数を使う](#コード内の文字列の代わりにconfigファイルとlanguageのファイル、定数を使う) 55 | 56 | [コミュニティに受け入れられた標準のLaravelツールを使う](#コミュニティに受け入れられた標準のLaravelツールを使う) 57 | 58 | [Laravelの命名規則に従う](#Laravelの命名規則に従う) 59 | 60 | [できるだけ短く読みやすい構文で書く](#できるだけ短く読みやすい構文で書く) 61 | 62 | [newの代わりにIoCコンテナもしくはファサードを使う](#newの代わりにIoCコンテナもしくはファサードを使う) 63 | 64 | [`.env`ファイルのデータを直接参照しない](#`.env`ファイルのデータを直接参照しない) 65 | 66 | [日付を標準フォーマットで保存する。アクセサとミューテータを使って日付フォーマットを変更する](#日付を標準フォーマットで保存する。アクセサとミューテータを使って日付フォーマットを変更する) 67 | 68 | [その他 グッドプラクティス](#その他-グッドプラクティス) 69 | 70 | ### **単一責任の原則** 71 | 72 | クラスとメソッドは1つの責任だけを持つべきです。 73 | 74 | Bad: 75 | 76 | ```php 77 | public function getFullNameAttribute() 78 | { 79 | if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) { 80 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 81 | } else { 82 | return $this->first_name[0] . '. ' . $this->last_name; 83 | } 84 | } 85 | ``` 86 | 87 | Good: 88 | 89 | ```php 90 | public function getFullNameAttribute() 91 | { 92 | return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort(); 93 | } 94 | 95 | public function isVerifiedClient() 96 | { 97 | return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified(); 98 | } 99 | 100 | public function getFullNameLong() 101 | { 102 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 103 | } 104 | 105 | public function getFullNameShort() 106 | { 107 | return $this->first_name[0] . '. ' . $this->last_name; 108 | } 109 | ``` 110 | 111 | [🔝 コンテンツに戻る](#コンテンツ) 112 | 113 | ### **ファットモデル、スキニーコントローラ** 114 | 115 | DBに関連するすべてのロジックはEloquentモデルに入れるか、もしクエリビルダもしくは生のSQLクエリを使用する場合はレポジトリークラスに入れます。 116 | 117 | Bad: 118 | 119 | ```php 120 | public function index() 121 | { 122 | $clients = Client::verified() 123 | ->with(['orders' => function ($q) { 124 | $q->where('created_at', '>', Carbon::today()->subWeek()); 125 | }]) 126 | ->get(); 127 | 128 | return view('index', ['clients' => $clients]); 129 | } 130 | ``` 131 | 132 | Good: 133 | 134 | ```php 135 | public function index() 136 | { 137 | return view('index', ['clients' => $this->client->getWithNewOrders()]); 138 | } 139 | 140 | class Client extends Model 141 | { 142 | public function getWithNewOrders() 143 | { 144 | return $this->verified() 145 | ->with(['orders' => function ($q) { 146 | $q->where('created_at', '>', Carbon::today()->subWeek()); 147 | }]) 148 | ->get(); 149 | } 150 | } 151 | ``` 152 | 153 | [🔝 コンテンツに戻る](#コンテンツ) 154 | 155 | ### **バリデーション** 156 | 157 | バリデーションはコントローラからリクエストクラスに移動させます。 158 | 159 | Bad: 160 | 161 | ```php 162 | public function store(Request $request) 163 | { 164 | $request->validate([ 165 | 'title' => 'required|unique:posts|max:255', 166 | 'body' => 'required', 167 | 'publish_at' => 'nullable|date', 168 | ]); 169 | 170 | .... 171 | } 172 | ``` 173 | 174 | Good: 175 | 176 | ```php 177 | public function store(PostRequest $request) 178 | { 179 | .... 180 | } 181 | 182 | class PostRequest extends Request 183 | { 184 | public function rules() 185 | { 186 | return [ 187 | 'title' => 'required|unique:posts|max:255', 188 | 'body' => 'required', 189 | 'publish_at' => 'nullable|date', 190 | ]; 191 | } 192 | } 193 | ``` 194 | 195 | [🔝 コンテンツに戻る](#コンテンツ) 196 | 197 | ### **ビジネスロジックはサービスクラスの中に書く** 198 | 199 | コントローラはただ1つの責任だけを持たないといけません、そのためビジネスロジックはコントローラからサービスクラスに移動させます。 200 | 201 | Bad: 202 | 203 | ```php 204 | public function store(Request $request) 205 | { 206 | if ($request->hasFile('image')) { 207 | $request->file('image')->move(public_path('images') . 'temp'); 208 | } 209 | 210 | .... 211 | } 212 | ``` 213 | 214 | Good: 215 | 216 | ```php 217 | public function store(Request $request) 218 | { 219 | $this->articleService->handleUploadedImage($request->file('image')); 220 | 221 | .... 222 | } 223 | 224 | class ArticleService 225 | { 226 | public function handleUploadedImage($image) 227 | { 228 | if (!is_null($image)) { 229 | $image->move(public_path('images') . 'temp'); 230 | } 231 | } 232 | } 233 | ``` 234 | 235 | [🔝 コンテンツに戻る](#コンテンツ) 236 | 237 | ### **繰り返し書かない (DRY)** 238 | 239 | 可能であればコードを再利用します。単一責任の原則は重複を避けることに役立ちます。また、Bladeテンプレートを再利用したり、Eloquentのスコープなどを使用したりします。 240 | 241 | Bad: 242 | 243 | ```php 244 | public function getActive() 245 | { 246 | return $this->where('verified', 1)->whereNotNull('deleted_at')->get(); 247 | } 248 | 249 | public function getArticles() 250 | { 251 | return $this->whereHas('user', function ($q) { 252 | $q->where('verified', 1)->whereNotNull('deleted_at'); 253 | })->get(); 254 | } 255 | ``` 256 | 257 | Good: 258 | 259 | ```php 260 | public function scopeActive($q) 261 | { 262 | return $q->where('verified', 1)->whereNotNull('deleted_at'); 263 | } 264 | 265 | public function getActive() 266 | { 267 | return $this->active()->get(); 268 | } 269 | 270 | public function getArticles() 271 | { 272 | return $this->whereHas('user', function ($q) { 273 | $q->active(); 274 | })->get(); 275 | } 276 | ``` 277 | 278 | [🔝 コンテンツに戻る](#コンテンツ) 279 | 280 | ### **クエリビルダや生のSQLクエリよりもEloquentを優先して使い、配列よりもコレクションを優先する** 281 | 282 | Eloquentにより読みやすくメンテナンスしやすいコードを書くことができます。また、Eloquentには論理削除、イベント、スコープなどの優れた組み込みツールがあります。 283 | 284 | Bad: 285 | 286 | ```sql 287 | SELECT * 288 | FROM `articles` 289 | WHERE EXISTS (SELECT * 290 | FROM `users` 291 | WHERE `articles`.`user_id` = `users`.`id` 292 | AND EXISTS (SELECT * 293 | FROM `profiles` 294 | WHERE `profiles`.`user_id` = `users`.`id`) 295 | AND `users`.`deleted_at` IS NULL) 296 | AND `verified` = '1' 297 | AND `active` = '1' 298 | ORDER BY `created_at` DESC 299 | ``` 300 | 301 | Good: 302 | 303 | ```php 304 | Article::has('user.profile')->verified()->latest()->get(); 305 | ``` 306 | 307 | [🔝 コンテンツに戻る](#コンテンツ) 308 | 309 | ### **マスアサインメント** 310 | 311 | Bad: 312 | 313 | ```php 314 | $article = new Article; 315 | $article->title = $request->title; 316 | $article->content = $request->content; 317 | $article->verified = $request->verified; 318 | // Add category to article 319 | $article->category_id = $category->id; 320 | $article->save(); 321 | ``` 322 | 323 | Good: 324 | 325 | ```php 326 | $category->article()->create($request->validated()); 327 | ``` 328 | 329 | [🔝 コンテンツに戻る](#コンテンツ) 330 | 331 | ### **Bladeテンプレート内でクエリを実行しない。Eager Lodingを使う(N + 1問題)** 332 | 333 | Bad (100ユーザに対して、101回のDBクエリが実行される): 334 | 335 | ```php 336 | @foreach (User::all() as $user) 337 | {{ $user->profile->name }} 338 | @endforeach 339 | ``` 340 | 341 | Good (100ユーザに対して、2回のDBクエリが実行される): 342 | 343 | ```php 344 | $users = User::with('profile')->get(); 345 | 346 | ... 347 | 348 | @foreach ($users as $user) 349 | {{ $user->profile->name }} 350 | @endforeach 351 | ``` 352 | 353 | [🔝 コンテンツに戻る](#コンテンツ) 354 | 355 | ### **コメントを書く。ただしコメントよりも説明的なメソッド名と変数名を付けるほうが良い** 356 | 357 | Bad: 358 | 359 | ```php 360 | if (count((array) $builder->getQuery()->joins) > 0) 361 | ``` 362 | 363 | Better: 364 | 365 | ```php 366 | // Determine if there are any joins. 367 | if (count((array) $builder->getQuery()->joins) > 0) 368 | ``` 369 | 370 | Good: 371 | 372 | ```php 373 | if ($this->hasJoins()) 374 | ``` 375 | 376 | [🔝 コンテンツに戻る](#コンテンツ) 377 | 378 | ### **JSとCSSをBladeテンプレートの中に入れない、PHPクラスの中にHTMLを入れない** 379 | 380 | Bad: 381 | 382 | ```php 383 | let article = `{{ json_encode($article) }}`; 384 | ``` 385 | 386 | Better: 387 | 388 | ```php 389 | 390 | 391 | Or 392 | 393 | {{ $article->name }} 394 | ``` 395 | 396 | JavaScript ファイルで以下のように記述します: 397 | 398 | ```javascript 399 | let article = $('#article').val(); 400 | ``` 401 | 402 | もっとも良い方法は、データを転送するためJSパッケージに特別なPHPを使用することです。 403 | 404 | 405 | [🔝 コンテンツに戻る](#コンテンツ) 406 | 407 | ### **コード内の文字列の代わりにconfigファイルとlanguageのファイル、定数を使う** 408 | 409 | Bad: 410 | 411 | ```php 412 | public function isNormal() 413 | { 414 | return $article->type === 'normal'; 415 | } 416 | 417 | return back()->with('message', 'Your article has been added!'); 418 | ``` 419 | 420 | Good: 421 | 422 | ```php 423 | public function isNormal() 424 | { 425 | return $article->type === Article::TYPE_NORMAL; 426 | } 427 | 428 | return back()->with('message', __('app.article_added')); 429 | ``` 430 | 431 | [🔝 コンテンツに戻る](#コンテンツ) 432 | 433 | ### **コミュニティに受け入れられた標準のLaravelツールを使う** 434 | 435 | サードパーティ製のパッケージやツールの代わりに、Laravel標準機能とコミュニティパッケージを使うことを推奨します。将来あなたと共に働くことになるどの開発者も新しいツールを学習する必要があります。また、サードパーティ製のパッケージやツールを使用している場合は、Laravelコミュニティから助けを得る機会が大幅に少なくなります。あなたのクライアントにその代金を払わせないでください。 436 | 437 | タスク | 標準ツール | サードパーティ製ツール 438 | ------------ | ------------- | ------------- 439 | 認可 | Policies | Entrust, Sentinel または他のパッケージ 440 | アセットコンパイル | Laravel Mix | Grunt, Gulp, サードパーティ製パッケージ 441 | 開発環境 | Homestead | Docker 442 | デプロイ | Laravel Forge | Deployer またはその他ソリューション 443 | 単体テスト| PHPUnit, Mockery | Phpspec 444 | ブラウザテスト | Laravel Dusk | Codeception 445 | DB | Eloquent | SQL, Doctrine 446 | テンプレート | Blade | Twig 447 | データの取り扱い | Laravel collections | Arrays 448 | フォームバリデーション | Request classes | サードパーティ製パッケージ、コントローラ内でバリデーション 449 | 認証 | 標準組み込み | サードパーティ製パッケージ、独自実装 450 | API 認証 | Laravel Passport | サードパーティ製の JWT や OAuth パッケージ 451 | API作成 | 標準組み込み | Dingo API や類似パッケージ 452 | DB構造の取り扱い | Migrations | 直接DB構造を扱う 453 | ローカライゼーション | 標準組み込み | サードパーティ製パッケージ 454 | リアルタイムユーザインターフェース | Laravel Echo, Pusher | サードパーティ製パッケージ または直接Webソケットを扱う 455 | テストデータ生成 | Seeder classes, Model Factories, Faker | 手動でテストデータを作成 456 | タスクスケジューリング | Laravel Task Scheduler | スクリプトやサードパーティ製パッケージ 457 | DB | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB 458 | 459 | [🔝 コンテンツに戻る](#コンテンツ) 460 | 461 | ### **Laravelの命名規則に従う** 462 | 463 | [PSR](http://www.php-fig.org/psr/psr-2/)に従います。 464 | 465 | また、Laravelコミュニティに受け入れられた命名規則に従います。 466 | 467 | 対象 | 規則 | Good | Bad 468 | ------------ | ------------- | ------------- | ------------- 469 | コントローラ | 単数形 | ArticleController | ~~ArticlesController~~ 470 | ルート | 複数形 | articles/1 | ~~article/1~~ 471 | 名前付きルート | スネークケースとドット表記 | users.show_active | ~~users.show-active, show-active-users~~ 472 | モデル | 単数形 | User | ~~Users~~ 473 | hasOne または belongsTo 関係 | 単数形 | articleComment | ~~articleComments, article_comment~~ 474 | その他すべての関係 | 複数形 | articleComments | ~~articleComment, article_comments~~ 475 | テーブル | 複数形 | article_comments | ~~article_comment, articleComments~~ 476 | Pivotテーブル | 単数形 モデル名のアルファベット順 | article_user | ~~user_article, articles_users~~ 477 | テーブルカラム | スネークケース モデル名は含めない | meta_title | ~~MetaTitle; article_meta_title~~ 478 | モデルプロパティ | スネークケース | $model->created_at | ~~$model->createdAt~~ 479 | 外部キー | 単数形 モデル名の最後に_idをつける | article_id | ~~ArticleId, id_article, articles_id~~ 480 | 主キー | - | id | ~~custom_id~~ 481 | マイグレーション | - | 2017_01_01_000000_create_articles_table | ~~2017_01_01_000000_articles~~ 482 | メソッド | キャメルケース | getAll | ~~get_all~~ 483 | リソースコントローラのメソッド | [一覧](https://laravel.com/docs/master/controllers#resource-controllers) | store | ~~saveArticle~~ 484 | テストクラスのメソッド | キャメルケース | testGuestCannotSeeArticle | ~~test_guest_cannot_see_article~~ 485 | 変数 | キャメルケース | $articlesWithAuthor | ~~$articles_with_author~~ 486 | コレクション | 説明的、 複数形 | $activeUsers = User::active()->get() | ~~$active, $data~~ 487 | オブジェクト | 説明的, 単数形 | $activeUser = User::active()->first() | ~~$users, $obj~~ 488 | 設定ファイルと言語ファイルのインデックス | スネークケース | articles_enabled | ~~ArticlesEnabled; articles-enabled~~ 489 | ビュー | ケバブケース | show-filtered.blade.php | ~~showFiltered.blade.php, show_filtered.blade.php~~ 490 | コンフィグ | スネークケース | google_calendar.php | ~~googleCalendar.php, google-calendar.php~~ 491 | 契約 (インターフェイス) | 形容詞または名詞 | Authenticatable | ~~AuthenticationInterface, IAuthentication~~ 492 | Trait | 形容詞 | Notifiable | ~~NotificationTrait~~ 493 | 494 | [🔝 コンテンツに戻る](#コンテンツ) 495 | 496 | ### **できるだけ短く読みやすい構文で書く** 497 | 498 | Bad: 499 | 500 | ```php 501 | $request->session()->get('cart'); 502 | $request->input('name'); 503 | ``` 504 | 505 | Good: 506 | 507 | ```php 508 | session('cart'); 509 | $request->name; 510 | ``` 511 | 512 | さらなる例: 513 | 514 | 一般的な構文 | 短く読みやすい構文 515 | ------------ | ------------- 516 | `Session::get('cart')` | `session('cart')` 517 | `$request->session()->get('cart')` | `session('cart')` 518 | `Session::put('cart', $data)` | `session(['cart' => $data])` 519 | `$request->input('name'), Request::get('name')` | `$request->name, request('name')` 520 | `return Redirect::back()` | `return back()` 521 | `is_null($object->relation) ? null : $object->relation->id` | `optional($object->relation)->id` 522 | `return view('index')->with('title', $title)->with('client', $client)` | `return view('index', compact('title', 'client'))` 523 | `$request->has('value') ? $request->value : 'default';` | `$request->get('value', 'default')` 524 | `Carbon::now(), Carbon::today()` | `now(), today()` 525 | `App::make('Class')` | `app('Class')` 526 | `->where('column', '=', 1)` | `->where('column', 1)` 527 | `->orderBy('created_at', 'desc')` | `->latest()` 528 | `->orderBy('age', 'desc')` | `->latest('age')` 529 | `->orderBy('created_at', 'asc')` | `->oldest()` 530 | `->select('id', 'name')->get()` | `->get(['id', 'name'])` 531 | `->first()->name` | `->value('name')` 532 | 533 | [🔝 コンテンツに戻る](#コンテンツ) 534 | 535 | ### **newの代わりにIoCコンテナもしくはファサードを使う** 536 | 537 | new構文はクラス間の密結合を生み出し、テストすることを難しくします。IoCコンテナまたはファサードを代わりに使います。 538 | 539 | Bad: 540 | 541 | ```php 542 | $user = new User; 543 | $user->create($request->validated()); 544 | ``` 545 | 546 | Good: 547 | 548 | ```php 549 | public function __construct(User $user) 550 | { 551 | $this->user = $user; 552 | } 553 | 554 | .... 555 | 556 | $this->user->create($request->validated()); 557 | ``` 558 | 559 | [🔝 コンテンツに戻る](#コンテンツ) 560 | 561 | ### **`.env`ファイルのデータを直接参照しない** 562 | 563 | 代わりにconfigファイルへデータを渡します。そして、アプリケーション内でデータを参照する場合は`config()`ヘルパー関数を使います。 564 | 565 | Bad: 566 | 567 | ```php 568 | $apiKey = env('API_KEY'); 569 | ``` 570 | 571 | Good: 572 | 573 | ```php 574 | // config/api.php 575 | 'key' => env('API_KEY'), 576 | 577 | // データを使用する 578 | $apiKey = config('api.key'); 579 | ``` 580 | 581 | [🔝 コンテンツに戻る](#コンテンツ) 582 | 583 | ### **日付を標準フォーマットで保存する。アクセサとミューテータを使って日付フォーマットを変更する** 584 | 585 | Bad: 586 | 587 | ```php 588 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }} 589 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }} 590 | ``` 591 | 592 | Good: 593 | 594 | ```php 595 | // Model 596 | protected $dates = ['ordered_at', 'created_at', 'updated_at']; 597 | public function getSomeDateAttribute($date) 598 | { 599 | return $date->format('m-d'); 600 | } 601 | 602 | // View 603 | {{ $object->ordered_at->toDateString() }} 604 | {{ $object->ordered_at->some_date }} 605 | ``` 606 | 607 | [🔝 コンテンツに戻る](#コンテンツ) 608 | 609 | ### **その他 グッドプラクティス** 610 | 611 | ルートファイルにはロジックを入れないでください。 612 | 613 | Bladeテンプレートの中でVanilla PHP(標準のPHPコードを記述すること)の使用は最小限にします。 614 | 615 | [🔝 コンテンツに戻る](#コンテンツ) 616 | -------------------------------------------------------------------------------- /persian.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | ترجمه ها: 6 | 7 | [한국어](https://github.com/xotrs/laravel-best-practices) (by [cherrypick](https://github.com/xotrs)) 8 | 9 | [روسی](russian.md) 10 | 11 | [فارسی](persian.md) 12 | 13 | [پرتقالی](https://github.com/jonaselan/laravel-best-practices) (by [jonaselan](https://github.com/jonaselan)) 14 | 15 | [Tiếng Việt](https://chungnguyen.xyz/posts/code-laravel-lam-sao-cho-chuan) (by [Chung Nguyễn](https://github.com/nguyentranchung)) 16 | 17 | [Türkçe](turkish.md) (by [Burak](https://github.com/ikidnapmyself)) 18 | 19 | [Deutsche](german.md) (by [Sujal Patel](https://github.com/sujalpatel2209)) 20 | 21 | [Italiana](italian.md) (by [Sujal Patel](https://github.com/sujalpatel2209)) 22 | 23 | این مستندات درباره سازگاری لاراول با اصول SOLID یا Design Pattern ها و ... نیست. اینجا شما روش های اصولی توسعه پروژه های مبتنی بر لاراول رو پیدا میکنید که معمولا داخل پروژه ها در نظر گرفته نمیشوند. 24 | 25 | ## فهرست مطالب 26 | 27 | - [اصل تک وظیفه ای بودن](#اصل-تک-وظیفه-ای-بودن) 28 | 29 | - [مدل های بزرگ، کنترلرهای کوچک!](#مدل-های-بزرگ-کنترلرهای-کوچک) 30 | 31 | - [اعتبارسنجی](#اعتبارسنجی) 32 | 33 | - [منطق برنامه باید در service class باشد.](#منطق-برنامه-باید-در-service-class-باشد) 34 | 35 | - [اصل DRY یا خودت را تکرار نکن!](#اصل-dry-یا-خودت-را-تکرار-نکن) 36 | 37 | - [به جای استفاده از Query Builder و raw SQL queries از Eloquent ORM استفاده کنید. همچنین به جای استفاده از Arrays از Collections استفاده کنید.](#به-جای-استفاده-از-query-builder-و-raw-sql-queries-از-eloquent-orm-استفاده-کنید-همچنین-به-جای-استفاده-از-arrays-از-collections-استفاده-کنید) 38 | 39 | - [ایجاد یک مدل](#ایجاد-یک-مدل) 40 | 41 | - [به جای نوشتن query ها در blade از eager loading استفاده کنید. (مسئله N+1)](#به-جای-نوشتن-query-ها-در-blade-از-eager-loading-استفاده-کنید-مسئله-n1) 42 | 43 | - [کامنت گذاری بکنید، ولی اسامی متدها یا متغیرها را توصیفی و معنادار در نظر بگیرید. ](#کامنت-گذاری-بکنید-ولی-اسامی-متدها-یا-متغیرها-را-توصیفی-و-معنادار-در-نظر-بگیرید) 44 | 45 | - [در تمپلیت های Blade از js و css استفاده نکنید و هیچگونه کد HTML ای را در class های PHP استفاده نکنید.](#در-تمپلیت-های-blade-از-js-و-css-استفاده-نکنید-و-هیچگونه-کد-html-ای-را-در-class-های-php-استفاده-نکنید) 46 | 47 | - [به جای استفاده مستقیم از متن ها در کد، از فایل های config و languages استفاده کنید!](#به-جای-استفاده-مستقیم-از-متن-ها-در-کد-از-فایل-های-config-و-langugeus-استفاده-کنید) 48 | 49 | - [از ابزارهای استاندارد لاراول که مورد تایید جامعه کاربری آن میباشد، استفاده کنید.](#از-ابزارهای-استاندارد-لاراول-که-مورد-تایید-جامعه-کاربری-آن-میباشد-استفاده-کنید) 50 | 51 | - [از قرارداد های لاراول برای نامگذاری ها استفاده کنید.](#از-قرارداد-های-لاراول-برای-نامگذاری-ها-استفاده-کنید) 52 | 53 | - [تا حد ممکن در کدتان، از Syntax های معنادار و کوتاه استفاده کنید.](#تا-حد-ممکن-در-کدتان-از-syntax-های-معنادار-و-کوتاه-استفاده-کنید) 54 | 55 | - [به جای ایجاد یک object با new، از IoC container و facades استفاده کنید.](#به-جای-ایجاد-یک-object-با-new-از-ioc-container-و-facades-استفاده-کنید) 56 | 57 | - [از فایل .env هیچ وقت مستقیم داده ای دریافت نکنید.](#از-فایل-env-هیچ-وقت-مستقیم-داده-ای-دریافت-نکنید) 58 | 59 | - [تاریخ و زمان را در قالب استاندارد ذخیره کنید. از Accessors & Mutators ها برای دستکاری در نمایش تاریخ و زمان استفاده کنید.](#تاریخ-و-زمان-را-در-قالب-استاندارد-ذخیره-کنید-از-accessors--mutators-ها-برای-دستکاری-در-نمایش-تاریخ-و-زمان-استفاده-کنید) 60 | 61 | - [دیگر روش ها](#دیگر-قواعد-توسعه-روش-قابل-قبول-بدون-فهرست) 62 | 63 | 64 | 65 | 66 | ### **اصل تک وظیفه ای بودن** 67 | 68 | هر class و هر methode باید یک وظیفه داشته باشد. 69 | 70 | ❌ روش اشتباه: 71 | 72 | 73 | 74 | ```php 75 | public function getFullNameAttribute() 76 | { 77 | if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) { 78 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 79 | } else { 80 | return $this->first_name[0] . '. ' . $this->last_name; 81 | } 82 | } 83 | ``` 84 | 85 | 86 | ✔️ روش قابل قبول: 87 | 88 | 89 | 90 | ```php 91 | public function getFullNameAttribute() 92 | { 93 | return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort(); 94 | } 95 | 96 | public function isVerifiedClient() 97 | { 98 | return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified(); 99 | } 100 | 101 | public function getFullNameLong() 102 | { 103 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 104 | } 105 | 106 | public function getFullNameShort() 107 | { 108 | return $this->first_name[0] . '. ' . $this->last_name; 109 | } 110 | ``` 111 | 112 | 113 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 114 | 115 | ### **مدل های بزرگ، کنترلرهای کوچک!** 116 | 117 | اگر از Query Builder یا raw SQL queries استفاده میکنید، تمام منطق پایگاه داده را در model ها یا Repository classes قرار بدهید. 118 | 119 | ❌ روش اشتباه: 120 | 121 | 122 | 123 | ```php 124 | public function index() 125 | { 126 | $clients = Client::verified() 127 | ->with(['orders' => function ($q) { 128 | $q->where('created_at', '>', Carbon::today()->subWeek()); 129 | }]) 130 | ->get(); 131 | 132 | return view('index', ['clients' => $clients]); 133 | } 134 | ``` 135 | 136 | 137 | ✔️ روش قابل قبول: 138 | 139 | 140 | 141 | ```php 142 | public function index() 143 | { 144 | return view('index', ['clients' => $this->client->getWithNewOrders()]); 145 | } 146 | 147 | class Client extends Model 148 | { 149 | public function getWithNewOrders() 150 | { 151 | return $this->verified() 152 | ->with(['orders' => function ($q) { 153 | $q->where('created_at', '>', Carbon::today()->subWeek()); 154 | }]) 155 | ->get(); 156 | } 157 | } 158 | ``` 159 | 160 | 161 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 162 | 163 | ### **اعتبارسنجی** 164 | 165 | اعتبارسنجی ها را در Request classes انجام دهید نه در controllers. 166 | 167 | ❌ روش اشتباه: 168 | 169 | 170 | 171 | ```php 172 | public function store(Request $request) 173 | { 174 | $request->validate([ 175 | 'title' => 'required|unique:posts|max:255', 176 | 'body' => 'required', 177 | 'publish_at' => 'nullable|date', 178 | ]); 179 | 180 | .... 181 | } 182 | ``` 183 | 184 | 185 | ✔️ روش قابل قبول: 186 | 187 | 188 | 189 | ```php 190 | public function store(PostRequest $request) 191 | { 192 | .... 193 | } 194 | 195 | class PostRequest extends Request 196 | { 197 | public function rules() 198 | { 199 | return [ 200 | 'title' => 'required|unique:posts|max:255', 201 | 'body' => 'required', 202 | 'publish_at' => 'nullable|date', 203 | ]; 204 | } 205 | } 206 | ``` 207 | 208 | 209 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 210 | 211 | ### **منطق برنامه باید در service class باشد** 212 | 213 | هر کنترلر باید یک وظیفه داشته باشد، بنابراین منطق برنامه را در service classes بنویسید. 214 | 215 | ❌ روش اشتباه: 216 | 217 | 218 | 219 | ```php 220 | public function store(Request $request) 221 | { 222 | if ($request->hasFile('image')) { 223 | $request->file('image')->move(public_path('images') . 'temp'); 224 | } 225 | 226 | .... 227 | } 228 | ``` 229 | 230 | 231 | ✔️ روش قابل قبول: 232 | 233 | 234 | 235 | ```php 236 | public function store(Request $request) 237 | { 238 | $this->articleService->handleUploadedImage($request->file('image')); 239 | 240 | .... 241 | } 242 | 243 | class ArticleService 244 | { 245 | public function handleUploadedImage($image) 246 | { 247 | if (!is_null($image)) { 248 | $image->move(public_path('images') . 'temp'); 249 | } 250 | } 251 | } 252 | ``` 253 | 254 | 255 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 256 | 257 | ### **اصل DRY یا خودت را تکرار نکن!** 258 | 259 | تا حد ممکن از کد ها بازاستفاده کنید. تک وظیفه ای شدن به شما کمک میکند تا کار تکراری نکنید. همچنین در Blade template حتما این اصل را رعایت کنید و در model ها از Eloquent scopes استفاده کنید و ... . 260 | 261 | ❌ روش اشتباه: 262 | 263 | 264 | 265 | ```php 266 | public function getActive() 267 | { 268 | return $this->where('verified', 1)->whereNotNull('deleted_at')->get(); 269 | } 270 | 271 | public function getArticles() 272 | { 273 | return $this->whereHas('user', function ($q) { 274 | $q->where('verified', 1)->whereNotNull('deleted_at'); 275 | })->get(); 276 | } 277 | ``` 278 | 279 | 280 | ✔️ روش قابل قبول: 281 | 282 | 283 | 284 | ```php 285 | public function scopeActive($q) 286 | { 287 | return $q->where('verified', 1)->whereNotNull('deleted_at'); 288 | } 289 | 290 | public function getActive() 291 | { 292 | return $this->active()->get(); 293 | } 294 | 295 | public function getArticles() 296 | { 297 | return $this->whereHas('user', function ($q) { 298 | $q->active(); 299 | })->get(); 300 | } 301 | ``` 302 | 303 | 304 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 305 | 306 | ### **به جای استفاده از Query Builder و raw SQL queries از Eloquent ORM استفاده کنید. همچنین به جای استفاده از Arrays از Collections استفاده کنید.** 307 | 308 | Eloquent به شما این قابلیت را میدهد که کدهای خوانا و قابل توسعه بنویسید. همچنین دارای ویژگی های داخلی کاربردی مثل soft deletes یا events یا scopes و .. میباشد. 309 | 310 | ❌ روش اشتباه: 311 | 312 | 313 | 314 | ```sql 315 | SELECT * 316 | FROM `articles` 317 | WHERE EXISTS (SELECT * 318 | FROM `users` 319 | WHERE `articles`.`user_id` = `users`.`id` 320 | AND EXISTS (SELECT * 321 | FROM `profiles` 322 | WHERE `profiles`.`user_id` = `users`.`id`) 323 | AND `users`.`deleted_at` IS NULL) 324 | AND `verified` = '1' 325 | AND `active` = '1' 326 | ORDER BY `created_at` DESC 327 | ``` 328 | 329 | 330 | ✔️ روش قابل قبول: 331 | 332 | 333 | 334 | ```php 335 | Article::has('user.profile')->verified()->latest()->get(); 336 | ``` 337 | 338 | 339 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 340 | 341 | ### **ایجاد یک مدل** 342 | 343 | [منظور نویسنده از این بخش این میباشد که برای راحتی کار و کوتاه تر شدن کد، در html طوری مقادیر name هر input یا ... را نامگذاری کنید که با column های جدول مربوطه یکسان باشد تا laravel آن ها رو با یک کامند خیلی سریع مپ و ذخیره کند.] 344 | 345 | ❌ روش اشتباه: 346 | 347 | 348 | 349 | ```php 350 | $article = new Article; 351 | $article->title = $request->title; 352 | $article->content = $request->content; 353 | $article->verified = $request->verified; 354 | // Add category to article 355 | $article->category_id = $category->id; 356 | $article->save(); 357 | ``` 358 | 359 | 360 | ✔️ روش قابل قبول: 361 | 362 | 363 | 364 | ```php 365 | $category->article()->create($request->all()); 366 | ``` 367 | 368 | 369 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 370 | 371 | ### **به جای نوشتن query ها در blade از eager loading استفاده کنید. (مسئله N+1)** 372 | 373 | ❌ روش اشتباه (برای ۱۰۰ کاربر، ما ۱۰۱ کوئری را اجرا میکنیم!): 374 | 375 | 376 | 377 | ```php 378 | @foreach (User::all() as $user) 379 | {{ $user->profile->name }} 380 | @endforeach 381 | ``` 382 | 383 | 384 | ✔️ روش قابل قبول (برای ۱۰۰ کاربر، ما فقط ۲ کوئری اجرا کردیم!): 385 | 386 | 387 | 388 | ```php 389 | $users = User::with('profile')->get(); 390 | 391 | ... 392 | 393 | @foreach ($users as $user) 394 | {{ $user->profile->name }} 395 | @endforeach 396 | ``` 397 | 398 | 399 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 400 | 401 | ### **کامنت گذاری بکنید، ولی اسامی متدها یا متغیرها را توصیفی و معنادار در نظر بگیرید.** 402 | 403 | ❌ روش اشتباه: 404 | 405 | 406 | 407 | ```php 408 | if (count((array) $builder->getQuery()->joins) > 0) 409 | ``` 410 | 411 | 412 | ❗️ قابل قبول: 413 | 414 | 415 | 416 | ```php 417 | // Determine if there are any joins. 418 | if (count((array) $builder->getQuery()->joins) > 0) 419 | ``` 420 | 421 | 422 | ✔️ روش قابل قبول: 423 | 424 | 425 | 426 | ```php 427 | if ($this->hasJoins()) 428 | ``` 429 | 430 | 431 | 432 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 433 | 434 | ### **در تمپلیت های Blade از js و css استفاده نکنید و هیچگونه کد HTML ای را در class های PHP استفاده نکنید.** 435 | 436 | ❌ روش اشتباه: 437 | 438 | 439 | 440 | ```php 441 | let article = `{{ json_encode($article) }}`; 442 | ``` 443 | 444 | 445 | ❗️ قابل قبول: 446 | 447 | 448 | 449 | ```php 450 | 451 | 452 | Or 453 | 454 | {{ $article->name }} 455 | ``` 456 | 457 | 458 | در فایل JavaScript: 459 | 460 | 461 | 462 | ```javascript 463 | let article = $('#article').val(); 464 | ``` 465 | 466 | 467 | بهترین راه استفاده از پکیج مخصوص انتقال داده از php به js میباشد. 468 | 469 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 470 | 471 | ### **به جای استفاده مستقیم از متن ها در کد، از فایل های config و langugeus استفاده کنید!** 472 | 473 | ❌ روش اشتباه: 474 | 475 | 476 | 477 | ```php 478 | public function isNormal() 479 | { 480 | return $article->type === 'normal'; 481 | } 482 | 483 | return back()->with('message', 'مقاله شما ایجاد گردید!'); 484 | ``` 485 | 486 | 487 | ✔️ روش قابل قبول: 488 | 489 | 490 | 491 | ```php 492 | public function isNormal() 493 | { 494 | return $article->type === Article::TYPE_NORMAL; 495 | } 496 | 497 | return back()->with('message', __('app.article_added')); 498 | ``` 499 | 500 | 501 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 502 | 503 | ### **از ابزارهای استاندارد لاراول که مورد تایید جامعه کاربری آن میباشد، استفاده کنید.** 504 | 505 | به جای استفاده از ابزارها و پکیج های غیررسمی لاراول از ابزارها و قابلیت های داخلی و رسمی لاراول استفاده کنید. هر توسعه دهنده [لاراولی] ای که در آینده بخواهد با کدهای شما کار کند، باید ابزارهای جدید یاد بگیرد. همچنین اگر شما از ابزارهای غیررسمی استفاده کنید، شانس دریافت کمک از جامعه کاربری لاراول به طرز قابل توجهی کمتر میشود. این هزینه را به گردن مشتریان خود نیندازید! [منظور نویسنده این هست که تو کارهای بزرگ و مهمتون از ابزارهای جدید و پراکنده استفاده نکنید. همیشه سعی کنید از ابزارهای داخلی و یا پکیج های رسمی لاراول استفاده کنید مگر این که چاره دیگری نباشد! شما با پراکنده کردن ابزارها هزینه فنی/مالی توسعه محصول را برای آینده زیادتر میکنید!] 506 | 507 | 508 | 509 | نیاز | ابزارهای رسمی لاراول | ابزارهای غیررسمی لاراول 510 | ------------ | ------------- | ------------- 511 | Authorization | Policies | Entrust, Sentinel and other packages 512 | Compiling assets | Laravel Mix | Grunt, Gulp, 3rd party packages 513 | Development Environment | Homestead | Docker 514 | Deployment | Laravel Forge | Deployer and other solutions 515 | Unit testing | PHPUnit, Mockery | Phpspec 516 | Browser testing | Laravel Dusk | Codeception 517 | DB | Eloquent | SQL, Doctrine 518 | Templates | Blade | Twig 519 | Working with data | Laravel collections | Arrays 520 | Form validation | Request classes | 3rd party packages, validation in controller 521 | Authentication | Built-in | 3rd party packages, your own solution 522 | API authentication | Laravel Passport | 3rd party JWT and OAuth packages 523 | Creating API | Built-in | Dingo API and similar packages 524 | Working with DB structure | Migrations | Working with DB structure directly 525 | Localization | Built-in | 3rd party packages 526 | Realtime user interfaces | Laravel Echo, Pusher | 3rd party packages and working with WebSockets directly 527 | Generating testing data | Seeder classes, Model Factories, Faker | Creating testing data manually 528 | Task scheduling | Laravel Task Scheduler | Scripts and 3rd party packages 529 | DB | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB 530 | 531 | 532 | 533 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 534 | 535 | ### **از قرارداد های لاراول برای نامگذاری ها استفاده کنید** 536 | 537 | از استاندارد های php که به [PSR](http://www.php-fig.org/psr/psr-2/) معروف است استفاده کنید. 538 | 539 | همچنین روش های نامگذاری مورد قبول جامعه کاربری لاراول: 540 | 541 | 542 | 543 | بخش مربوطه | قاعده اسم گذاری | ✔️ روش قابل قبول | ❌ روش اشتباه 544 | ------------ | ------------- | ------------- | ------------- 545 | Controller | اسامی مفرد | ArticleController | ~~ArticlesController~~ 546 | Route | اسامی جمع | articles/1 | ~~article/1~~ 547 | Named route | روش snake_case همراه با نقاط اتصال | users.show_active | ~~users.show-active, show-active-users~~ 548 | Model | اسامی مفرد | User | ~~Users~~ 549 | hasOne or belongsTo relationship | اسامی مفرد | articleComment | ~~articleComments, article_comment~~ 550 | All other relationships | اسامی جمع | articleComments | ~~articleComment, article_comments~~ 551 | Table | اسامی جمع | article_comments | ~~article_comment, articleComments~~ 552 | Pivot table | نام مدل ها با اسامی مفرد و ترتیب الفبایی | article_user | ~~user_article, articles_users~~ 553 | Table column | روش snake_case بدون اسم مدل| meta_title | ~~MetaTitle; article_meta_title~~ 554 | Model property | روش snake_case | $model->created_at | ~~$model->createdAt~~ 555 | Foreign key | اسامی مفرد model name with _id suffix | article_id | ~~ArticleId, id_article, articles_id~~ 556 | Primary key | - | id | ~~custom_id~~ 557 | Migration | - | 2017_01_01_000000_create_articles_table | ~~2017_01_01_000000_articles~~ 558 | Method | روش camelCase | getAll | ~~get_all~~ 559 | Method in resource controller | [table](https://laravel.com/docs/master/controllers#resource-controllers) | store | ~~saveArticle~~ 560 | Method in test class | روش camelCase | testGuestCannotSeeArticle | ~~test_guest_cannot_see_article~~ 561 | Variable | روش camelCase | $articlesWithAuthor | ~~$articles_with_author~~ 562 | Collection | توصیفی و اسامی جمع | $activeUsers = User::active()->get() | ~~$active, $data~~ 563 | Object | توصیفی و اسامی مفرد | $activeUser = User::active()->first() | ~~$users, $obj~~ 564 | Config and language files index | snake_case | articles_enabled | ~~ArticlesEnabled; articles-enabled~~ 565 | View | kebab-case | show-filtered.blade.php | ~~showFiltered.blade.php, show_filtered.blade.php~~ 566 | Config | snake_case | google_calendar.php | ~~googleCalendar.php, google-calendar.php~~ 567 | Contract (interface) | صفت یا اسم | Authenticatable | ~~AuthenticationInterface, IAuthentication~~ 568 | Trait | صفت | Notifiable | ~~NotificationTrait~~ 569 | 570 | 571 | 572 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 573 | 574 | ### **تا حد ممکن در کدتان، از Syntax های معنادار و کوتاه استفاده کنید** 575 | 576 | ❌ روش اشتباه: 577 | 578 | 579 | 580 | ```php 581 | $request->session()->get('cart'); 582 | $request->input('name'); 583 | ``` 584 | 585 | 586 | ✔️ روش قابل قبول: 587 | 588 | 589 | 590 | ```php 591 | session('cart'); 592 | $request->name; 593 | ``` 594 | 595 | 596 | مثال های بیشتر: 597 | 598 | 599 | 600 | سینتکس متداول | سینتکس کوتاهتر و خواناتر 601 | ------------ | ------------- 602 | `Session::get('cart')` | `session('cart')` 603 | `$request->session()->get('cart')` | `session('cart')` 604 | `Session::put('cart', $data)` | `session(['cart' => $data])` 605 | `$request->input('name'), Request::get('name')` | `$request->name, request('name')` 606 | `return Redirect::back()` | `return back()` 607 | `is_null($object->relation) ? null : $object->relation->id` | `optional($object->relation)->id` 608 | `return view('index')->with('title', $title)->with('client', $client)` | `return view('index', compact('title', 'client'))` 609 | `$request->has('value') ? $request->value : 'default';` | `$request->get('value', 'default')` 610 | `Carbon::now(), Carbon::today()` | `now(), today()` 611 | `App::make('Class')` | `app('Class')` 612 | `->where('column', '=', 1)` | `->where('column', 1)` 613 | `->orderBy('created_at', 'desc')` | `->latest()` 614 | `->orderBy('age', 'desc')` | `->latest('age')` 615 | `->orderBy('created_at', 'asc')` | `->oldest()` 616 | `->select('id', 'name')->get()` | `->get(['id', 'name'])` 617 | `->first()->name` | `->value('name')` 618 | 619 | 620 | 621 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 622 | 623 | ### **به جای ایجاد یک object با new، از IoC container و facades استفاده کنید.** 624 | 625 | ایجاد یک object جدید با کلمه new یک اتصال کامل بین class ها و تست های پیچیده ایجاد میکند! بهتر است از IoC container یا facades استفاده کنید. [این بخش از مطلب تا جایی که من متوجه شدم مربوط به مباحث توسعه تست محور میباشد که من آشنایی زیادی ندارم ولی بعد از مطالعه و تکمیل اطلاعاتم این بخش را با توضیح تکمیلی، کامل خواهم کرد.] 626 | 627 | ❌ روش اشتباه: 628 | 629 | create($request->all()); 634 | ``` 635 | 636 | 637 | ✔️ روش قابل قبول: 638 | 639 | 640 | 641 | ```php 642 | public function __construct(User $user) 643 | { 644 | $this->user = $user; 645 | } 646 | 647 | .... 648 | 649 | $this->user->create($request->all()); 650 | ``` 651 | 652 | 653 | 654 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 655 | 656 | ### **از فایل .env هیچ وقت مستقیم داده ای دریافت نکنید.** 657 | 658 | اطلاعات موجود در .env را در صورت نیاز به استفاده، در فایل های config لود کنید و سپس با استفاده از helper function فایل های کانفیگ یعنی config() با آن در نرم افزار خود کار کنید. 659 | 660 | ❌ روش اشتباه: 661 | 662 | 663 | ```php 664 | $apiKey = env('API_KEY'); 665 | ``` 666 | 667 | 668 | ✔️ روش قابل قبول: 669 | 670 | 671 | 672 | ```php 673 | // config/api.php 674 | 'key' => env('API_KEY'), 675 | 676 | // Use the data 677 | $apiKey = config('api.key'); 678 | ``` 679 | 680 | 681 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 682 | 683 | ### **تاریخ و زمان را در قالب استاندارد ذخیره کنید. از Accessors & Mutators ها برای دستکاری در نمایش تاریخ و زمان استفاده کنید.** 684 | 685 | ❌ روش اشتباه: 686 | 687 | 688 | 689 | ```php 690 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }} 691 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }} 692 | ``` 693 | 694 | 695 | ✔️ روش قابل قبول: 696 | 697 | 698 | 699 | ```php 700 | // Model 701 | protected $dates = ['ordered_at', 'created_at', 'updated_at']; 702 | public function getSomeDateAttribute($date) 703 | { 704 | return $date->format('m-d'); 705 | } 706 | 707 | // View 708 | {{ $object->ordered_at->toDateString() }} 709 | {{ $object->ordered_at->some_date }} 710 | ``` 711 | 712 | 713 | 714 | 715 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 716 | 717 | ### **دیگر قواعد توسعه روش قابل قبول (بدون فهرست)** 718 | 719 | - در فایل های route خود هیچوقت منطق برنامه را قرار ندهید. 720 | 721 | - تا حد ممکن از vanilla PHP در فایل های blade استفاده نکنید. 722 | 723 | [🔝 بازگشت به فهرست](#فهرست-مطالب) 724 | 725 | 726 | -------------------------------------------------------------------------------- /russian.md: -------------------------------------------------------------------------------- 1 |  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 | ```php 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 | ```php 63 | public function getFullNameAttribute() 64 | { 65 | return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort(); 66 | } 67 | 68 | public function isVerifiedClient() 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 | ```php 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 | ```php 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 | ```php 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 | ```php 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 | ```php 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 | ```php 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 | ```php 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 | ```php 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 | ```php 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 | ```php 277 | Article::has('user.profile')->verified()->latest()->get(); 278 | ``` 279 | 280 | [🔝 Наверх](#Содержание) 281 | 282 | ### **Используйте массовое заполнение (mass assignment)** 283 | 284 | Плохо: 285 | 286 | ```php 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 | ```php 299 | $category->article()->create($request->validated()); 300 | ``` 301 | 302 | [🔝 Наверх](#Содержание) 303 | 304 | ### **Не выполняйте запросы в представлениях и используйте нетерпеливую загрузку (проблема N + 1)** 305 | 306 | Плохо (будет выполнен 101 запрос в БД для 100 пользователей): 307 | 308 | ```php 309 | @foreach (User::all() as $user) 310 | {{ $user->profile->name }} 311 | @endforeach 312 | ``` 313 | 314 | Хорошо (будет выполнено 2 запроса в БД для 100 пользователей): 315 | 316 | ```php 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 | ```php 333 | if (count((array) $builder->getQuery()->joins) > 0) 334 | ``` 335 | 336 | Лучше: 337 | 338 | ```php 339 | // Determine if there are any joins. 340 | if (count((array) $builder->getQuery()->joins) > 0) 341 | ``` 342 | 343 | Хорошо: 344 | 345 | ```php 346 | if ($this->hasJoins()) 347 | ``` 348 | 349 | [🔝 Наверх](#Содержание) 350 | 351 | ### **Выносите JS и CSS из шаблонов Blade и HTML из PHP кода** 352 | 353 | Плохо: 354 | 355 | ```php 356 | let article = `{{ json_encode($article) }}`; 357 | ``` 358 | 359 | Лучше: 360 | 361 | ```php 362 | 363 | 364 | Или 365 | 366 | {{ $article->name }} 367 | ``` 368 | 369 | В Javascript файле: 370 | 371 | ```php 372 | let article = $('#article').val(); 373 | ``` 374 | 375 | Еще лучше использовать специализированный пакет для передачи данных из бэкенда во фронтенд. 376 | 377 | [🔝 Наверх](#Содержание) 378 | 379 | ### **Конфиги, языковые файлы и константы вместо текста в коде** 380 | 381 | Непосредственно в коде не должно быть никакого текста. 382 | 383 | Плохо: 384 | 385 | ```php 386 | public function isNormal() 387 | { 388 | return $article->type === 'normal'; 389 | } 390 | 391 | return back()->with('message', 'Ваша статья была успешно добавлена'); 392 | ``` 393 | 394 | Хорошо: 395 | 396 | ```php 397 | public function isNormal() 398 | { 399 | return $article->type === Article::TYPE_NORMAL; 400 | } 401 | 402 | return back()->with('message', __('app.article_added')); 403 | ``` 404 | 405 | [🔝 Наверх](#Содержание) 406 | 407 | ### **Используйте инструменты и практики принятые сообществом** 408 | 409 | Laravel имеет встроенные инструменты для решения часто встречаемых задач. Предпочитайте пользоваться ими использованию сторонних пакетов и инструментов. Laravel разработчику, пришедшему в проект после вас, придется изучать и работать с новым для него инструментом, со всеми вытекающими последствиями. Получить помощь от сообщества будет также гораздо труднее. Не заставляйте клиента или работодателя платить за ваши велосипеды. 410 | 411 | Задача | Стандартные инструмент | Нестандартные инструмент 412 | ------------ | ------------- | ------------- 413 | Авторизация | Политики | Entrust, Sentinel и др. пакеты, собственное решение 414 | Работа с JS, CSS и пр. | Laravel Mix | Grunt, Gulp, сторонние пакеты 415 | Среда разработки | Homestead | Docker 416 | Разворачивание приложений | Laravel Forge | Deployer и многие другие 417 | Тестирование | Phpunit, Mockery | Phpspec 418 | e2e тестирование | Laravel Dusk | Codeception 419 | Работа с БД | Eloquent | SQL, построитель запросов, Doctrine 420 | Шаблоны | Blade | Twig 421 | Работа с данными | Коллекции Laravel | Массивы 422 | Валидация форм | Request классы | Сторонние пакеты, валидация в контроллере 423 | Аутентификация | Встроенный функционал | Сторонние пакеты, собственное решение 424 | Аутентификация API | Laravel Passport | Сторонние пакеты, использующие JWT, OAuth 425 | Создание API | Встроенный функционал | Dingo API и другие пакеты 426 | Работа со структурой БД | Миграции | Работа с БД напрямую 427 | Локализация | Встроенный функционал | Сторонние пакеты 428 | Обмен данными в реальном времени | Laravel Echo, Pusher | Пакеты и работа с веб сокетами напрямую 429 | Генерация тестовых данных | Seeder классы, фабрики моделей, Faker | Ручное заполнение и пакеты 430 | Планирование задач | Планировщик задач Laravel | Скрипты и сторонние пакеты 431 | БД | MySQL, PostgreSQL, SQLite, SQL Server | MongoDb 432 | 433 | [🔝 Наверх](#Содержание) 434 | 435 | ### **Соблюдайте соглашения сообщества об именовании** 436 | 437 | Следуйте [стандартам PSR](http://www.php-fig.org/psr/psr-2/) при написании кода. 438 | 439 | Также, соблюдайте другие cоглашения об именовании: 440 | 441 | Что | Правило | Принято | Не принято 442 | ------------ | ------------- | ------------- | ------------- 443 | Контроллер | ед. ч. | ArticleController | ~~ArticlesController~~ 444 | Маршруты | мн. ч. | articles/1 | ~~article/1~~ 445 | Имена маршрутов | snake_case | users.show_active | ~~users.show-active, show-active-users~~ 446 | Модель | ед. ч. | User | ~~Users~~ 447 | Отношения hasOne и belongsTo | ед. ч. | articleComment | ~~articleComments, article_comment~~ 448 | Все остальные отношения | мн. ч. | articleComments | ~~articleComment, article_comments~~ 449 | Таблица | мн. ч. | article_comments | ~~article_comment, articleComments~~ 450 | Pivot таблица | имена моделей в алфавитном порядке в ед. ч. | article_user | ~~user_article, articles_users~~ 451 | Столбец в таблице | snake_case без имени модели | meta_title | ~~MetaTitle; article_meta_title~~ 452 | Свойство модели | snake_case | $model->created_at | ~~$model->createdAt~~ 453 | Внешний ключ | имя модели ед. ч. и _id | article_id | ~~ArticleId, id_article, articles_id~~ 454 | Первичный ключ | - | id | ~~custom_id~~ 455 | Миграция | - | 2017_01_01_000000_create_articles_table | ~~2017_01_01_000000_articles~~ 456 | Метод | camelCase | getAll | ~~get_all~~ 457 | Метод в контроллере ресурсов | [таблица](https://laravel.com/docs/master/controllers#resource-controllers) | store | ~~saveArticle~~ 458 | Метод в тесте | camelCase | testGuestCannotSeeArticle | ~~test_guest_cannot_see_article~~ 459 | Переменные | camelCase | $articlesWithAuthor | ~~$articles_with_author~~ 460 | Коллекция | описательное, мн. ч. | $activeUsers = User::active()->get() | ~~$active, $data~~ 461 | Объект | описательное, ед. ч. | $activeUser = User::active()->first() | ~~$users, $obj~~ 462 | Индексы в конфиге и языковых файлах | snake_case | articles_enabled | ~~ArticlesEnabled; articles-enabled~~ 463 | Представление | kebab-case | show-filtered.blade.php | ~~showFiltered.blade.php, show_filtered.blade.php~~ 464 | Конфигурационный файл | snake_case | google_calendar.php | ~~googleCalendar.php, google-calendar.php~~ 465 | Контракт (интерфейс) | прилагательное или существительное | Authenticatable | ~~AuthenticationInterface, IAuthentication~~ 466 | Трейт | прилагательное | Notifiable | ~~NotificationTrait~~ 467 | 468 | [🔝 Наверх](#Содержание) 469 | 470 | ### **Короткий и читаемый синтаксис там, где это возможно** 471 | 472 | Плохо: 473 | 474 | ```php 475 | $request->session()->get('cart'); 476 | $request->input('name'); 477 | ``` 478 | 479 | Хорошо: 480 | 481 | ```php 482 | session('cart'); 483 | $request->name; 484 | ``` 485 | 486 | Еще примеры: 487 | 488 | Часто используемый синтаксис | Более короткий и читаемый синтаксис 489 | ------------ | ------------- 490 | `Session::get('cart')` | `session('cart')` 491 | `$request->session()->get('cart')` | `session('cart')` 492 | `Session::put('cart', $data)` | `session(['cart' => $data])` 493 | `$request->input('name'), Request::get('name')` | `$request->name, request('name')` 494 | `return Redirect::back()` | `return back()` 495 | `is_null($object->relation) ? null : $object->relation->id` | `optional($object->relation)->id` 496 | `return view('index')->with('title', $title)->with('client', $client)` | `return view('index', compact('title', 'client'))` 497 | `$request->has('value') ? $request->value : 'default';` | `$request->get('value', 'default')` 498 | `Carbon::now(), Carbon::today()` | `now(), today()` 499 | `App::make('Class')` | `app('Class')` 500 | `->where('column', '=', 1)` | `->where('column', 1)` 501 | `->orderBy('created_at', 'desc')` | `->latest()` 502 | `->orderBy('age', 'desc')` | `->latest('age')` 503 | `->orderBy('created_at', 'asc')` | `->oldest()` 504 | `->select('id', 'name')->get()` | `->get(['id', 'name'])` 505 | `->first()->name` | `->value('name')` 506 | 507 | [🔝 Наверх](#Содержание) 508 | 509 | ### **Используйте IoC или фасады вместо new Class** 510 | 511 | Внедрение классов через синтаксис new Class создает сильное сопряжение между частями приложения и усложняет тестирование. Используйте контейнер или фасады. 512 | 513 | Плохо: 514 | 515 | ```php 516 | $user = new User; 517 | $user->create($request->validated()); 518 | ``` 519 | 520 | Хорошо: 521 | 522 | ```php 523 | public function __construct(User $user) 524 | { 525 | $this->user = $user; 526 | } 527 | 528 | .... 529 | 530 | $this->user->create($request->validated()); 531 | ``` 532 | 533 | [🔝 Наверх](#Содержание) 534 | 535 | ### **Не работайте с данными из файла `.env` напрямую** 536 | 537 | Передайте данные из `.env` файла в кофигурационный файл и используйте `config()` в приложении, чтобы использовать эти данными. 538 | 539 | Плохо: 540 | 541 | ```php 542 | $apiKey = env('API_KEY'); 543 | ``` 544 | 545 | Хорошо: 546 | 547 | ```php 548 | // config/api.php 549 | 'key' => env('API_KEY'), 550 | 551 | // Используйте данные в приложении 552 | $apiKey = config('api.key'); 553 | ``` 554 | 555 | [🔝 Наверх](#Содержание) 556 | 557 | ### **Храните даты в стандартном формате. Используйте читатели и преобразователи для преобразования формата** 558 | 559 | Плохо: 560 | 561 | ```php 562 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }} 563 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }} 564 | ``` 565 | 566 | Хорошо: 567 | 568 | ```php 569 | // Модель 570 | protected $dates = ['ordered_at', 'created_at', 'updated_at']; 571 | // Читатель (accessor) 572 | public function getSomeDateAttribute($date) 573 | { 574 | return $date->format('m-d'); 575 | } 576 | 577 | // Шаблон 578 | {{ $object->ordered_at->toDateString() }} 579 | {{ $object->ordered_at->some_date }} 580 | ``` 581 | 582 | [🔝 Наверх](#Содержание) 583 | 584 | ### **Другие советы и практики** 585 | 586 | Не размещайте логику в маршрутах. 587 | 588 | Старайтесь не использовать "сырой" PHP в шаблонах Blade. 589 | 590 | [🔝 Наверх](#Содержание) 591 | -------------------------------------------------------------------------------- /spanish.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | No se trata de una adaptación a Laravel de los principios SOLID ni de patrones, etcétera. Aquí encontrarás las mejores prácticas que, por lo general, son ignoradas en proyectos Laravel de la vida real. 4 | 5 | ## Índice de contenido 6 | 7 | [Principio de propósito único](#principio-de-proposito-unico) 8 | 9 | [Modelos gordos, controladores delgados](#modelos-gordos-controladores-delgados) 10 | 11 | [Validación](#validacion) 12 | 13 | [La lógica de negocios debe estar en una clase ayudante](#la-logica-de-negocios-debe-estar-en-una-clase-ayudante) 14 | 15 | [No te repitas (DRY)](#no-te-repitas-dry) 16 | 17 | [Prioriza el uso de Eloquent por sobre el constructor de consultas y consultas puras. Prioriza las colecciones sobre los arreglos](#prioriza-el-uso-de-eloquent-por-sobre-el-constructor-de-consultas-y-consultas-puras-prioriza-las-colecciones-sobre-los-arreglos) 18 | 19 | [Asignación en masa](#asignacion-en-masa) 20 | 21 | [No ejecutes consultas en las plantillas blade y utiliza el cargado prematuro (Problema N + 1)](#no-ejecutes-consultas-en-las-plantillas-blade-y-utiliza-el-cargado-prematuro-problema-n--1)) 22 | 23 | [Comenta tu código, pero prioriza los métodos y nombres de variables descriptivas por sobre los comentarios](#comenta-tu-codigo-pero-prioriza-los-metodos-y-nombres-de-variables-descriptivas-por-sobre-los-comentarios) 24 | 25 | [No coloques JS ni CSS en las plantillas blade y no coloques HTML en clases de PHP](#no-coloques-js-ni-css-en-las-plantillas-blade-y-no-coloques-html-en-clases-de-php) 26 | 27 | [Utiliza los archivos de configuración y lenguaje en lugar de texto en el código](#utiliza-los-archivos-de-configuracion-y-lenguaje-en-lugar-de-texto-en-el-codigo) 28 | 29 | [Utiliza las herramientas estándar de Laravel aceptadas por la comunidad](#utiliza-las-herramientas-estandar-de-laravel-aceptadas-por-la-comunidad) 30 | 31 | [Sigue la convención de Laravel para los nombres](#sigue-la-convencion-de-laravel-para-los-nombres) 32 | 33 | [Utiliza sintaxis cortas y legibles siempre que sea posible](#utiliza-sintaxis-cortas-y-legibles-siempre-que-sea-posible) 34 | 35 | [Utiliza contenedores IoC o fachadas en lugar de new Class](#utiliza-contenedores-ioc-o-fachadas-en-lugar-de-new-class) 36 | 37 | [No saques información directamente del archivo .env](#no-saques-informacion-directamente-del-archivo-env) 38 | 39 | [Guarda las fechas en los formatos estándares. Utiliza los accessors y mutators para modificar el formato](#guarda-las-fechas-en-los-formatos-estandares-utiliza-los-accessors-y-mutators-para-modificar-el-formato) 40 | 41 | [Otras buenas prácticas](#otras-buenas-practicas) 42 | 43 | ### **Principio de proposito unico** 44 | 45 | Las clases y los métodos deben tener un solo propósito. 46 | 47 | Malo: 48 | 49 | ```php 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 | Bueno: 61 | 62 | ```php 63 | public function getFullNameAttribute() 64 | { 65 | return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort(); 66 | } 67 | 68 | public function isVerifiedClient() 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 | [🔝 Volver al índice](#índice-de-contenido) 85 | 86 | ### **Modelos gordos, controladores delgados** 87 | 88 | Coloca toda la lógica relacionada a la base de datos en los modelos de Eloquent o en un repositorio de clases si estás utilizando el constructor de consultas o consultas SQL puras. 89 | 90 | Malo: 91 | 92 | ```php 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 | Bueno: 106 | 107 | ```php 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 | [🔝 Volver al índice](#índice-de-contenido) 127 | 128 | ### **Validacion** 129 | 130 | Quita las validaciones de los controladores y colócalas en clases Request 131 | 132 | Malo: 133 | 134 | ```php 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 | Bueno: 148 | 149 | ```php 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 | [🔝 Volver al índice](#índice-de-contenido) 169 | 170 | ### **La logica de negocios debe estar en una clase ayudante** 171 | 172 | Un controlador solo debe tener un propósito, así que mueve la lógica de negocio fuera de los controladores y colócala en clases ayudantes. 173 | 174 | Malo: 175 | 176 | ```php 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 | Bueno: 188 | 189 | ```php 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 | [🔝 Volver al índice](#índice-de-contenido) 209 | 210 | ### **No te repitas (DRY)** 211 | 212 | Reutiliza código cada vez que puedas. El SRP te ayuda a evitar la duplicación. Reutiliza también las plantillas blade, utiliza eloquent scope, etcétera. 213 | 214 | Malo: 215 | 216 | ```php 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 | Bueno: 231 | 232 | ```php 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 | [🔝 Volver al índice](#índice-de-contenido) 252 | 253 | ### **Prioriza el uso de Eloquent por sobre el constructor de consultas y consultas puras. Prioriza las colecciones sobre los arreglos** 254 | 255 | Eloquent te permite escribir código legible y mantenible. Eloquent también tiene muy buenas herramientas preconstruidas como los borrados leves, eventos, scopes, etcétera. 256 | 257 | Malo: 258 | 259 | ```sql 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 | Bueno: 275 | 276 | ```php 277 | Article::has('user.profile')->verified()->latest()->get(); 278 | ``` 279 | 280 | [🔝 Volver al índice](#índice-de-contenido) 281 | 282 | ### **Asignacion en masa** 283 | 284 | Malo: 285 | 286 | ```php 287 | $article = new Article; 288 | $article->title = $request->title; 289 | $article->content = $request->content; 290 | $article->verified = $request->verified; 291 | // Add category to article 292 | $article->category_id = $category->id; 293 | $article->save(); 294 | ``` 295 | 296 | Bueno: 297 | 298 | ```php 299 | $category->article()->create($request->validated()); 300 | ``` 301 | 302 | [🔝 Volver al índice](#índice-de-contenido) 303 | 304 | ### **No ejecutes consultas en las plantillas blade y utiliza el cargado prematuro (Problema N + 1)** 305 | 306 | Malo (Para 100 usuarios, se ejecutarán 101 consultas): 307 | 308 | ```php 309 | @foreach (User::all() as $user) 310 | {{ $user->profile->name }} 311 | @endforeach 312 | ``` 313 | 314 | Bueno (Para 100 usuarios, se ejecutarán 2 consultas): 315 | 316 | ```php 317 | $users = User::with('profile')->get(); 318 | 319 | ... 320 | 321 | @foreach ($users as $user) 322 | {{ $user->profile->name }} 323 | @endforeach 324 | ``` 325 | 326 | [🔝 Volver al índice](#índice-de-contenido) 327 | 328 | ### **Comenta tu codigo, pero prioriza los metodos y nombres de variables descriptivas por sobre los comentarios** 329 | 330 | Malo: 331 | 332 | ```php 333 | if (count((array) $builder->getQuery()->joins) > 0) 334 | ``` 335 | 336 | Mejor: 337 | 338 | ```php 339 | // Determina si hay alguna unión 340 | if (count((array) $builder->getQuery()->joins) > 0) 341 | ``` 342 | 343 | Bueno: 344 | 345 | ```php 346 | if ($this->hasJoins()) 347 | ``` 348 | 349 | [🔝 Volver al índice](#índice-de-contenido) 350 | 351 | ### **No coloques JS ni CSS en las plantillas blade y no coloques HTML en clases de PHP** 352 | 353 | Malo: 354 | 355 | ```php 356 | let article = `{{ json_encode($article) }}`; 357 | ``` 358 | 359 | Mejor: 360 | 361 | ```php 362 | 363 | 364 | Or 365 | 366 | {{ $article->name }} 367 | ``` 368 | 369 | En el archivo JavaScript: 370 | 371 | ```javascript 372 | let article = $('#article').val(); 373 | ``` 374 | 375 | La mejor ruta es utilizar algún paquete especializado para transferir información de PHP a JS. 376 | 377 | [🔝 Volver al índice](#índice-de-contenido) 378 | 379 | ### **Utiliza los archivos de configuracion y lenguaje en lugar de texto en el codigo** 380 | 381 | Malo: 382 | 383 | ```php 384 | public function isNormal() 385 | { 386 | return $article->type === 'normal'; 387 | } 388 | 389 | return back()->with('mensaje', '¡Su artículo ha sido agregado!'); 390 | ``` 391 | 392 | Bueno: 393 | 394 | ```php 395 | public function isNormal() 396 | { 397 | return $article->type === Article::TYPE_NORMAL; 398 | } 399 | 400 | return back()->with('mensaje', __('app.articulo_agregado')); 401 | ``` 402 | 403 | [🔝 Volver al índice](#índice-de-contenido) 404 | 405 | ### **Utiliza las herramientas estandar de Laravel aceptadas por la comunidad** 406 | 407 | Prioriza la utilización de funcionalidades integradas y los paquetes de la comunidad en lugar de utilizar paquetes o herramientas de terceros ya que cualquier desarrollador que vaya a trabajar a futuro en tu aplicación necesitará aprender a utilizar nuevas herramientas. También, las probabilidades de recibir ayuda de la comunidad son significativamente más bajas cuando utilizas herramientas o paquetes de terceros. No hagas que tu cliente pague por ello. 408 | 409 | Tarea | Herramienta estándar | Herramientas de terceras personas 410 | ------------ | ------------- | ------------- 411 | Autorización | Policies | Entrust, Sentinel y otros paquetes 412 | Compilar assets | Laravel Mix | Grunt, Gulp, paquetes de terceros 413 | Entorno de desarrollo | Homestead | Docker 414 | Deployment | Laravel Forge | Deployer y otras soluciones 415 | Unit testing | PHPUnit, Mockery | Phpspec 416 | Testeo en el navegador | Laravel Dusk | Codeception 417 | Base de datos | Eloquent | SQL, Doctrine 418 | Plantillas | Blade | Twig 419 | Trabajar con data | Laravel collections | Arreglos 420 | Validación de formularios | Clases Request | Paquetes de terceros, validación en el controlador 421 | Autenticación | Integrada | Paquetes de terceros, solución propia 422 | Autenticación para API's | Laravel Passport | Paquetes oAuth y JWT de terceros 423 | Creación de API's | Integrado | Dingo API y paquetes similares 424 | Estructura de la base de datos | Migraciones | Trabajar directamente con la estructura 425 | Localización | Integrada | Paquetes de terceros 426 | Interfaces en tiempo real | Laravel Echo, Pusher | Paquetes de terceros y trabajar directamente con WebSockets. 427 | Generación de información de prueba | Clases Seeder, Fábricas de modelos, Faker | Crear la información manualmente 428 | Programación de tareas | Laravel Task Scheduler | Scripts y paquetes de terceros 429 | Base de datos | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB 430 | 431 | [🔝 Volver al índice](#índice-de-contenido) 432 | 433 | ### **Sigue la convencion de Laravel para los nombres** 434 | 435 | Sigue los [estándares PSR](http://www.php-fig.org/psr/psr-2/). 436 | 437 | También, sigue la convención aceptada por la comunidad: 438 | 439 | Qué | Cómo | Bueno | Malo 440 | ------------ | ------------- | ------------- | ------------- 441 | Controlador | singular | ControladorArticulo | ~~ControladorArticulos~~ 442 | Ruta | plural | articulos/1 | ~~articulo/1~~ 443 | Nombres de rutas | snake_case con notación de puntos | usuarios.mostrar_activos | ~~usuarios.mostrar-activos, mostrar-usuarios-activos~~ 444 | Modelo | singular | Usuario | ~~Usuarios~~ 445 | Relaciones hasOne o belongsTo | singular | comentarioArticulo | ~~comentariosArticulo, comentario_articulo~~ 446 | Cualquier otra relación | plural | comentariosArticulo | ~~comentarioArticulo, comentarios_articulo~~ 447 | Tabla | plural | comentarios_articulo | ~~comentario_articulo, comentariosArticulo~~ 448 | Tabla de pivote | Nombres de modelos en singular y en orden alfabético | articulo_usuario | ~~usuario_articulo, articulos_usuarios~~ 449 | Columna de tabla | snake_case sin el nombre del modelo | meta_titulo | ~~MetaTitulo; articulo_meta_titulo~~ 450 | Propiedad de mdelo | snake_case | $model->created_at | ~~$model->createdAt~~ 451 | Clave foránea | Nombre en singular del modelo con el sufijo _id | articulo_id | ~~articuloId, id_articulo, articulos_id~~ 452 | Clave primaria | - | id | ~~id_personalizado~~ 453 | Migración | - | 2017_01_01_000000_create_articles_table | ~~2017_01_01_000000_articles~~ 454 | Método | camelCase | traerTodo | ~~traer_todo~~ 455 | Método en controlador de recursos | [table](https://laravel.com/docs/master/controllers#resource-controllers) | guardar | ~~guardarArticulo~~ 456 | Método en clase de pruebas | camelCase | testGuestCannotSeeArticle | ~~test_guest_cannot_see_article~~ 457 | Variable | camelCase | $articulosConAutor | ~~$articulos_con_autor~~ 458 | Colección | descriptiva, plural | $usuariosActivos = Usuario::active()->get() | ~~$activo, $data~~ 459 | Objeto | descriptivo, singular | $usuarioActivo = Usuario::active()->first() | ~~$usuarios, $obj~~ 460 | Índice de archivos de configuración y lenguaje | snake_case | articulos_habilitados | ~~articulosHabilitados; articulos-habilitados~~ 461 | Vistas | kebab-case | show-filtered.blade.php | ~~showFiltered.blade.php, show_filtered.blade.php~~ 462 | Configuración | snake_case | google_calendar.php | ~~googleCalendar.php, google-calendar.php~~ 463 | Contrato (interface) | adjetivo o sustantivo | Autenticable | ~~interfaceAutenticacion, IAutenticacion~~ 464 | Trait | adjetivo | Notifiable | ~~NotificationTrait~~ 465 | 466 | [🔝 Volver al índice](#índice-de-contenido) 467 | 468 | ### **Utiliza sintaxis cortas y legibles siempre que sea posible** 469 | 470 | Malo: 471 | 472 | ```php 473 | $request->session()->get('cart'); 474 | $request->input('name'); 475 | ``` 476 | 477 | Bueno: 478 | 479 | ```php 480 | session('cart'); 481 | $request->name; 482 | ``` 483 | 484 | Más ejemplos 485 | 486 | Sintaxis común | Sintaxis corta y legible 487 | ------------ | ------------- 488 | `Session::get('cart')` | `session('cart')` 489 | `$request->session()->get('cart')` | `session('cart')` 490 | `Session::put('cart', $data)` | `session(['cart' => $data])` 491 | `$request->input('name'), Request::get('name')` | `$request->name, request('name')` 492 | `return Redirect::back()` | `return back()` 493 | `is_null($object->relation) ? null : $object->relation->id` | `optional($object->relation)->id` 494 | `return view('index')->with('title', $title)->with('client', $client)` | `return view('index', compact('title', 'client'))` 495 | `$request->has('value') ? $request->value : 'default';` | `$request->get('value', 'default')` 496 | `Carbon::now(), Carbon::today()` | `now(), today()` 497 | `App::make('Class')` | `app('Class')` 498 | `->where('column', '=', 1)` | `->where('column', 1)` 499 | `->orderBy('created_at', 'desc')` | `->latest()` 500 | `->orderBy('age', 'desc')` | `->latest('age')` 501 | `->orderBy('created_at', 'asc')` | `->oldest()` 502 | `->select('id', 'name')->get()` | `->get(['id', 'name'])` 503 | `->first()->name` | `->value('name')` 504 | 505 | [🔝 Volver al índice](#índice-de-contenido) 506 | 507 | ### **Utiliza contenedores IoC o fachadas en lugar de new Class** 508 | 509 | La sintaxis new Class crea acoplamientos estrechos y complica las pruebas. Utiliza contenedores IoC o fachadas en lugar de ello. 510 | 511 | Malo: 512 | 513 | ```php 514 | $user = new User; 515 | $user->create($request->validated()); 516 | ``` 517 | 518 | Bueno: 519 | 520 | ```php 521 | public function __construct(User $user) 522 | { 523 | $this->user = $user; 524 | } 525 | 526 | .... 527 | 528 | $this->user->create($request->validated()); 529 | ``` 530 | 531 | [🔝 Volver al índice](#índice-de-contenido) 532 | 533 | ### **No saques informacion directamente del archivo `.env`** 534 | 535 | En lugar de ello, pasa la información a un archivo de configuración y luego utiliza el ayudante `config()` para obtener la información en tu aplicación. 536 | 537 | Malo: 538 | 539 | ```php 540 | $apiKey = env('API_KEY'); 541 | ``` 542 | 543 | Bueno: 544 | 545 | ```php 546 | // config/api.php 547 | 'key' => env('API_KEY'), 548 | 549 | // Utiliza la información 550 | $apiKey = config('api.key'); 551 | ``` 552 | 553 | [🔝 Volver al índice](#índice-de-contenido) 554 | 555 | ### **Guarda las fechas en los formatos estandares. Utiliza los accessors y mutators para modificar el formato** 556 | 557 | Malo: 558 | 559 | ```php 560 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }} 561 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }} 562 | ``` 563 | 564 | Bueno: 565 | 566 | ```php 567 | // Modelo 568 | protected $dates = ['ordered_at', 'created_at', 'updated_at']; 569 | public function getSomeDateAttribute($date) 570 | { 571 | return $date->format('m-d'); 572 | } 573 | 574 | // Vista 575 | {{ $object->ordered_at->toDateString() }} 576 | {{ $object->ordered_at->some_date }} 577 | ``` 578 | 579 | [🔝 Volver al índice](#índice-de-contenido) 580 | 581 | ### **Otras buenas practicas** 582 | 583 | No coloques ningún tipo de lógica en los archivos de rutas. 584 | 585 | Minimiza el uso de PHP vanilla en las plantillas blade. 586 | 587 | [🔝 Volver al índice](#índice-de-contenido) 588 | -------------------------------------------------------------------------------- /thai.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | การแปลภาษา: 4 | 5 | [English](README.md) 6 | 7 | [Nederlands](https://github.com/Protoqol/Beste-Laravel-Praktijken) (by [Protoqol](https://github.com/Protoqol)) 8 | 9 | [한국어](https://github.com/xotrs/laravel-best-practices) (by [cherrypick](https://github.com/xotrs)) 10 | 11 | [Русский](russian.md) 12 | 13 | [日本語](japanese.md) (by [2bo](https://github.com/2bo)) 14 | 15 | [漢語](chinese.md) (by [xiaoyi](https://github.com/Shiloh520)) 16 | 17 | [فارسی](persian.md) (by [amirhossein baghaie](https://github.com/amirbagh75)) 18 | 19 | [Português](https://github.com/jonaselan/laravel-best-practices) (by [jonaselan](https://github.com/jonaselan)) 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 | [ภาษาไทย](thai.md) (by [Kongvut](https://github.com/kongvut/laravel-best-practices)) 32 | 33 | [Deutsche](german.md) (by [Sujal Patel](https://github.com/sujalpatel2209)) 34 | 35 | [Italiana](italian.md) (by [Sujal Patel](https://github.com/sujalpatel2209)) 36 | 37 | เอกสารนี้ไม่ใช่การดัดแปลงหลักการ SOLID หรือรูปแบบและอื่น ๆ ของ Laravel โดยบทความนี้คุณจะพบแนวทางปฏิบัติในการ Coding ที่ดีที่สุด ซึ่งหลายคนมักจะละเลยในงานโปรเจค Laravel จริงของคุณ 38 | 39 | ## เนื้อหา 40 | 41 | [1. แนวทางรูปแบบการตอบกลับเพียงที่เดียว [Single responsibility principle]](#single-responsibility-principle) 42 | 43 | [2. ความอ้วนของ Models และ Controllers ขนาดเล็ก [Fat models, skinny controllers]](#fat-models-skinny-controllers) 44 | 45 | [3. การตรวจสอบ [Validation]](#validation) 46 | 47 | [4. Business logic ควรจะอยู่ในคลาส Service [Business logic should be in service class]](#business-logic-should-be-in-service-class) 48 | 49 | [5. อย่าเรียกตัวเองซ้ำ [Don't repeat yourself (DRY)]](#dont-repeat-yourself-dry) 50 | 51 | [6. ควรจะใช้ Eloquent มากกว่า Query Builder หรือ Raw SQL queries และชอบที่จะใช้ collections มากกว่า arrays [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) 52 | 53 | [7. ความอ้วนเบอะบะของการกำหนดค่า [Mass assignment]](#mass-assignment) 54 | 55 | [8. ไม่ควรที่จะเรียกรัน Queries ในเทมเพลต Blade และใช้เทคนิค Eager loading แทน (เพราะปัญหา N + 1) [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) 56 | 57 | [9. หมั่นคอมเม้นโค้ดของคุณ อีกทั้งควรจะอธิบายการทำงานของเมธอด และชื่อตัวแปร มากกว่าการคอมเม้นเฉย ๆ [Comment your code, but prefer descriptive method and variable names over comments]](#comment-your-code-but-prefer-descriptive-method-and-variable-names-over-comments) 58 | 59 | [10. อย่าใส่ JS และ CSS ในเทมเพลต Blade และอย่าใส่ HTML ใด ๆ ในคลาส PHP [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) 60 | 61 | [11. ใช้ค่าคงที่ Config และไฟล์ภาษา แทนการใส่ข้อความตรง ๆ ลงในโค้ด [Use config and language files, constants instead of text in the code]](#use-config-and-language-files-constants-instead-of-text-in-the-code) 62 | 63 | [12. ใช้เครื่องมือ Laravel มาตรฐานที่ชุมชนยอมรับ [Use standard Laravel tools accepted by community]](#use-standard-laravel-tools-accepted-by-community) 64 | 65 | [13. ปฏิบัติตามแนวทางการตั้งชื่อต่าง ๆ ตามกรอบกติกา Laravel [Follow Laravel naming conventions]](#follow-laravel-naming-conventions) 66 | 67 | [14. ใช้ไวยากรณ์ที่สั้นกว่าและอ่านง่ายกว่าถ้าเป็นไปได้ [Use shorter and more readable syntax where possible]](#use-shorter-and-more-readable-syntax-where-possible) 68 | 69 | [15. ใช้ชุดรูปแบบ IoC หรือ Facades แทนเรียกคลาสใหม่ [Use IoC container or facades instead of new Class]](#use-ioc-container-or-facades-instead-of-new-class) 70 | 71 | [16. อย่าเรียกข้อมูลจากไฟล์ `.env` โดยตรง [Do not get data from the `.env` file directly]](#do-not-get-data-from-the-env-file-directly) 72 | 73 | [17. เก็บวันที่ในรูปแบบมาตรฐาน อีกทั้งใช้ Accessors และ Mutators เพื่อแก้ไขรูปแบบวันที่ [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) 74 | 75 | [- แนวทางการปฏิบัติที่ดีอื่น ๆ [Other good practices]](#other-good-practices) 76 | 77 | ## 78 | 79 | ### 1. แนวทางรูปแบบการตอบกลับเพียงที่เดียว [Single responsibility principle] 80 | 81 | ภายในคลาส ซึ่งในเมธอดควรมีการ Return ค่าเพียงที่เดียว 82 | 83 | ที่แย่: 84 | 85 | ```php 86 | public function getFullNameAttribute() 87 | { 88 | if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) { 89 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 90 | } else { 91 | return $this->first_name[0] . '. ' . $this->last_name; 92 | } 93 | } 94 | ``` 95 | 96 | ที่ดี: 97 | 98 | ```php 99 | public function getFullNameAttribute() 100 | { 101 | return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort(); 102 | } 103 | 104 | public function isVerifiedClient() 105 | { 106 | return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified(); 107 | } 108 | 109 | public function getFullNameLong() 110 | { 111 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 112 | } 113 | 114 | public function getFullNameShort() 115 | { 116 | return $this->first_name[0] . '. ' . $this->last_name; 117 | } 118 | ``` 119 | 120 | [🔝 Back to contents](#contents) 121 | 122 | ### 2. ความอ้วนของ Models และ Controllers ขนาดเล็ก [Fat models, skinny controllers] 123 | 124 | เขียนความสัมพันธ์ฐานข้อมูลทั้งหมด (รวมทั้งแบบ Query Builder หรือ raw SQL queries) ลงใน Model Eloquent หรือในคลาส Repository สร้างเป็น Method สำหรับเรียกใช้งาน เพื่อลดความซ้ำซ้อนของ Logic และขนาด Controllers เพื่อให้มีขนาดเล็กลง 125 | 126 | ที่แย่: 127 | 128 | ```php 129 | public function index() 130 | { 131 | $clients = Client::verified() 132 | ->with(['orders' => function ($q) { 133 | $q->where('created_at', '>', Carbon::today()->subWeek()); 134 | }]) 135 | ->get(); 136 | 137 | return view('index', ['clients' => $clients]); 138 | } 139 | ``` 140 | 141 | ที่ดี: 142 | 143 | ```php 144 | public function index() 145 | { 146 | return view('index', ['clients' => $this->client->getWithNewOrders()]); 147 | } 148 | 149 | class Client extends Model 150 | { 151 | public function getWithNewOrders() 152 | { 153 | return $this->verified() 154 | ->with(['orders' => function ($q) { 155 | $q->where('created_at', '>', Carbon::today()->subWeek()); 156 | }]) 157 | ->get(); 158 | } 159 | } 160 | ``` 161 | 162 | [🔝 Back to contents](#contents) 163 | 164 | ### 3. การตรวจสอบ [Validation] 165 | 166 | ย้ายการตรวจสอบ Validation จาก Controllers ไปที่ Request classes แทน 167 | 168 | ที่แย่: 169 | 170 | ```php 171 | public function store(Request $request) 172 | { 173 | $request->validate([ 174 | 'title' => 'required|unique:posts|max:255', 175 | 'body' => 'required', 176 | 'publish_at' => 'nullable|date', 177 | ]); 178 | 179 | .... 180 | } 181 | ``` 182 | 183 | ที่ดี: 184 | 185 | ```php 186 | public function store(PostRequest $request) 187 | { 188 | .... 189 | } 190 | 191 | class PostRequest extends Request 192 | { 193 | public function rules() 194 | { 195 | return [ 196 | 'title' => 'required|unique:posts|max:255', 197 | 'body' => 'required', 198 | 'publish_at' => 'nullable|date', 199 | ]; 200 | } 201 | } 202 | ``` 203 | 204 | [🔝 Back to contents](#contents) 205 | 206 | ### 4. Business logic ควรจะอยู่ในคลาส Service [Business logic should be in service class] 207 | 208 | เพื่อให้ Method ภายใน Controller มีขนาดที่เล็กลง ดังนั้นควรย้าย Business logic จาก Controllers ไปที่คลาส Service แทน 209 | 210 | ที่แย่: 211 | 212 | ```php 213 | public function store(Request $request) 214 | { 215 | if ($request->hasFile('image')) { 216 | $request->file('image')->move(public_path('images') . 'temp'); 217 | } 218 | 219 | .... 220 | } 221 | ``` 222 | 223 | ที่ดี: 224 | 225 | ```php 226 | public function store(Request $request) 227 | { 228 | $this->articleService->handleUploadedImage($request->file('image')); 229 | 230 | .... 231 | } 232 | 233 | class ArticleService 234 | { 235 | public function handleUploadedImage($image) 236 | { 237 | if (!is_null($image)) { 238 | $image->move(public_path('images') . 'temp'); 239 | } 240 | } 241 | } 242 | ``` 243 | 244 | [🔝 Back to contents](#contents) 245 | 246 | ### 5. อย่าเรียกตัวเองซ้ำ [Don't repeat yourself (DRY)] 247 | 248 | ทำการ Reuse โค้ดเพื่อช่วยหลีกเลี่ยงโค้ดที่ซ้ำซ้อน เช่นเดียวกันกับการ Reuse เทมเพลต Blade โดยสำหรับ Model ให้ใช้ Eloquent scopes ช่วยเป็นต้น 249 | 250 | ที่แย่: 251 | 252 | ```php 253 | public function getActive() 254 | { 255 | return $this->where('verified', 1)->whereNotNull('deleted_at')->get(); 256 | } 257 | 258 | public function getArticles() 259 | { 260 | return $this->whereHas('user', function ($q) { 261 | $q->where('verified', 1)->whereNotNull('deleted_at'); 262 | })->get(); 263 | } 264 | ``` 265 | 266 | ที่ดี: 267 | 268 | ```php 269 | public function scopeActive($q) 270 | { 271 | return $q->where('verified', 1)->whereNotNull('deleted_at'); 272 | } 273 | 274 | public function getActive() 275 | { 276 | return $this->active()->get(); 277 | } 278 | 279 | public function getArticles() 280 | { 281 | return $this->whereHas('user', function ($q) { 282 | $q->active(); 283 | })->get(); 284 | } 285 | ``` 286 | 287 | [🔝 Back to contents](#contents) 288 | 289 | ### 6. ควรที่จะใช้ Eloquent มากกว่า Query Builder หรือ Raw SQL queries และชอบที่จะใช้ collections มากกว่า arrays [Prefer to use Eloquent over using Query Builder and raw SQL queries. Prefer collections over arrays] 290 | 291 | Eloquent ช่วยให้คุณสามารถอ่านโค้ดเข้าใจง่าย 292 | และบำรุงรักษาได้ง่าย นอกจากนี้ Eloquent ยังมีเครื่องมือในตัวที่ยอดเยี่ยม เช่น soft deletes, events, scopes เป็นต้น 293 | 294 | ที่แย่: 295 | 296 | ```sql 297 | SELECT * 298 | FROM `articles` 299 | WHERE EXISTS (SELECT * 300 | FROM `users` 301 | WHERE `articles`.`user_id` = `users`.`id` 302 | AND EXISTS (SELECT * 303 | FROM `profiles` 304 | WHERE `profiles`.`user_id` = `users`.`id`) 305 | AND `users`.`deleted_at` IS NULL) 306 | AND `verified` = '1' 307 | AND `active` = '1' 308 | ORDER BY `created_at` DESC 309 | ``` 310 | 311 | ที่ดี: 312 | 313 | ```php 314 | Article::has('user.profile')->verified()->latest()->get(); 315 | ``` 316 | 317 | [🔝 Back to contents](#contents) 318 | 319 | ### 7. ความอ้วนเบอะบะของการกำหนดค่า [Mass assignment] 320 | 321 | ที่แย่: 322 | 323 | ```php 324 | $article = new Article; 325 | $article->title = $request->title; 326 | $article->content = $request->content; 327 | $article->verified = $request->verified; 328 | // Add category to article 329 | $article->category_id = $category->id; 330 | $article->save(); 331 | ``` 332 | 333 | ที่ดี: 334 | 335 | ```php 336 | $category->article()->create($request->validated()); 337 | ``` 338 | 339 | [🔝 Back to contents](#contents) 340 | 341 | ### 8. ไม่ควรที่จะเรียกรัน Queries ในเทมเพลต Blade และใช้เทคนิค Eager loading แทน (เพราะปัญหา N + 1) [Do not execute queries in Blade templates and use eager loading (N + 1 problem)] 342 | 343 | ที่แย่: (สำหรับข้อมูลตารางผู้ใช้ 100 users โดยจะมีการรันคำสั่ง Queries 101 ครั้ง): 344 | 345 | ```php 346 | @foreach (User::all() as $user) 347 | {{ $user->profile->name }} 348 | @endforeach 349 | ``` 350 | 351 | ที่ดี: (สำหรับข้อมูลตารางผู้ใช้ 100 users โดยจะมีการรันคำสั่ง Queries 2 ครั้ง): 352 | 353 | ```php 354 | $users = User::with('profile')->get(); 355 | 356 | ... 357 | 358 | @foreach ($users as $user) 359 | {{ $user->profile->name }} 360 | @endforeach 361 | ``` 362 | 363 | [🔝 Back to contents](#contents) 364 | 365 | ### 9. หมั่นคอมเม้นโค้ดของคุณ อีกทั้งควรจะอธิบายการทำงานของเมธอด และชื่อตัวแปร มากกว่าการคอมเม้นเฉย ๆ [Comment your code, but prefer descriptive method and variable names over comments] 366 | 367 | ที่แย่: 368 | 369 | ```php 370 | if (count((array) $builder->getQuery()->joins) > 0) 371 | ``` 372 | 373 | ที่ควร: 374 | 375 | ```php 376 | // Determine if there are any joins. 377 | if (count((array) $builder->getQuery()->joins) > 0) 378 | ``` 379 | 380 | ที่ดี: 381 | 382 | ```php 383 | if ($this->hasJoins()) 384 | ``` 385 | 386 | [🔝 Back to contents](#contents) 387 | 388 | ### 10. อย่าใส่ JS และ CSS ในเทมเพลต Blade และอย่าใส่ HTML ใด ๆ ในคลาส PHP [Do not put JS and CSS in Blade templates and do not put any HTML in PHP classes] 389 | 390 | ที่แย่: 391 | 392 | ```php 393 | let article = `{{ json_encode($article) }}`; 394 | ``` 395 | 396 | ที่ควร: 397 | 398 | ```php 399 | 400 | 401 | หรือ 402 | 403 | {{ $article->name }} 404 | ``` 405 | 406 | ที่ไฟล์ Javascript: 407 | 408 | ```javascript 409 | let article = $('#article').val(); 410 | ``` 411 | 412 | [🔝 Back to contents](#contents) 413 | 414 | ### 11. ใช้ค่าคงที่ Config และไฟล์ภาษา แทนการใส่ข้อความตรง ๆ ลงในโค้ด [Use config and language files, constants instead of text in the code] 415 | 416 | ที่แย่: 417 | 418 | ```php 419 | public function isNormal() 420 | { 421 | return $article->type === 'normal'; 422 | } 423 | 424 | return back()->with('message', 'Your article has been added!'); 425 | ``` 426 | 427 | ที่ดี: 428 | 429 | ```php 430 | public function isNormal() 431 | { 432 | return $article->type === Article::TYPE_NORMAL; 433 | } 434 | 435 | return back()->with('message', __('app.article_added')); 436 | ``` 437 | 438 | [🔝 Back to contents](#contents) 439 | 440 | ### 12. ใช้เครื่องมือ Laravel มาตรฐานที่ชุมชนยอมรับ [Use standard Laravel tools accepted by community] 441 | 442 | ควรที่จะใช้ฟังก์ชันมาตรฐานที่ Built-in มาใน Laravel และแพ็คเกจคอมมิวนิตี้ยอดนิยม แทนการใช้แพ็คเกจและเครื่องมือของ 3rd party ปัญหาก็คือนักพัฒนาใหม่ ๆ ที่จะมาพัฒนาร่วมกับแอพของคุณในอนาคต จะต้องเรียนรู้เครื่องมือใหม่ ๆ (3rd party packages) นอกจากนี้โอกาสที่จะได้รับความช่วยเหลือจากชุมชน Laravel จะน้อยอย่างมากเมื่อคุณใช้แพ็คเกจหรือเครื่องมือของ 3rd party อีกทั้งอย่าทำให้ลูกค้าของคุณจ่ายเงินเพิ่มเติมสำหรับสิ่งพวกนั้น (Licenses) 443 | 444 | *เพิ่มเติม: 445 | ปัญหาอีกอย่างของการใช้ 3rd party packages คืออาจจะถูกละเลยการอัพเดทแพ็คเกจสำหรับคุณสมบัติใหม่ ๆ หรือฟังก์ชันความปลอดภัย 446 | 447 | ฟังก์ชัน | เครื่องมือมาตรฐาน | เครื่องมือ 3rd party 448 | ------------ | ------------- | ------------- 449 | Authorization | Policies | Entrust, Sentinel and other packages 450 | Compiling assets | Laravel Mix | Grunt, Gulp, 3rd party packages 451 | Development Environment | Homestead | Docker 452 | Deployment | Laravel Forge | Deployer and other solutions 453 | Unit testing | PHPUnit, Mockery | Phpspec 454 | Browser testing | Laravel Dusk | Codeception 455 | DB | Eloquent | SQL, Doctrine 456 | Templates | Blade | Twig 457 | Working with data | Laravel collections | Arrays 458 | Form validation | Request classes | 3rd party packages, validation in controller 459 | Authentication | Built-in | 3rd party packages, your own solution 460 | API authentication | Laravel Passport | 3rd party JWT and OAuth packages 461 | Creating API | Built-in | Dingo API and similar packages 462 | Working with DB structure | Migrations | Working with DB structure directly 463 | Localization | Built-in | 3rd party packages 464 | Realtime user interfaces | Laravel Echo, Pusher | 3rd party packages and working with WebSockets directly 465 | Generating testing data | Seeder classes, Model Factories, Faker | Creating testing data manually 466 | Task scheduling | Laravel Task Scheduler | Scripts and 3rd party packages 467 | DB | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB 468 | 469 | [🔝 Back to contents](#contents) 470 | 471 | ### 13. ปฏิบัติตามแนวทางการตั้งชื่อต่าง ๆ ตามกรอบกติกา Laravel [Follow Laravel naming conventions] 472 | 473 | ปฏิบัติตามแนวทาง [มาตรฐาน PSR](http://www.php-fig.org/psr/psr-2/). 474 | 475 | นอกจากนี้ให้ปฏิบัติตามแบบแผนการตั้งชื่อที่ชุมชน Laravel ยอมรับ: 476 | 477 | เกี่ยวกับ | แนวทาง | ที่ดี | ที่แย่ 478 | ------------ | ------------- | ------------- | ------------- 479 | Controller | singular | ArticleController | ~~ArticlesController~~ 480 | Route | plural | articles/1 | ~~article/1~~ 481 | Named route | snake_case with dot notation | users.show_active | ~~users.show-active, show-active-users~~ 482 | Model | singular | User | ~~Users~~ 483 | hasOne or belongsTo relationship | singular | articleComment | ~~articleComments, article_comment~~ 484 | All other relationships | plural | articleComments | ~~articleComment, article_comments~~ 485 | Table | plural | article_comments | ~~article_comment, articleComments~~ 486 | Pivot table | singular model names in alphabetical order | article_user | ~~user_article, articles_users~~ 487 | Table column | snake_case without model name | meta_title | ~~MetaTitle; article_meta_title~~ 488 | Model property | snake_case | $model->created_at | ~~$model->createdAt~~ 489 | Foreign key | singular model name with _id suffix | article_id | ~~ArticleId, id_article, articles_id~~ 490 | Primary key | - | id | ~~custom_id~~ 491 | Migration | - | 2017_01_01_000000_create_articles_table | ~~2017_01_01_000000_articles~~ 492 | Method | camelCase | getAll | ~~get_all~~ 493 | Method in resource controller | [table](https://laravel.com/docs/master/controllers#resource-controllers) | store | ~~saveArticle~~ 494 | Method in test class | camelCase | testGuestCannotSeeArticle | ~~test_guest_cannot_see_article~~ 495 | Variable | camelCase | $articlesWithAuthor | ~~$articles_with_author~~ 496 | Collection | descriptive, plural | $activeUsers = User::active()->get() | ~~$active, $data~~ 497 | Object | descriptive, singular | $activeUser = User::active()->first() | ~~$users, $obj~~ 498 | Config and language files index | snake_case | articles_enabled | ~~ArticlesEnabled; articles-enabled~~ 499 | View | kebab-case | show-filtered.blade.php | ~~showFiltered.blade.php, show_filtered.blade.php~~ 500 | Config | snake_case | google_calendar.php | ~~googleCalendar.php, google-calendar.php~~ 501 | Contract (interface) | adjective or noun | Authenticatable | ~~AuthenticationInterface, IAuthentication~~ 502 | Trait | adjective | Notifiable | ~~NotificationTrait~~ 503 | 504 | [🔝 Back to contents](#contents) 505 | 506 | ### 14. ใช้ไวยากรณ์ที่สั้นกว่าและอ่านง่ายกว่าถ้าเป็นไปได้ [Use shorter and more readable syntax where possible] 507 | 508 | ที่แย่: 509 | 510 | ```php 511 | $request->session()->get('cart'); 512 | $request->input('name'); 513 | ``` 514 | 515 | ที่ดี: 516 | 517 | ```php 518 | session('cart'); 519 | $request->name; 520 | ``` 521 | 522 | ตัวอย่างอื่น ๆ เพิ่มเติม: 523 | 524 | Syntax ทั่วไป | Syntax ที่สั้นและอ่านง่ายกว่า 525 | ------------ | ------------- 526 | `Session::get('cart')` | `session('cart')` 527 | `$request->session()->get('cart')` | `session('cart')` 528 | `Session::put('cart', $data)` | `session(['cart' => $data])` 529 | `$request->input('name'), Request::get('name')` | `$request->name, request('name')` 530 | `return Redirect::back()` | `return back()` 531 | `is_null($object->relation) ? null : $object->relation->id` | `optional($object->relation)->id` 532 | `return view('index')->with('title', $title)->with('client', $client)` | `return view('index', compact('title', 'client'))` 533 | `$request->has('value') ? $request->value : 'default';` | `$request->get('value', 'default')` 534 | `Carbon::now(), Carbon::today()` | `now(), today()` 535 | `App::make('Class')` | `app('Class')` 536 | `->where('column', '=', 1)` | `->where('column', 1)` 537 | `->orderBy('created_at', 'desc')` | `->latest()` 538 | `->orderBy('age', 'desc')` | `->latest('age')` 539 | `->orderBy('created_at', 'asc')` | `->oldest()` 540 | `->select('id', 'name')->get()` | `->get(['id', 'name'])` 541 | `->first()->name` | `->value('name')` 542 | 543 | [🔝 Back to contents](#contents) 544 | 545 | ### 15. ใช้ชุดรูปแบบ IoC หรือ Facades แทนเรียกคลาสใหม่ [Use IoC container or facades instead of new Class] 546 | 547 | การเรียกคลาสใหม่ระหว่างคลาสเป็นอะไรที่ซับซ้อนและซ้ำซ้อน แนะนำให้ใช้หลัก IoC หรือ Facades แทน 548 | 549 | *เพิ่มเติม: ในกรณี Controller ของ Model เดียวกันแนะนำให้ทำดังตัวอย่าง 550 | 551 | ที่แย่: 552 | 553 | ```php 554 | $user = new User; 555 | $user->create($request->validated()); 556 | ``` 557 | 558 | ที่ดี: 559 | 560 | ```php 561 | public function __construct(User $user) 562 | { 563 | $this->user = $user; 564 | } 565 | 566 | .... 567 | 568 | $this->user->create($request->validated()); 569 | ``` 570 | 571 | [🔝 Back to contents](#contents) 572 | 573 | ### 16. อย่าเรียกข้อมูลจากไฟล์ `.env` โดยตรง [Do not get data from the `.env` file directly] 574 | 575 | แนะนำให้ส่งผ่านข้อมูลเพื่อกำหนดค่าจากไฟล์ Config แทน จากนั้นเรียกใช้ฟังก์ชันตัวช่วย `config ()` เพื่อเรียกใช้ข้อมูลในแอปพลิเคชัน 576 | 577 | ที่แย่: 578 | 579 | ```php 580 | $apiKey = env('API_KEY'); 581 | ``` 582 | 583 | ที่ดี: 584 | 585 | ```php 586 | // config/api.php 587 | 'key' => env('API_KEY'), 588 | 589 | // Use the data 590 | $apiKey = config('api.key'); 591 | ``` 592 | 593 | [🔝 Back to contents](#contents) 594 | 595 | ### 17. เก็บวันที่ในรูปแบบมาตรฐาน อีกทั้งใช้ Accessors และ Mutators เพื่อแก้ไขรูปแบบวันที่ [Store dates in the standard format. Use accessors and mutators to modify date format] 596 | 597 | ที่แย่: 598 | 599 | ```php 600 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }} 601 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }} 602 | ``` 603 | 604 | ที่ดี: 605 | 606 | ```php 607 | // Model 608 | protected $dates = ['ordered_at', 'created_at', 'updated_at']; 609 | public function getSomeDateAttribute($date) 610 | { 611 | return $date->format('m-d'); 612 | } 613 | 614 | // View 615 | {{ $object->ordered_at->toDateString() }} 616 | {{ $object->ordered_at->some_date }} 617 | ``` 618 | 619 | [🔝 Back to contents](#contents) 620 | 621 | ### - แนวทางการปฏิบัติที่ดีอื่น ๆ [Other good practices] 622 | 623 | - อย่าใส่ Logic ใด ๆ ในไฟล์ routes 624 | - ลดการใช้ Vanilla PHP ให้น้อยที่สุดในเทมเพลต Blade 625 | 626 | [🔝 Back to contents](#contents) 627 | -------------------------------------------------------------------------------- /turkish.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | Çeviriler: 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 | [Русский](russian.md) 10 | 11 | [日本語](japanese.md) (by [2bo](https://github.com/2bo)) 12 | 13 | [漢語](chinese.md) (by [xiaoyi](https://github.com/Shiloh520)) 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 | [Tiếng Việt](https://chungnguyen.xyz/posts/code-laravel-lam-sao-cho-chuan) (by [Chung Nguyễn](https://github.com/nguyentranchung)) 20 | 21 | [Español](spanish.md) (by [César Escudero](https://github.com/cedaesca)) 22 | 23 | [Français](french.md) (by [Mikayil S.](https://github.com/mikayilsrt)) 24 | 25 | [Polski](https://github.com/maciejjeziorski/laravel-best-practices-pl) (by [Maciej Jeziorski](https://github.com/maciejjeziorski)) 26 | 27 | [Türkçe](turkish.md) (by [Burak](https://github.com/ikidnapmyself)) 28 | 29 | [Deutsche](german.md) (by [Sujal Patel](https://github.com/sujalpatel2209)) 30 | 31 | [Italiana](italian.md) (by [Sujal Patel](https://github.com/sujalpatel2209)) 32 | 33 | Bu metin Laravel için SOLID prensipleri, patternler vb. şeylerin uygulaması değildir. Burada, Laravel projelerinde 34 | geliştiriciler tarafından dikkate alınmayan iyi ve kötü pratiklerin karşılaştırmalarını bulacaksınız. 35 | 36 | **Çevirmenin Notu #1: Wikipedia'da Türkçe başlığı olmaması nedeniyle SOLID prensipleri hakkında açıklayıcı bir [Ekşi Sözlük entrysi](https://eksisozluk.com/entry/50438875)* 37 | 38 | ## İçerik 39 | 40 | [Single responsibility principle (Tek sorumluluk prensibi)](#single-responsibility-principle-tek-sorumluluk-prensibi) 41 | 42 | [Büyük modeller, çirkin controllerlar](#büyük-modeller-çirkin-controllerlar) 43 | 44 | [Validation (Veri Doğrulama)](#veri-doğrulama-validasyon) 45 | 46 | [Business logic servis class'ında bulunmalıdır](#business-logic-servis-classında-bulunmalıdır) 47 | 48 | [Kendini tekrar etme (DRY: Don't repeat yourself)](#kendini-tekrar-etme-dry-dont-repeat-yourself) 49 | 50 | [Query Builder ve düz queryler kullanmak yerine Eloquent, array kullanmak yerine Collection kullanın](#query-builder-ve-düz-queryler-kullanmak-yerine-eloquent-array-kullanmak-yerine-collection-kullanın) 51 | 52 | [Mass assignment (Toplu atama)](#mass-assignment-toplu-atama) 53 | 54 | [Blade templatelerinde asla query çalıştırmayın, eager loading kullanın (N + 1 problemi)](#blade-templatelerinde-asla-query-çalıştırmayın-eager-loading-kullanın-n--1-problemi) 55 | 56 | [Koda yorum yazın ancak öncelikli olarak anlamlı method ve değişken isimleri seçin](#koda-yorum-yazın-ancak-öncelikli-olarak-anlamlı-method-ve-değişken-isimleri-seçin) 57 | 58 | [Blade içinde JS ve CSS kullanmayın ve PHP classlarına HTML yazmayın](#blade-içinde-js-ve-css-kullanmayın-ve-php-classlarına-html-yazmayın) 59 | 60 | [Config ve language dosyalarını kullanın, kod içinde ise metin kullanmak yerine constant kullanın](#config-ve-language-dosyalarını-kullanın-kod-içinde-ise-metin-kullanmak-yerine-constant-kullanın) 61 | 62 | [Laravel topluluğu tarafından kabul edilen standart araçları kullanın](#laravel-topluluğu-tarafından-kabul-edilen-standart-araçları-kullanın) 63 | 64 | [Laravel'de isimlendirme](#laravelde-isimlendirme) 65 | 66 | [Mümkün olduğunca daha kısa ve okunabilir syntax kullanın](#mümkün-olduğunca-daha-kısa-ve-okunabilir-syntax-kullanın) 67 | 68 | [new Class kullanımı yerine IoC container ya da facade kullanın](#new-class-kullanımı-yerine-ioc-container-ya-da-facade-kullanın) 69 | 70 | [`.env` dosyasından doğrudan veri çekmeyin](#env-dosyasından-doğrudan-veri-çekmeyin) 71 | 72 | [Tarihleri standart formatta kaydedin. Tarihleri formatlamak için accessor ve mutator kullanın](#tarihleri-standart-formatta-kaydedin-tarihleri-formatlamak-için-accessor-ve-mutator-kullanın) 73 | 74 | [Diğer iyi pratikler](#diğer-iyi-pratikler) 75 | 76 | ### **Single responsibility principle (Tek sorumluluk prensibi)** 77 | 78 | Bir class ya da method'un tek bir görevi ve amacı olmalıdır. 79 | 80 | Kötü: 81 | 82 | ```php 83 | public function getFullNameAttribute() 84 | { 85 | if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) { 86 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 87 | } else { 88 | return $this->first_name[0] . '. ' . $this->last_name; 89 | } 90 | } 91 | ``` 92 | 93 | İyi: 94 | 95 | ```php 96 | public function getFullNameAttribute() 97 | { 98 | return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort(); 99 | } 100 | 101 | public function isVerifiedClient() 102 | { 103 | return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified(); 104 | } 105 | 106 | public function getFullNameLong() 107 | { 108 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 109 | } 110 | 111 | public function getFullNameShort() 112 | { 113 | return $this->first_name[0] . '. ' . $this->last_name; 114 | } 115 | ``` 116 | 117 | [🔝 Başa dön](#içerik) 118 | 119 | ### **Büyük modeller, çirkin controllerlar** 120 | 121 | Bütün database ile ilişkili Query Builder ve raw SQL işlemlerini Eloquent modelinde ya da Repository class'ında tanımlayarak kullanın. 122 | 123 | 124 | Kötü: 125 | 126 | ```php 127 | public function index() 128 | { 129 | $clients = Client::verified() 130 | ->with(['orders' => function ($q) { 131 | $q->where('created_at', '>', Carbon::today()->subWeek()); 132 | }]) 133 | ->get(); 134 | 135 | return view('index', ['clients' => $clients]); 136 | } 137 | ``` 138 | 139 | İyi: 140 | 141 | ```php 142 | public function index() 143 | { 144 | return view('index', ['clients' => $this->client->getWithNewOrders()]); 145 | } 146 | 147 | class Client extends Model 148 | { 149 | public function getWithNewOrders() 150 | { 151 | return $this->verified() 152 | ->with(['orders' => function ($q) { 153 | $q->where('created_at', '>', Carbon::today()->subWeek()); 154 | }]) 155 | ->get(); 156 | } 157 | } 158 | ``` 159 | 160 | [🔝 Başa dön](#içerik) 161 | 162 | ### **Veri Doğrulama, Validasyon** 163 | 164 | Validation işlemlerini controller içinde değil, Request classları içinde yapın. 165 | 166 | Kötü: 167 | 168 | ```php 169 | public function store(Request $request) 170 | { 171 | $request->validate([ 172 | 'title' => 'required|unique:posts|max:255', 173 | 'body' => 'required', 174 | 'publish_at' => 'nullable|date', 175 | ]); 176 | 177 | .... 178 | } 179 | ``` 180 | 181 | İyi: 182 | 183 | ```php 184 | public function store(PostRequest $request) 185 | { 186 | .... 187 | } 188 | 189 | class PostRequest extends Request 190 | { 191 | public function rules() 192 | { 193 | return [ 194 | 'title' => 'required|unique:posts|max:255', 195 | 'body' => 'required', 196 | 'publish_at' => 'nullable|date', 197 | ]; 198 | } 199 | } 200 | ``` 201 | 202 | [🔝 Başa dön](#içerik) 203 | 204 | ### **Business logic servis class'ında bulunmalıdır** 205 | 206 | Bir controller sadece bir görevden sorumlu olmalıdır. Bu nedenle business logic, controllerlar yerine servis classında tanımlanmalıdır. 207 | 208 | Kötü: 209 | 210 | ```php 211 | public function store(Request $request) 212 | { 213 | if ($request->hasFile('image')) { 214 | $request->file('image')->move(public_path('images') . 'temp'); 215 | } 216 | 217 | .... 218 | } 219 | ``` 220 | 221 | İyi: 222 | 223 | ```php 224 | public function store(Request $request) 225 | { 226 | $this->articleService->handleUploadedImage($request->file('image')); 227 | 228 | .... 229 | } 230 | 231 | class ArticleService 232 | { 233 | public function handleUploadedImage($image) 234 | { 235 | if (!is_null($image)) { 236 | $image->move(public_path('images') . 'temp'); 237 | } 238 | } 239 | } 240 | ``` 241 | 242 | [🔝 Başa dön](#içerik) 243 | 244 | ### **Kendini tekrar etme (DRY: Don't repeat yourself)** 245 | 246 | Kullanabildiğiniz sürece kodu tekrar kullanın. SRP (single responsibility principle - tek sorumluluk ilkesi) size kod 247 | tekrarını azaltmanıza da katkı sunar. Blade templatelerini, Eloqunt scopelarını tekrar kullanın. 248 | 249 | Kötü: 250 | 251 | ```php 252 | public function getActive() 253 | { 254 | return $this->where('verified', 1)->whereNotNull('deleted_at')->get(); 255 | } 256 | 257 | public function getArticles() 258 | { 259 | return $this->whereHas('user', function ($q) { 260 | $q->where('verified', 1)->whereNotNull('deleted_at'); 261 | })->get(); 262 | } 263 | ``` 264 | 265 | İyi: 266 | 267 | ```php 268 | public function scopeActive($q) 269 | { 270 | return $q->where('verified', 1)->whereNotNull('deleted_at'); 271 | } 272 | 273 | public function getActive() 274 | { 275 | return $this->active()->get(); 276 | } 277 | 278 | public function getArticles() 279 | { 280 | return $this->whereHas('user', function ($q) { 281 | $q->active(); 282 | })->get(); 283 | } 284 | ``` 285 | 286 | [🔝 Başa dön](#içerik) 287 | 288 | ### **Query Builder ve düz queryler kullanmak yerine Eloquent, array kullanmak yerine Collection kullanın** 289 | 290 | Eloquent okunabilir ve sürdürülebilir kod yazmanıza izin verir. Ayrıca, Eloquent güzel dahili araçlara sahiptir. Örnek olarak; 291 | soft delete, event ve scope özellikleri verilebilir. 292 | 293 | Kötü: 294 | 295 | ```sql 296 | SELECT * 297 | FROM `articles` 298 | WHERE EXISTS (SELECT * 299 | FROM `users` 300 | WHERE `articles`.`user_id` = `users`.`id` 301 | AND EXISTS (SELECT * 302 | FROM `profiles` 303 | WHERE `profiles`.`user_id` = `users`.`id`) 304 | AND `users`.`deleted_at` IS NULL) 305 | AND `verified` = '1' 306 | AND `active` = '1' 307 | ORDER BY `created_at` DESC 308 | ``` 309 | 310 | İyi: 311 | 312 | ```php 313 | Article::has('user.profile')->verified()->latest()->get(); 314 | ``` 315 | 316 | [🔝 Başa dön](#içerik) 317 | 318 | ### **Mass assignment (Toplu atama)** 319 | 320 | Kötü: 321 | 322 | ```php 323 | $article = new Article; 324 | $article->title = $request->title; 325 | $article->content = $request->content; 326 | $article->verified = $request->verified; 327 | // Add category to article 328 | $article->category_id = $category->id; 329 | $article->save(); 330 | ``` 331 | 332 | İyi: 333 | 334 | ```php 335 | $category->article()->create($request->validated()); 336 | ``` 337 | 338 | [🔝 Başa dön](#içerik) 339 | 340 | ### **Blade templatelerinde asla query çalıştırmayın, eager loading kullanın (N + 1 problemi)** 341 | 342 | Kötü (100 kullanıcı için, 101 DB tane query çalıştırılacak): 343 | 344 | ```php 345 | @foreach (User::all() as $user) 346 | {{ $user->profile->name }} 347 | @endforeach 348 | ``` 349 | 350 | İyi (100 kullanıcı için, 2 DB tane query çalıştırılacak): 351 | 352 | ```php 353 | $users = User::with('profile')->get(); 354 | 355 | ... 356 | 357 | @foreach ($users as $user) 358 | {{ $user->profile->name }} 359 | @endforeach 360 | ``` 361 | 362 | [🔝 Başa dön](#içerik) 363 | 364 | ### **Koda yorum yazın ancak öncelikli olarak anlamlı method ve değişken isimleri seçin** 365 | 366 | Kötü: 367 | 368 | ```php 369 | if (count((array) $builder->getQuery()->joins) > 0) 370 | ``` 371 | 372 | Görece İyi: 373 | 374 | ```php 375 | // Determine if there are any joins. 376 | if (count((array) $builder->getQuery()->joins) > 0) 377 | ``` 378 | 379 | İyi: 380 | 381 | ```php 382 | if ($this->hasJoins()) 383 | ``` 384 | 385 | [🔝 Başa dön](#içerik) 386 | 387 | ### **Blade içinde JS ve CSS kullanmayın ve PHP classlarına HTML yazmayın** 388 | 389 | Kötü: 390 | 391 | ```php 392 | let article = `{{ json_encode($article) }}`; 393 | ``` 394 | 395 | Daha İyi: 396 | 397 | ```php 398 | 399 | 400 | Ya da 401 | 402 | {{ $article->name }} 403 | ``` 404 | 405 | Javascript dosyasında: 406 | 407 | ```javascript 408 | let article = $('#article').val(); 409 | ``` 410 | 411 | Data transferi için en iyi yol amaca özel programlanmış PHP'den JS'ye veri aktaran paketleri kullanmaktır. 412 | 413 | [🔝 Başa dön](#içerik) 414 | 415 | ### **Config ve language dosyalarını kullanın, kod içinde ise metin kullanmak yerine constant kullanın** 416 | 417 | Kötü: 418 | 419 | ```php 420 | public function isNormal() 421 | { 422 | return $article->type === 'normal'; 423 | } 424 | 425 | return back()->with('message', 'Makaleniz eklendi!'); 426 | ``` 427 | 428 | İyi: 429 | 430 | ```php 431 | public function isNormal() 432 | { 433 | return $article->type === Article::TYPE_NORMAL; 434 | } 435 | 436 | return back()->with('message', __('app.article_added')); 437 | ``` 438 | 439 | [🔝 Başa dön](#içerik) 440 | 441 | ### **Laravel topluluğu tarafından kabul edilen standart araçları kullanın** 442 | 443 | Geniş topluluk desteği olmayan paketler yerine Laravel'e dahili gelen ya da topluluk tarafından kabul edilmiş araç ve paketleri kullanın. 444 | Bu şekilde koda dahil olan herkesin rahatlıkla projeye katkı sunmasını sağlayabilirsiniz. Ayrıca topluluk desteği düşük olan 445 | paketleri ya da araçları kullandığınızda yardım alabilme ihtimaliniz önemli derecede azalmaktadır. Bu paketleri tekrar 446 | hazırlamak yerine, tercih edilen paketleri kullanın ve bu maliyetlerden kaçının. 447 | 448 | *Çevirmen Notu #2: Tercihen en çok forklanmış, en çok yıldızlanmış, en çok takip edilen repositorylerden faydalanabilirsiniz. Koda yapılan son commit tarihi ve issue kısmında yanıtlanan soru ve cevap dökümleri paketin güncelliği ve ne kadar güncel kalacağı ile ilgili size bilgi sunar.* 449 | 450 | Yapılacak | Standart araç | 3rd party araçlar 451 | ------------ | ------------- | ------------- 452 | Authorization (Yetkilendirme) | Policies | Entrust, Sentinel vb. 453 | Compiling assets (CSS ve JS Derleme) | Laravel Mix | Grunt, Gulp, 3rd party paketler 454 | Geliştirme Ortamı | Homestead | Docker 455 | Deployment | Laravel Forge | Deployer and other solutions 456 | Unit testing | PHPUnit, Mockery | Phpspec 457 | Browser testing | Laravel Dusk | Codeception 458 | DB | Eloquent | SQL, Doctrine 459 | Template | Blade | Twig 460 | Veri işleme | Laravel collections | Arrays 461 | Form doğrulama | Request classları | 3rd party paketler, controllerda doğrulama 462 | Authentication (Doğrulama) | Dahili | 3rd party paketler ya da kendi çözümünüz 463 | API authentication (Doğrulama) | Laravel Passport | 3rd party JWT and OAuth packetleri 464 | API Oluşturma | Dahili | Dingo API vb. 465 | DB Yapısı | Migrations | Doğrudan DB yönetimi 466 | Lokalizasyon (Yerelleştirme) | Dahili | 3rd party paketler 467 | Gerçek zamanlı kullanıcı etkileşimi | Laravel Echo, Pusher | Doğrudan WebSocket kullanan 3rd party paketler 468 | Test verisi oluşturmak | Seeder classları, Model Factoryleri, Faker | Oluşturup manuel test etmek 469 | Görev Zamanlama | Laravel Task Scheduler | Scriptler ve 3rd party paketler 470 | DB | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB 471 | 472 | [🔝 Başa dön](#içerik) 473 | 474 | ### **Laravel'de isimlendirme** 475 | 476 | [PSR standards](http://www.php-fig.org/psr/psr-2/) takip edin. 477 | 478 | Ayrıca, topluluk tarafından kabul gören isimlendirmeler: 479 | 480 | Ne | Nasıl | İyi | Kötü 481 | ------------ | ------------- | ------------- | ------------- 482 | Controller | tekil | ArticleController | ~~ArticlesController~~ 483 | Route | çoğul | articles/1 | ~~article/1~~ 484 | Named route | snake_case ve dot notation (nokta kullanımı) | users.show_active | ~~users.show-active, show-active-users~~ 485 | Model | tekil | User | ~~Users~~ 486 | hasOne or belongsTo relationship | tekil | articleComment | ~~articleComments, article_comment~~ 487 | All other relationships | çoğul | articleComments | ~~articleComment, article_comments~~ 488 | Table | çoğul | article_comments | ~~article_comment, articleComments~~ 489 | Pivot table | tekil model isimleri alfabetik sırada | article_user | ~~user_article, articles_users~~ 490 | Table column | snake_case ve model adı olmadan | meta_title | ~~MetaTitle; article_meta_title~~ 491 | Model property | snake_case | $model->created_at | ~~$model->createdAt~~ 492 | Foreign key | tekil model adı ve _id suffix'i (soneki) | article_id | ~~ArticleId, id_article, articles_id~~ 493 | Primary key | - | id | ~~custom_id~~ 494 | Migration | - | 2017_01_01_000000_create_articles_table | ~~2017_01_01_000000_articles~~ 495 | Method | camelCase | getAll | ~~get_all~~ 496 | Method in resource controller | [table](https://laravel.com/docs/master/controllers#resource-controllers) | store | ~~saveArticle~~ 497 | Method in test class | camelCase | testGuestCannotSeeArticle | ~~test_guest_cannot_see_article~~ 498 | Variable | camelCase | $articlesWithAuthor | ~~$articles_with_author~~ 499 | Collection | tanımlayıcı, çoğul | $activeUsers = User::active()->get() | ~~$active, $data~~ 500 | Object | tanımlayıcı, tekil | $activeUser = User::active()->first() | ~~$users, $obj~~ 501 | Config and language files index | snake_case | articles_enabled | ~~ArticlesEnabled; articles-enabled~~ 502 | View | kebab-case | show-filtered.blade.php | ~~showFiltered.blade.php, show_filtered.blade.php~~ 503 | Config | snake_case | google_calendar.php | ~~googleCalendar.php, google-calendar.php~~ 504 | Contract (interface) | sıfat ya da isim | Authenticatable | ~~AuthenticationInterface, IAuthentication~~ 505 | Trait | sıfat | Notifiable | ~~NotificationTrait~~ 506 | 507 | [🔝 Başa dön](#içerik) 508 | 509 | ### **Mümkün olduğunca daha kısa ve okunabilir syntax kullanın** 510 | 511 | Kötü: 512 | 513 | ```php 514 | $request->session()->get('cart'); 515 | $request->input('name'); 516 | ``` 517 | 518 | İyi: 519 | 520 | ```php 521 | session('cart'); 522 | $request->name; 523 | ``` 524 | 525 | Daha çok örnek: 526 | 527 | Ortak syntax | Kısa ve daha okunabilir syntax 528 | ------------ | ------------- 529 | `Session::get('cart')` | `session('cart')` 530 | `$request->session()->get('cart')` | `session('cart')` 531 | `Session::put('cart', $data)` | `session(['cart' => $data])` 532 | `$request->input('name'), Request::get('name')` | `$request->name, request('name')` 533 | `return Redirect::back()` | `return back()` 534 | `is_null($object->relation) ? null : $object->relation->id` | `optional($object->relation)->id` 535 | `return view('index')->with('title', $title)->with('client', $client)` | `return view('index', compact('title', 'client'))` 536 | `$request->has('value') ? $request->value : 'default';` | `$request->get('value', 'default')` 537 | `Carbon::now(), Carbon::today()` | `now(), today()` 538 | `App::make('Class')` | `app('Class')` 539 | `->where('column', '=', 1)` | `->where('column', 1)` 540 | `->orderBy('created_at', 'desc')` | `->latest()` 541 | `->orderBy('age', 'desc')` | `->latest('age')` 542 | `->orderBy('created_at', 'asc')` | `->oldest()` 543 | `->select('id', 'name')->get()` | `->get(['id', 'name'])` 544 | `->first()->name` | `->value('name')` 545 | 546 | [🔝 Başa dön](#içerik) 547 | 548 | ### **new Class kullanımı yerine IoC container ya da facade kullanın** 549 | 550 | new Class kullanımı classlar arası bağlantıları doğrudan kurar ve test sürecini karmaşıklaştırır. IoC container ya da facade kullanın. 551 | 552 | Kötü: 553 | 554 | ```php 555 | $user = new User; 556 | $user->create($request->validated()); 557 | ``` 558 | 559 | İyi: 560 | 561 | ```php 562 | public function __construct(User $user) 563 | { 564 | $this->user = $user; 565 | } 566 | 567 | .... 568 | 569 | $this->user->create($request->validated()); 570 | ``` 571 | 572 | [🔝 Başa dön](#içerik) 573 | 574 | ### **`.env` dosyasından doğrudan veri çekmeyin** 575 | 576 | Veriyi config dosyasında çağırın ve `config()` helper fonksiyonunu kullanarak uygulama içinde erişin. 577 | 578 | Kötü: 579 | 580 | ```php 581 | $apiKey = env('API_KEY'); 582 | ``` 583 | 584 | İyi: 585 | 586 | ```php 587 | // config/api.php 588 | 'key' => env('API_KEY'), 589 | 590 | // Use the data 591 | $apiKey = config('api.key'); 592 | ``` 593 | 594 | [🔝 Başa dön](#içerik) 595 | 596 | ### **Tarihleri standart formatta kaydedin. Tarihleri formatlamak için accessor ve mutator kullanın** 597 | 598 | Kötü: 599 | 600 | ```php 601 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }} 602 | {{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }} 603 | ``` 604 | 605 | İyi: 606 | 607 | ```php 608 | // Model 609 | protected $dates = ['ordered_at', 'created_at', 'updated_at']; 610 | public function getSomeDateAttribute($date) 611 | { 612 | return $date->format('m-d'); 613 | } 614 | 615 | // View 616 | {{ $object->ordered_at->toDateString() }} 617 | {{ $object->ordered_at->some_date }} 618 | ``` 619 | 620 | [🔝 Başa dön](#içerik) 621 | 622 | ### **Diğer iyi pratikler** 623 | 624 | Route dosyalarına asla logic yazmayın. 625 | 626 | Blade template dosyalarında vanilya PHP (düz PHP) kullanmayın. 627 | 628 | [🔝 Başa dön](#içerik) 629 | --------------------------------------------------------------------------------