├── README.md ├── images ├── logo-english.png └── logo-russian.png └── russian.md /README.md: -------------------------------------------------------------------------------- 1 | ![Boas Práticas Laravel](/images/logo-english.png?raw=true) 2 | 3 | > Talvez você deva checar o [real-world Laravel example application](https://github.com/alexeymezenin/laravel-realworld-example-app) 4 | 5 | O que é descrito aqui não é uma adaptação ao principio SOLID, padrões e etc. Aqui você irá encontrar as melhores práticas que geralmente são ignoradas em um projeto Laravel na vida real. 6 | 7 | ## Conteúdo 8 | 9 | [Princípio da responsabilidade única](#princípio-da-responsabilidade-única) 10 | 11 | [Models gordos, controllers finos](#models-gordos-controllers-finos) 12 | 13 | [Validação](#validação) 14 | 15 | [Lógica de negócio deve ser posta em classes](#lógica-de-negócio-deve-ser-posta-em-classes) 16 | 17 | [Não se repita (Don't repeat yourself: DRY)](#não-se-repita-dont-repeat-yourself-dry) 18 | 19 | [Usar o Eloquent em vez de Query Builder e consultas SQL puras (raw SQL). Usar collections no lugar de arrays](#usar-o-eloquent-em-vez-de-query-builder-e-consultas-sql-puras-raw-sql-usar-collections-no-lugar-de--arrays) 20 | 21 | [Atribuição em massa](#atribuição-em-massa) 22 | 23 | [Não executar consultas no Blade templates e usar eager loading (N + 1)](#não-executar-consultas-no-blade-templates-e-usar-eager-loading-n--1) 24 | 25 | [Use chunk para tarefas de dados pesadas](#use-chunk-para-tarefas-de-dados-pesadas) 26 | 27 | [Comente seu código, mas prefira um método descritivo e nomes de variáveis em vez de comentários](#comente-seu-código-mas-prefira-um-método-descritivo-e-nomes-de-variáveis-em-vez-de--comentários) 28 | 29 | [Não coloque JS e CSS em templates Blade. Não coloque HTML em classes PHP](#não-coloque-js-e-css-em-templates-blade-não-coloque-html-em-classes-php) 30 | 31 | [Use arquivos de linguagem e configuração. Constantes em vez de texto no código](#use-arquivos-de-linguagem-e-configuração-constantes-em-vez-de-texto-no-código) 32 | 33 | [Use ferramentas padrões do Laravel aceitas pela comunidade](#use-ferramentas-padrões-do-laravel-aceitas-pela-comunidade) 34 | 35 | [Siga a convenção de nomes usada no Laravel](#siga-a-convenção-de-nomes-usada-no-laravel) 36 | 37 | [Tente sempre usar sintaxes pequenas e legíveis](#tente-sempre-usar-sintaxes-pequenas-e-legíveis) 38 | 39 | [Use contaneirs IoC (inversão de controle) ou facades no lugar de classes](#use-contaneirs-ioc-inversão-de-controle-ou-facades-no-lugar-de-classes) 40 | 41 | [Não recupere informações diretamente do `.env`](#não-recupere-informações-diretamente-do-env) 42 | 43 | [Armazene datas em formatos padrões. Use "accessors" e "mutators" para modificar o formato das datas](#armazene-datas-em-formatos-padrões-use-accessors-and-mutators-para-modificar-o-formato-das-datas) 44 | 45 | [Outras boas práticas](#outras-boas-práticas) 46 | 47 | ### **Princípio da responsabilidade única** 48 | 49 | Classes e métodos devem possuir somente uma responsabilidade. 50 | 51 | Ruim: 52 | 53 | ```php 54 | public function getFullNameAttribute() 55 | { 56 | if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) { 57 | return 'Sr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 58 | } else { 59 | return $this->first_name[0] . '. ' . $this->last_name; 60 | } 61 | } 62 | ``` 63 | 64 | Bom: 65 | 66 | ```php 67 | public function getFullNameAttribute() 68 | { 69 | return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort(); 70 | } 71 | 72 | public function isVerifiedClient() 73 | { 74 | return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified(); 75 | } 76 | 77 | public function getFullNameLong() 78 | { 79 | return 'Sr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; 80 | } 81 | 82 | public function getFullNameShort() 83 | { 84 | return $this->first_name[0] . '. ' . $this->last_name; 85 | } 86 | ``` 87 | 88 | [🔝 Voltar para o início](#conteúdo) 89 | 90 | ### **Models gordos, controllers finos** 91 | 92 | Coloque toda a lógica relacionada a banco em modelos Eloquent ou em repositórios caso você esteja usando Query Builder ou consultas SQL. 93 | 94 | Ruim: 95 | 96 | ```php 97 | public function index() 98 | { 99 | $clients = Client::verified() 100 | ->with(['orders' => function ($q) { 101 | $q->where('created_at', '>', Carbon::today()->subWeek()); 102 | }]) 103 | ->get(); 104 | 105 | return view('index', ['clients' => $clients]); 106 | } 107 | ``` 108 | 109 | Bom: 110 | 111 | ```php 112 | public function index() 113 | { 114 | return view('index', ['clients' => $this->client->getWithNewOrders()]); 115 | } 116 | 117 | class Client extends Model 118 | { 119 | public function getWithNewOrders() 120 | { 121 | return $this->verified() 122 | ->with(['orders' => function ($q) { 123 | $q->where('created_at', '>', Carbon::today()->subWeek()); 124 | }]) 125 | ->get(); 126 | } 127 | } 128 | ``` 129 | 130 | [🔝 Voltar para o início](#conteúdo) 131 | 132 | ### **Validação** 133 | 134 | Não use validações em controllers e sim em classes de Requisição. 135 | 136 | Ruim: 137 | 138 | ```php 139 | public function store(Request $request) 140 | { 141 | $request->validate([ 142 | 'title' => 'required|unique:posts|max:255', 143 | 'body' => 'required', 144 | 'publish_at' => 'nullable|date', 145 | ]); 146 | 147 | .... 148 | } 149 | ``` 150 | 151 | Bom: 152 | 153 | ```php 154 | public function store(PostRequest $request) 155 | { 156 | .... 157 | } 158 | 159 | class PostRequest extends Request 160 | { 161 | public function rules() 162 | { 163 | return [ 164 | 'title' => 'required|unique:posts|max:255', 165 | 'body' => 'required', 166 | 'publish_at' => 'nullable|date', 167 | ]; 168 | } 169 | } 170 | ``` 171 | 172 | [🔝 Voltar para o início](#conteúdo) 173 | 174 | ### **Lógica de negócio deve ser posta em classes** 175 | 176 | Controllers devem ter somente uma responsabilidade, então mova lógica de negócio para outros serviços. 177 | 178 | Ruim: 179 | 180 | ```php 181 | public function store(Request $request) 182 | { 183 | if ($request->hasFile('image')) { 184 | $request->file('image')->move(public_path('images') . 'temp'); 185 | } 186 | 187 | .... 188 | } 189 | ``` 190 | 191 | Bom: 192 | 193 | ```php 194 | public function store(Request $request) 195 | { 196 | $this->articleService->handleUploadedImage($request->file('image')); 197 | 198 | .... 199 | } 200 | 201 | class ArticleService 202 | { 203 | public function handleUploadedImage($image) 204 | { 205 | if (!is_null($image)) { 206 | $image->move(public_path('images') . 'temp'); 207 | } 208 | } 209 | } 210 | ``` 211 | 212 | [🔝 Voltar para o início](#conteúdo) 213 | 214 | ### **Não se repita (Don't repeat yourself: DRY)** 215 | 216 | Reutilize seu código sempre que possível. A ideia da responsabilidade única ajuda você a evitar duplicação. Isso serve também para templates Blade. 217 | 218 | Ruim: 219 | 220 | ```php 221 | public function getActive() 222 | { 223 | return $this->where('verified', 1)->whereNotNull('deleted_at')->get(); 224 | } 225 | 226 | public function getArticles() 227 | { 228 | return $this->whereHas('user', function ($q) { 229 | $q->where('verified', 1)->whereNotNull('deleted_at'); 230 | })->get(); 231 | } 232 | ``` 233 | 234 | Bom: 235 | 236 | ```php 237 | public function scopeActive($q) 238 | { 239 | return $q->where('verified', 1)->whereNotNull('deleted_at'); 240 | } 241 | 242 | public function getActive() 243 | { 244 | return $this->active()->get(); 245 | } 246 | 247 | public function getArticles() 248 | { 249 | return $this->whereHas('user', function ($q) { 250 | $q->active(); 251 | })->get(); 252 | } 253 | ``` 254 | 255 | [🔝 Voltar para o início](#conteúdo) 256 | 257 | ### **Usar o Eloquent em vez de Query Builder e consultas SQL puras (raw SQL). Usar collections no lugar de arrays** 258 | 259 | Eloquent permite que você escreva código legível e manutenível. Além disso, Eloquent possui ferramentas ótimas para implementar "soft deletes", eventos, escopos e etc. 260 | 261 | Ruim: 262 | 263 | ```sql 264 | SELECT * 265 | FROM `articles` 266 | WHERE EXISTS (SELECT * 267 | FROM `users` 268 | WHERE `articles`.`user_id` = `users`.`id` 269 | AND EXISTS (SELECT * 270 | FROM `profiles` 271 | WHERE `profiles`.`user_id` = `users`.`id`) 272 | AND `users`.`deleted_at` IS NULL) 273 | AND `verified` = '1' 274 | AND `active` = '1' 275 | ORDER BY `created_at` DESC 276 | ``` 277 | 278 | Bom: 279 | 280 | ```php 281 | Article::has('user.profile')->verified()->latest()->get(); 282 | ``` 283 | 284 | [🔝 Voltar para o início](#conteúdo) 285 | 286 | ### **Atribuição em massa** 287 | 288 | Ruim: 289 | 290 | ```php 291 | $article = new Article; 292 | $article->title = $request->title; 293 | $article->content = $request->content; 294 | $article->verified = $request->verified; 295 | // Adicionar categoria em artigos 296 | $article->category_id = $category->id; 297 | $article->save(); 298 | ``` 299 | 300 | Bom: 301 | 302 | ```php 303 | $category->article()->create($request->all()); 304 | ``` 305 | 306 | [🔝 Voltar para o início](#conteúdo) 307 | 308 | ### **Não executar consultas no Blade templates e usar eager loading (N + 1)** 309 | 310 | Ruim (para 100 usuários, 101 consultas são feitas): 311 | 312 | ```php 313 | @foreach (User::all() as $user) 314 | {{ $user->profile->name }} 315 | @endforeach 316 | ``` 317 | 318 | Bom (para 100 usuários, duas consultas são feitas): 319 | 320 | ```php 321 | $users = User::with('profile')->get(); 322 | 323 | ... 324 | 325 | @foreach ($users as $user) 326 | {{ $user->profile->name }} 327 | @endforeach 328 | ``` 329 | 330 | [🔝 Voltar para o início](#conteúdo) 331 | 332 | ### **Use chunk para tarefas de dados pesadas** 333 | 334 | Ruim (): 335 | 336 | ```php 337 | $users = $this->get(); 338 | 339 | foreach ($users as $user) { 340 | ... 341 | } 342 | ``` 343 | 344 | Bom: 345 | 346 | ```php 347 | $this->chunk(500, function ($users) { 348 | foreach ($users as $user) { 349 | ... 350 | } 351 | }); 352 | ``` 353 | 354 | [🔝 Voltar para o início](#conteúdo) 355 | 356 | ### **Comente seu código, mas prefira um método descritivo e nomes de variáveis em vez de comentários** 357 | 358 | Ruim: 359 | 360 | ```php 361 | if (count((array) $builder->getQuery()->joins) > 0) 362 | ``` 363 | 364 | Melhor: 365 | 366 | ```php 367 | // Determine se há algum join. 368 | if (count((array) $builder->getQuery()->joins) > 0) 369 | ``` 370 | 371 | Bom: 372 | 373 | ```php 374 | if ($this->hasJoins()) 375 | ``` 376 | 377 | [🔝 Voltar para o início](#conteúdo) 378 | 379 | ### **Não coloque JS e CSS em templates Blade. Não coloque HTML em classes PHP** 380 | 381 | Ruim: 382 | 383 | ```php 384 | let article = `{{ json_encode($article) }}`; 385 | ``` 386 | 387 | Melhor: 388 | 389 | ```php 390 | 391 | 392 | Ou 393 | 394 |