├── images
└── logo-english.png
└── README.md
/images/logo-english.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maciejjeziorski/laravel-best-practices-pl/HEAD/images/logo-english.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [Oryginał w języku angielskim](https://github.com/alexeymezenin/laravel-best-practices) (autorstwa [alexeymezenin](https://github.com/alexeymezenin))
4 |
5 | Niniejszy dokument nie stanowi adaptacji zasad SOLID lub jakichkolwiek wzorców. Znajdziesz tutaj najlepsze praktyki, które zwykle są nieświadomie pomijane podczas tworzenia aplikacji z wykorzystaniem frameworka Laravel.
6 |
7 | ## Zawartość
8 |
9 | [Stosuj zasadę pojedynczej odpowiedzialności](#stosuj-zasadę-pojedynczej-odpowiedzialności)
10 |
11 | [Oszczędzaj kod w kontrolerach, kosztem modeli](#oszczędzaj-kod-w-kontrolerach-kosztem-modeli)
12 |
13 | [Walidacja](#walidacja)
14 |
15 | [Umieszczaj logikę biznesową w serwisach](#umieszczaj-logikę-biznesową-w-serwisach)
16 |
17 | [Zasada reużywalności kodu](#zasada-reużywalności-kodu)
18 |
19 | [Używaj Eloquent'a zamiast Query Builder'a / surowych zapytań SQL. Używaj kolekcji zamiast tablic](#używaj-eloquenta-zamiast-query-buildera--surowych-zapytań-sql-używaj-kolekcji-zamiast-tablic)
20 |
21 | [Masowe przypisywanie (Mass assignment)](#masowe-przypisywanie-mass-assignment)
22 |
23 | [Nie wykonuj zapytań bezpośrednio w szablonach. Używaj funkcjonalności Eager loading'u (problem N + 1)](#nie-wykonuj-zapytań-bezpośrednio-w-szablonach-używaj-funkcjonalności-eager-loadingu-problem-n--1)
24 |
25 | [Komentuj swój kod wszędzie](#komentuj-swój-kod-wszędzie)
26 |
27 | [Nie umieszczaj kodu JS i CSS w szablonach Blade, ani kodu HTML w klasach PHP](#nie-umieszczaj-kodu-js-i-css-w-szablonach-blade-ani-kodu-html-w-klasach-php)
28 |
29 | [Używaj konfiguracji, plików językowych i stałych zamiast czystego tekstu w kodzie](#używaj-konfiguracji-plików-językowych-i-stałych-zamiast-czystego-tekstu-w-kodzie)
30 |
31 | [Używaj paczek i narzędzi preferowanych przez społeczność Laravel'a](#używaj-paczek-i-narzędzi-preferowanych-przez-społeczność-laravela)
32 |
33 | [Stosuj się do konwencji nazewnictwa w Laravelu](#stosuj-się-do-konwencji-nazewnictwa-w-laravelu)
34 |
35 | [Używaj krótszej oraz czytelniejszej składni wszędzie gdzie to możliwe](#używaj-krótszej-oraz-czytelniejszej-składni-wszędzie-gdzie-to-możliwe)
36 |
37 | [Używaj IoC container lub facades zamiast new Class](#używaj-ioc-container-lub-facades-zamiast-new-class)
38 |
39 | [Nie pobieraj danych bezpośrednio z pliku `.env`](#nie-pobieraj-danych-bezpośrednio-z-pliku-env)
40 |
41 | [Przechowuj daty w standardowych formatach. Używaj akcesorów i mutatorów aby wyświetlić je w prawidłowym formacie](#przechowuj-daty-w-standardowych-formatach-używaj-akcesorów-i-mutatorów-aby-wyświetlić-je-w-prawidłowym-formacie)
42 |
43 | [Inne dobre praktyki](#inne-dobre-praktyki)
44 |
45 | ### **Stosuj zasadę pojedynczej odpowiedzialności**
46 |
47 | Każda klasa oraz metoda powinna mieć wyłącznie [jedną odpowiedzialność](https://pl.wikipedia.org/wiki/Zasada_jednej_odpowiedzialności). Innymi słowy powinna być stworzona i wykorzystywana tylko w jednym, określonym i z reguły prostym celu.
48 |
49 | Źle:
50 |
51 | ```php
52 | public function getFullNameAttribute()
53 | {
54 | if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) {
55 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
56 | } else {
57 | return $this->first_name[0] . '. ' . $this->last_name;
58 | }
59 | }
60 | ```
61 |
62 | Dobrze:
63 |
64 | ```php
65 | public function getFullNameAttribute()
66 | {
67 | return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
68 | }
69 |
70 | public function isVerifiedClient()
71 | {
72 | return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified();
73 | }
74 |
75 | public function getFullNameLong()
76 | {
77 | return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
78 | }
79 |
80 | public function getFullNameShort()
81 | {
82 | return $this->first_name[0] . '. ' . $this->last_name;
83 | }
84 | ```
85 |
86 | [🔝 Wróć do zawartości](#zawartość)
87 |
88 | ### **Oszczędzaj kod w kontrolerach, kosztem modeli**
89 |
90 | Jeżeli używasz *Query Builder* lub surowych zapytań SQL, umieść całą logikę bazy danych w modelach (*Eloquent Models*)
91 | lub w klasach z repozytoriami (*Repository Classes*).
92 |
93 | Źle:
94 |
95 | ```php
96 | public function index()
97 | {
98 | $clients = Client::verified()
99 | ->with(['orders' => function ($q) {
100 | $q->where('created_at', '>', Carbon::today()->subWeek());
101 | }])
102 | ->get();
103 |
104 | return view('index', ['clients' => $clients]);
105 | }
106 | ```
107 |
108 | Dobrze:
109 |
110 | ```php
111 | public function index()
112 | {
113 | return view('index', ['clients' => $this->client->getWithNewOrders()]);
114 | }
115 |
116 | class Client extends Model
117 | {
118 | public function getWithNewOrders()
119 | {
120 | return $this->verified()
121 | ->with(['orders' => function ($q) {
122 | $q->where('created_at', '>', Carbon::today()->subWeek());
123 | }])
124 | ->get();
125 | }
126 | }
127 | ```
128 |
129 | [🔝 Wróć do zawartości](#zawartość)
130 |
131 | ### **Walidacja**
132 |
133 | Przenieś logikę odpowiedzialną za walidację z kontrolerów do *Request Classes*.
134 |
135 | Źle:
136 |
137 | ```php
138 | public function store(Request $request)
139 | {
140 | $request->validate([
141 | 'title' => 'required|unique:posts|max:255',
142 | 'body' => 'required',
143 | 'publish_at' => 'nullable|date',
144 | ]);
145 |
146 | ....
147 | }
148 | ```
149 |
150 | Dobrze:
151 |
152 | ```php
153 | public function store(PostRequest $request)
154 | {
155 | ....
156 | }
157 |
158 | class PostRequest extends Request
159 | {
160 | public function rules()
161 | {
162 | return [
163 | 'title' => 'required|unique:posts|max:255',
164 | 'body' => 'required',
165 | 'publish_at' => 'nullable|date',
166 | ];
167 | }
168 | }
169 | ```
170 |
171 | [🔝 Wróć do zawartości](#zawartość)
172 |
173 | ### **Umieszczaj logikę biznesową w serwisach**
174 |
175 | Skomplikowaną logikę biznesową umieszczaj w serwisach (*ServiceContainer Classes*), aby ograniczyć kod w kontrolerach do
176 | niezbędnego minimum.
177 |
178 | Źle:
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 | Dobrze:
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 | [🔝 Wróć do zawartości](#zawartość)
213 |
214 | ### **Zasada reużywalności kodu**
215 |
216 | Staraj się wydzielać powtarzalne części tworzonego kodu, które będzie można wykorzystywać w wielu miejscach aplikacji.
217 | Zwróć uwagę na fakt, że najwięcej reużywalnych bloków kodu można stworzyć w tych obszarach: *Blate Templates*,
218 | *Eloquent Scopes*, *Service Containers*, *Helpers* itd.
219 |
220 | Źle:
221 |
222 | ```php
223 | public function getActive()
224 | {
225 | return $this->where('verified', 1)->whereNotNull('deleted_at')->get();
226 | }
227 |
228 | public function getArticles()
229 | {
230 | return $this->whereHas('user', function ($q) {
231 | $q->where('verified', 1)->whereNotNull('deleted_at');
232 | })->get();
233 | }
234 | ```
235 |
236 | Dobrze:
237 |
238 | ```php
239 | public function scopeActive($q)
240 | {
241 | return $q->where('verified', 1)->whereNotNull('deleted_at');
242 | }
243 |
244 | public function getActive()
245 | {
246 | return $this->active()->get();
247 | }
248 |
249 | public function getArticles()
250 | {
251 | return $this->whereHas('user', function ($q) {
252 | $q->active();
253 | })->get();
254 | }
255 | ```
256 |
257 | [🔝 Wróć do zawartości](#zawartość)
258 |
259 | ### **Używaj Eloquent'a zamiast Query Builder'a / surowych zapytań SQL. Używaj kolekcji zamiast tablic**
260 |
261 | *Eloquent* pozwala na pisanie czytelnego oraz łatwego w utrzymaniu kodu. Ponadto, *Eloquent* posiada wiele przydatnych
262 | wbudowanych narzędzi, takich jak: miękkie usuwanie (*Soft Deletes*), wydarzenia (*Events*), *Scopes* itd.
263 |
264 | Źle:
265 |
266 | ```sql
267 | SELECT *
268 | FROM `articles`
269 | WHERE EXISTS (SELECT *
270 | FROM `users`
271 | WHERE `articles`.`user_id` = `users`.`id`
272 | AND EXISTS (SELECT *
273 | FROM `profiles`
274 | WHERE `profiles`.`user_id` = `users`.`id`)
275 | AND `users`.`deleted_at` IS NULL)
276 | AND `verified` = '1'
277 | AND `active` = '1'
278 | ORDER BY `created_at` DESC
279 | ```
280 |
281 | Dobrze:
282 |
283 | ```php
284 | Article::has('user.profile')->verified()->latest()->get();
285 | ```
286 |
287 | [🔝 Wróć do zawartości](#zawartość)
288 |
289 | ### **Masowe przypisywanie (Mass assignment)**
290 |
291 | Wykorzystuj wbudowaną funkcjonalność *Mass Assignment* - dzięki temu kod stanie się bardziej czytelniejszy. Nie zapomnij
292 | o walidacji danych, a także określeniu polityki bezpieczeństwa pól modelu (*fillable* oraz *guarded*).
293 |
294 | Źle:
295 |
296 | ```php
297 | $article = new Article;
298 | $article->title = $request->title;
299 | $article->content = $request->content;
300 | $article->verified = $request->verified;
301 | // Add category to article
302 | $article->category_id = $category->id;
303 | $article->save();
304 | ```
305 |
306 | Dobrze:
307 |
308 | ```php
309 | $category->article()->create($request->validated());
310 | ```
311 |
312 | [🔝 Wróć do zawartości](#zawartość)
313 |
314 | ### **Nie wykonuj zapytań bezpośrednio w szablonach. Używaj funkcjonalności Eager loading'u (problem N + 1)**
315 |
316 | Źle (dla 100 użytkowników zostanie wykonanych 101 zapytań do bazy danych):
317 |
318 | ```php
319 | @foreach (User::all() as $user)
320 | {{ $user->profile->name }}
321 | @endforeach
322 | ```
323 |
324 | Dobrze (dla 100 użytkowników zostaną wykonane 2 zapytania do bazy danych):
325 |
326 | ```php
327 | $users = User::with('profile')->get();
328 |
329 | ...
330 |
331 | @foreach ($users as $user)
332 | {{ $user->profile->name }}
333 | @endforeach
334 | ```
335 |
336 | [🔝 Wróć do zawartości](#zawartość)
337 |
338 | ### **Komentuj swój kod wszędzie**
339 |
340 | Komentuj swój kod wszędzie gdzie to możliwe, ale postaraj się zastępować tradycyjne komentarze czytelniejszym nazywaniem
341 | metod i zmiennych.
342 |
343 | Źle:
344 |
345 | ```php
346 | if (count((array) $builder->getQuery()->joins) > 0)
347 | ```
348 |
349 | Lepiej:
350 |
351 | ```php
352 | // Determine if there are any joins.
353 | if (count((array) $builder->getQuery()->joins) > 0)
354 | ```
355 |
356 | Dobrze:
357 |
358 | ```php
359 | if ($this->hasJoins())
360 | ```
361 |
362 | [🔝 Wróć do zawartości](#zawartość)
363 |
364 | ### **Nie umieszczaj kodu JS i CSS w szablonach Blade, ani kodu HTML w klasach PHP**
365 |
366 | Źle:
367 |
368 | ```php
369 | let article = `{{ json_encode($article) }}`;
370 | ```
371 |
372 | Lepiej:
373 |
374 | ```php
375 |
376 |
377 | Lub
378 |
379 |