├── 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 | ![Laravel - Najlepsze Praktyki](/images/logo-english.png?raw=true) 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 |