├── README.md
├── images
├── logo-english.png
└── logo-russian.png
└── russian.md
/README.md:
--------------------------------------------------------------------------------
1 | 
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 |