├── .gitattributes ├── .github └── workflows │ └── coding_standard.yaml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json └── ecs.php /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-documentation=false 2 | *.md linguist-language=PHP 3 | -------------------------------------------------------------------------------- /.github/workflows/coding_standard.yaml: -------------------------------------------------------------------------------- 1 | name: Coding Standard 2 | 3 | on: 4 | pull_request: null 5 | push: null 6 | 7 | jobs: 8 | coding_standard: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | # see https://github.com/shivammathur/setup-php 14 | - uses: shivammathur/setup-php@v2 15 | with: 16 | php-version: 8.0 17 | coverage: none 18 | 19 | - run: composer install --no-progress --ansi 20 | 21 | - run: vendor/bin/ecs check-markdown README.md --ansi 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | /vendor -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ryan McDermott 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clean Code PHP 2 | 3 | ## Table of Contents 4 | 5 | 1. [Introduction](#introduction) 6 | 2. [Variables](#variables) 7 | * [Use meaningful and pronounceable variable names](#use-meaningful-and-pronounceable-variable-names) 8 | * [Use the same vocabulary for the same type of variable](#use-the-same-vocabulary-for-the-same-type-of-variable) 9 | * [Use searchable names (part 1)](#use-searchable-names-part-1) 10 | * [Use searchable names (part 2)](#use-searchable-names-part-2) 11 | * [Use explanatory variables](#use-explanatory-variables) 12 | * [Avoid nesting too deeply and return early (part 1)](#avoid-nesting-too-deeply-and-return-early-part-1) 13 | * [Avoid nesting too deeply and return early (part 2)](#avoid-nesting-too-deeply-and-return-early-part-2) 14 | * [Avoid Mental Mapping](#avoid-mental-mapping) 15 | * [Don't add unneeded context](#dont-add-unneeded-context) 16 | 3. [Comparison](#comparison) 17 | * [Use identical comparison](#use-identical-comparison) 18 | * [Null coalescing operator](#null-coalescing-operator) 19 | 4. [Functions](#functions) 20 | * [Use default arguments instead of short circuiting or conditionals](#use-default-arguments-instead-of-short-circuiting-or-conditionals) 21 | * [Function arguments (2 or fewer ideally)](#function-arguments-2-or-fewer-ideally) 22 | * [Function names should say what they do](#function-names-should-say-what-they-do) 23 | * [Functions should only be one level of abstraction](#functions-should-only-be-one-level-of-abstraction) 24 | * [Don't use flags as function parameters](#dont-use-flags-as-function-parameters) 25 | * [Avoid Side Effects](#avoid-side-effects) 26 | * [Don't write to global functions](#dont-write-to-global-functions) 27 | * [Don't use a Singleton pattern](#dont-use-a-singleton-pattern) 28 | * [Encapsulate conditionals](#encapsulate-conditionals) 29 | * [Avoid negative conditionals](#avoid-negative-conditionals) 30 | * [Avoid conditionals](#avoid-conditionals) 31 | * [Avoid type-checking (part 1)](#avoid-type-checking-part-1) 32 | * [Avoid type-checking (part 2)](#avoid-type-checking-part-2) 33 | * [Remove dead code](#remove-dead-code) 34 | 5. [Objects and Data Structures](#objects-and-data-structures) 35 | * [Use object encapsulation](#use-object-encapsulation) 36 | * [Make objects have private/protected members](#make-objects-have-privateprotected-members) 37 | 6. [Classes](#classes) 38 | * [Prefer composition over inheritance](#prefer-composition-over-inheritance) 39 | * [Avoid fluent interfaces](#avoid-fluent-interfaces) 40 | * [Prefer final classes](#prefer-final-classes) 41 | 7. [SOLID](#solid) 42 | * [Single Responsibility Principle (SRP)](#single-responsibility-principle-srp) 43 | * [Open/Closed Principle (OCP)](#openclosed-principle-ocp) 44 | * [Liskov Substitution Principle (LSP)](#liskov-substitution-principle-lsp) 45 | * [Interface Segregation Principle (ISP)](#interface-segregation-principle-isp) 46 | * [Dependency Inversion Principle (DIP)](#dependency-inversion-principle-dip) 47 | 8. [Don’t repeat yourself (DRY)](#dont-repeat-yourself-dry) 48 | 9. [Translations](#translations) 49 | 50 | ## Introduction 51 | 52 | Software engineering principles, from Robert C. Martin's book 53 | [*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882), 54 | adapted for PHP. This is not a style guide. It's a guide to producing 55 | readable, reusable, and refactorable software in PHP. 56 | 57 | Not every principle herein has to be strictly followed, and even fewer will be universally 58 | agreed upon. These are guidelines and nothing more, but they are ones codified over many 59 | years of collective experience by the authors of *Clean Code*. 60 | 61 | Inspired from [clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript). 62 | 63 | Although many developers still use PHP 5, most of the examples in this article only work with PHP 7.1+. 64 | 65 | ## Variables 66 | 67 | ### Use meaningful and pronounceable variable names 68 | 69 | **Bad:** 70 | 71 | ```php 72 | $ymdstr = $moment->format('y-m-d'); 73 | ``` 74 | 75 | **Good:** 76 | 77 | ```php 78 | $currentDate = $moment->format('y-m-d'); 79 | ``` 80 | 81 | **[⬆ back to top](#table-of-contents)** 82 | 83 | ### Use the same vocabulary for the same type of variable 84 | 85 | **Bad:** 86 | 87 | ```php 88 | getUserInfo(); 89 | getUserData(); 90 | getUserRecord(); 91 | getUserProfile(); 92 | ``` 93 | 94 | **Good:** 95 | 96 | ```php 97 | getUser(); 98 | ``` 99 | 100 | **[⬆ back to top](#table-of-contents)** 101 | 102 | ### Use searchable names (part 1) 103 | 104 | We will read more code than we will ever write. It's important that the code we do write is 105 | readable and searchable. By *not* naming variables that end up being meaningful for 106 | understanding our program, we hurt our readers. 107 | Make your names searchable. 108 | 109 | **Bad:** 110 | 111 | ```php 112 | // What the heck is 448 for? 113 | $result = $serializer->serialize($data, 448); 114 | ``` 115 | 116 | **Good:** 117 | 118 | ```php 119 | $json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); 120 | ``` 121 | 122 | ### Use searchable names (part 2) 123 | 124 | **Bad:** 125 | 126 | ```php 127 | class User 128 | { 129 | // What the heck is 7 for? 130 | public $access = 7; 131 | } 132 | 133 | // What the heck is 4 for? 134 | if ($user->access & 4) { 135 | // ... 136 | } 137 | 138 | // What's going on here? 139 | $user->access ^= 2; 140 | ``` 141 | 142 | **Good:** 143 | 144 | ```php 145 | class User 146 | { 147 | public const ACCESS_READ = 1; 148 | 149 | public const ACCESS_CREATE = 2; 150 | 151 | public const ACCESS_UPDATE = 4; 152 | 153 | public const ACCESS_DELETE = 8; 154 | 155 | // User as default can read, create and update something 156 | public $access = self::ACCESS_READ | self::ACCESS_CREATE | self::ACCESS_UPDATE; 157 | } 158 | 159 | if ($user->access & User::ACCESS_UPDATE) { 160 | // do edit ... 161 | } 162 | 163 | // Deny access rights to create something 164 | $user->access ^= User::ACCESS_CREATE; 165 | ``` 166 | 167 | **[⬆ back to top](#table-of-contents)** 168 | 169 | ### Use explanatory variables 170 | 171 | **Bad:** 172 | 173 | ```php 174 | $address = 'One Infinite Loop, Cupertino 95014'; 175 | $cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/'; 176 | preg_match($cityZipCodeRegex, $address, $matches); 177 | 178 | saveCityZipCode($matches[1], $matches[2]); 179 | ``` 180 | 181 | **Not bad:** 182 | 183 | It's better, but we are still heavily dependent on regex. 184 | 185 | ```php 186 | $address = 'One Infinite Loop, Cupertino 95014'; 187 | $cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/'; 188 | preg_match($cityZipCodeRegex, $address, $matches); 189 | 190 | [, $city, $zipCode] = $matches; 191 | saveCityZipCode($city, $zipCode); 192 | ``` 193 | 194 | **Good:** 195 | 196 | Decrease dependence on regex by naming subpatterns. 197 | 198 | ```php 199 | $address = 'One Infinite Loop, Cupertino 95014'; 200 | $cityZipCodeRegex = '/^[^,]+,\s*(?.+?)\s*(?\d{5})$/'; 201 | preg_match($cityZipCodeRegex, $address, $matches); 202 | 203 | saveCityZipCode($matches['city'], $matches['zipCode']); 204 | ``` 205 | 206 | **[⬆ back to top](#table-of-contents)** 207 | 208 | ### Avoid nesting too deeply and return early (part 1) 209 | 210 | Too many if-else statements can make your code hard to follow. Explicit is better 211 | than implicit. 212 | 213 | **Bad:** 214 | 215 | ```php 216 | function isShopOpen($day): bool 217 | { 218 | if ($day) { 219 | if (is_string($day)) { 220 | $day = strtolower($day); 221 | if ($day === 'friday') { 222 | return true; 223 | } elseif ($day === 'saturday') { 224 | return true; 225 | } elseif ($day === 'sunday') { 226 | return true; 227 | } 228 | return false; 229 | } 230 | return false; 231 | } 232 | return false; 233 | } 234 | ``` 235 | 236 | **Good:** 237 | 238 | ```php 239 | function isShopOpen(string $day): bool 240 | { 241 | if (empty($day)) { 242 | return false; 243 | } 244 | 245 | $openingDays = ['friday', 'saturday', 'sunday']; 246 | 247 | return in_array(strtolower($day), $openingDays, true); 248 | } 249 | ``` 250 | 251 | **[⬆ back to top](#table-of-contents)** 252 | 253 | ### Avoid nesting too deeply and return early (part 2) 254 | 255 | **Bad:** 256 | 257 | ```php 258 | function fibonacci(int $n) 259 | { 260 | if ($n < 50) { 261 | if ($n !== 0) { 262 | if ($n !== 1) { 263 | return fibonacci($n - 1) + fibonacci($n - 2); 264 | } 265 | return 1; 266 | } 267 | return 0; 268 | } 269 | return 'Not supported'; 270 | } 271 | ``` 272 | 273 | **Good:** 274 | 275 | ```php 276 | function fibonacci(int $n): int 277 | { 278 | if ($n === 0 || $n === 1) { 279 | return $n; 280 | } 281 | 282 | if ($n >= 50) { 283 | throw new Exception('Not supported'); 284 | } 285 | 286 | return fibonacci($n - 1) + fibonacci($n - 2); 287 | } 288 | ``` 289 | 290 | **[⬆ back to top](#table-of-contents)** 291 | 292 | ### Avoid Mental Mapping 293 | 294 | Don’t force the reader of your code to translate what the variable means. 295 | Explicit is better than implicit. 296 | 297 | **Bad:** 298 | 299 | ```php 300 | $l = ['Austin', 'New York', 'San Francisco']; 301 | 302 | for ($i = 0; $i < count($l); $i++) { 303 | $li = $l[$i]; 304 | doStuff(); 305 | doSomeOtherStuff(); 306 | // ... 307 | // ... 308 | // ... 309 | // Wait, what is `$li` for again? 310 | dispatch($li); 311 | } 312 | ``` 313 | 314 | **Good:** 315 | 316 | ```php 317 | $locations = ['Austin', 'New York', 'San Francisco']; 318 | 319 | foreach ($locations as $location) { 320 | doStuff(); 321 | doSomeOtherStuff(); 322 | // ... 323 | // ... 324 | // ... 325 | dispatch($location); 326 | } 327 | ``` 328 | 329 | **[⬆ back to top](#table-of-contents)** 330 | 331 | ### Don't add unneeded context 332 | 333 | If your class/object name tells you something, don't repeat that in your 334 | variable name. 335 | 336 | **Bad:** 337 | 338 | ```php 339 | class Car 340 | { 341 | public $carMake; 342 | 343 | public $carModel; 344 | 345 | public $carColor; 346 | 347 | //... 348 | } 349 | ``` 350 | 351 | **Good:** 352 | 353 | ```php 354 | class Car 355 | { 356 | public $make; 357 | 358 | public $model; 359 | 360 | public $color; 361 | 362 | //... 363 | } 364 | ``` 365 | 366 | **[⬆ back to top](#table-of-contents)** 367 | 368 | ## Comparison 369 | 370 | ### Use [identical comparison](http://php.net/manual/en/language.operators.comparison.php) 371 | 372 | **Not good:** 373 | 374 | The simple comparison will convert the string into an integer. 375 | 376 | ```php 377 | $a = '42'; 378 | $b = 42; 379 | 380 | if ($a != $b) { 381 | // The expression will always pass 382 | } 383 | ``` 384 | 385 | The comparison `$a != $b` returns `FALSE` but in fact it's `TRUE`! 386 | The string `42` is different than the integer `42`. 387 | 388 | **Good:** 389 | 390 | The identical comparison will compare type and value. 391 | 392 | ```php 393 | $a = '42'; 394 | $b = 42; 395 | 396 | if ($a !== $b) { 397 | // The expression is verified 398 | } 399 | ``` 400 | 401 | The comparison `$a !== $b` returns `TRUE`. 402 | 403 | **[⬆ back to top](#table-of-contents)** 404 | 405 | ### Null coalescing operator 406 | 407 | Null coalescing is a new operator [introduced in PHP 7](https://www.php.net/manual/en/migration70.new-features.php). The null coalescing operator `??` has been added as syntactic sugar for the common case of needing to use a ternary in conjunction with `isset()`. It returns its first operand if it exists and is not `null`; otherwise it returns its second operand. 408 | 409 | **Bad:** 410 | 411 | ```php 412 | if (isset($_GET['name'])) { 413 | $name = $_GET['name']; 414 | } elseif (isset($_POST['name'])) { 415 | $name = $_POST['name']; 416 | } else { 417 | $name = 'nobody'; 418 | } 419 | ``` 420 | 421 | **Good:** 422 | ```php 423 | $name = $_GET['name'] ?? $_POST['name'] ?? 'nobody'; 424 | ``` 425 | 426 | **[⬆ back to top](#table-of-contents)** 427 | 428 | ## Functions 429 | 430 | ### Use default arguments instead of short circuiting or conditionals 431 | 432 | **Not good:** 433 | 434 | This is not good because `$breweryName` can be `NULL`. 435 | 436 | ```php 437 | function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void 438 | { 439 | // ... 440 | } 441 | ``` 442 | 443 | **Not bad:** 444 | 445 | This opinion is more understandable than the previous version, but it better controls the value of the variable. 446 | 447 | ```php 448 | function createMicrobrewery($name = null): void 449 | { 450 | $breweryName = $name ?: 'Hipster Brew Co.'; 451 | // ... 452 | } 453 | ``` 454 | 455 | **Good:** 456 | 457 | You can use [type hinting](https://www.php.net/manual/en/language.types.declarations.php) and be sure that the `$breweryName` will not be `NULL`. 458 | 459 | ```php 460 | function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void 461 | { 462 | // ... 463 | } 464 | ``` 465 | 466 | **[⬆ back to top](#table-of-contents)** 467 | 468 | ### Function arguments (2 or fewer ideally) 469 | 470 | Limiting the amount of function parameters is incredibly important because it makes 471 | testing your function easier. Having more than three leads to a combinatorial explosion 472 | where you have to test tons of different cases with each separate argument. 473 | 474 | Zero arguments is the ideal case. One or two arguments is ok, and three should be avoided. 475 | Anything more than that should be consolidated. Usually, if you have more than two 476 | arguments then your function is trying to do too much. In cases where it's not, most 477 | of the time a higher-level object will suffice as an argument. 478 | 479 | **Bad:** 480 | 481 | ```php 482 | class Questionnaire 483 | { 484 | public function __construct( 485 | string $firstname, 486 | string $lastname, 487 | string $patronymic, 488 | string $region, 489 | string $district, 490 | string $city, 491 | string $phone, 492 | string $email 493 | ) { 494 | // ... 495 | } 496 | } 497 | ``` 498 | 499 | **Good:** 500 | 501 | ```php 502 | class Name 503 | { 504 | private $firstname; 505 | 506 | private $lastname; 507 | 508 | private $patronymic; 509 | 510 | public function __construct(string $firstname, string $lastname, string $patronymic) 511 | { 512 | $this->firstname = $firstname; 513 | $this->lastname = $lastname; 514 | $this->patronymic = $patronymic; 515 | } 516 | 517 | // getters ... 518 | } 519 | 520 | class City 521 | { 522 | private $region; 523 | 524 | private $district; 525 | 526 | private $city; 527 | 528 | public function __construct(string $region, string $district, string $city) 529 | { 530 | $this->region = $region; 531 | $this->district = $district; 532 | $this->city = $city; 533 | } 534 | 535 | // getters ... 536 | } 537 | 538 | class Contact 539 | { 540 | private $phone; 541 | 542 | private $email; 543 | 544 | public function __construct(string $phone, string $email) 545 | { 546 | $this->phone = $phone; 547 | $this->email = $email; 548 | } 549 | 550 | // getters ... 551 | } 552 | 553 | class Questionnaire 554 | { 555 | public function __construct(Name $name, City $city, Contact $contact) 556 | { 557 | // ... 558 | } 559 | } 560 | ``` 561 | 562 | **[⬆ back to top](#table-of-contents)** 563 | 564 | ### Function names should say what they do 565 | 566 | **Bad:** 567 | 568 | ```php 569 | class Email 570 | { 571 | //... 572 | 573 | public function handle(): void 574 | { 575 | mail($this->to, $this->subject, $this->body); 576 | } 577 | } 578 | 579 | $message = new Email(...); 580 | // What is this? A handle for the message? Are we writing to a file now? 581 | $message->handle(); 582 | ``` 583 | 584 | **Good:** 585 | 586 | ```php 587 | class Email 588 | { 589 | //... 590 | 591 | public function send(): void 592 | { 593 | mail($this->to, $this->subject, $this->body); 594 | } 595 | } 596 | 597 | $message = new Email(...); 598 | // Clear and obvious 599 | $message->send(); 600 | ``` 601 | 602 | **[⬆ back to top](#table-of-contents)** 603 | 604 | ### Functions should only be one level of abstraction 605 | 606 | When you have more than one level of abstraction your function is usually 607 | doing too much. Splitting up functions leads to reusability and easier 608 | testing. 609 | 610 | **Bad:** 611 | 612 | ```php 613 | function parseBetterPHPAlternative(string $code): void 614 | { 615 | $regexes = [ 616 | // ... 617 | ]; 618 | 619 | $statements = explode(' ', $code); 620 | $tokens = []; 621 | foreach ($regexes as $regex) { 622 | foreach ($statements as $statement) { 623 | // ... 624 | } 625 | } 626 | 627 | $ast = []; 628 | foreach ($tokens as $token) { 629 | // lex... 630 | } 631 | 632 | foreach ($ast as $node) { 633 | // parse... 634 | } 635 | } 636 | ``` 637 | 638 | **Bad too:** 639 | 640 | We have carried out some of the functionality, but the `parseBetterPHPAlternative()` function is still very complex and not testable. 641 | 642 | ```php 643 | function tokenize(string $code): array 644 | { 645 | $regexes = [ 646 | // ... 647 | ]; 648 | 649 | $statements = explode(' ', $code); 650 | $tokens = []; 651 | foreach ($regexes as $regex) { 652 | foreach ($statements as $statement) { 653 | $tokens[] = /* ... */; 654 | } 655 | } 656 | 657 | return $tokens; 658 | } 659 | 660 | function lexer(array $tokens): array 661 | { 662 | $ast = []; 663 | foreach ($tokens as $token) { 664 | $ast[] = /* ... */; 665 | } 666 | 667 | return $ast; 668 | } 669 | 670 | function parseBetterPHPAlternative(string $code): void 671 | { 672 | $tokens = tokenize($code); 673 | $ast = lexer($tokens); 674 | foreach ($ast as $node) { 675 | // parse... 676 | } 677 | } 678 | ``` 679 | 680 | **Good:** 681 | 682 | The best solution is move out the dependencies of `parseBetterPHPAlternative()` function. 683 | 684 | ```php 685 | class Tokenizer 686 | { 687 | public function tokenize(string $code): array 688 | { 689 | $regexes = [ 690 | // ... 691 | ]; 692 | 693 | $statements = explode(' ', $code); 694 | $tokens = []; 695 | foreach ($regexes as $regex) { 696 | foreach ($statements as $statement) { 697 | $tokens[] = /* ... */; 698 | } 699 | } 700 | 701 | return $tokens; 702 | } 703 | } 704 | 705 | class Lexer 706 | { 707 | public function lexify(array $tokens): array 708 | { 709 | $ast = []; 710 | foreach ($tokens as $token) { 711 | $ast[] = /* ... */; 712 | } 713 | 714 | return $ast; 715 | } 716 | } 717 | 718 | class BetterPHPAlternative 719 | { 720 | private $tokenizer; 721 | private $lexer; 722 | 723 | public function __construct(Tokenizer $tokenizer, Lexer $lexer) 724 | { 725 | $this->tokenizer = $tokenizer; 726 | $this->lexer = $lexer; 727 | } 728 | 729 | public function parse(string $code): void 730 | { 731 | $tokens = $this->tokenizer->tokenize($code); 732 | $ast = $this->lexer->lexify($tokens); 733 | foreach ($ast as $node) { 734 | // parse... 735 | } 736 | } 737 | } 738 | ``` 739 | 740 | **[⬆ back to top](#table-of-contents)** 741 | 742 | ### Don't use flags as function parameters 743 | 744 | Flags tell your user that this function does more than one thing. Functions should 745 | do one thing. Split out your functions if they are following different code paths 746 | based on a boolean. 747 | 748 | **Bad:** 749 | 750 | ```php 751 | function createFile(string $name, bool $temp = false): void 752 | { 753 | if ($temp) { 754 | touch('./temp/' . $name); 755 | } else { 756 | touch($name); 757 | } 758 | } 759 | ``` 760 | 761 | **Good:** 762 | 763 | ```php 764 | function createFile(string $name): void 765 | { 766 | touch($name); 767 | } 768 | 769 | function createTempFile(string $name): void 770 | { 771 | touch('./temp/' . $name); 772 | } 773 | ``` 774 | 775 | **[⬆ back to top](#table-of-contents)** 776 | 777 | ### Avoid Side Effects 778 | 779 | A function produces a side effect if it does anything other than take a value in and 780 | return another value or values. A side effect could be writing to a file, modifying 781 | some global variable, or accidentally wiring all your money to a stranger. 782 | 783 | Now, you do need to have side effects in a program on occasion. Like the previous 784 | example, you might need to write to a file. What you want to do is to centralize where 785 | you are doing this. Don't have several functions and classes that write to a particular 786 | file. Have one service that does it. One and only one. 787 | 788 | The main point is to avoid common pitfalls like sharing state between objects without 789 | any structure, using mutable data types that can be written to by anything, and not 790 | centralizing where your side effects occur. If you can do this, you will be happier 791 | than the vast majority of other programmers. 792 | 793 | **Bad:** 794 | 795 | ```php 796 | // Global variable referenced by following function. 797 | // If we had another function that used this name, now it'd be an array and it could break it. 798 | $name = 'Ryan McDermott'; 799 | 800 | function splitIntoFirstAndLastName(): void 801 | { 802 | global $name; 803 | 804 | $name = explode(' ', $name); 805 | } 806 | 807 | splitIntoFirstAndLastName(); 808 | 809 | var_dump($name); 810 | // ['Ryan', 'McDermott']; 811 | ``` 812 | 813 | **Good:** 814 | 815 | ```php 816 | function splitIntoFirstAndLastName(string $name): array 817 | { 818 | return explode(' ', $name); 819 | } 820 | 821 | $name = 'Ryan McDermott'; 822 | $newName = splitIntoFirstAndLastName($name); 823 | 824 | var_dump($name); 825 | // 'Ryan McDermott'; 826 | 827 | var_dump($newName); 828 | // ['Ryan', 'McDermott']; 829 | ``` 830 | 831 | **[⬆ back to top](#table-of-contents)** 832 | 833 | ### Don't write to global functions 834 | 835 | Polluting globals is a bad practice in many languages because you could clash with another 836 | library and the user of your API would be none-the-wiser until they get an exception in 837 | production. Let's think about an example: what if you wanted to have configuration array? 838 | You could write global function like `config()`, but it could clash with another library 839 | that tried to do the same thing. 840 | 841 | **Bad:** 842 | 843 | ```php 844 | function config(): array 845 | { 846 | return [ 847 | 'foo' => 'bar', 848 | ]; 849 | } 850 | ``` 851 | 852 | **Good:** 853 | 854 | ```php 855 | class Configuration 856 | { 857 | private $configuration = []; 858 | 859 | public function __construct(array $configuration) 860 | { 861 | $this->configuration = $configuration; 862 | } 863 | 864 | public function get(string $key): ?string 865 | { 866 | // null coalescing operator 867 | return $this->configuration[$key] ?? null; 868 | } 869 | } 870 | ``` 871 | 872 | Load configuration and create instance of `Configuration` class 873 | 874 | ```php 875 | $configuration = new Configuration([ 876 | 'foo' => 'bar', 877 | ]); 878 | ``` 879 | 880 | And now you must use instance of `Configuration` in your application. 881 | 882 | **[⬆ back to top](#table-of-contents)** 883 | 884 | ### Don't use a Singleton pattern 885 | 886 | Singleton is an [anti-pattern](https://en.wikipedia.org/wiki/Singleton_pattern). Paraphrased from Brian Button: 887 | 1. They are generally used as a **global instance**, why is that so bad? Because **you hide the dependencies** of your application in your code, instead of exposing them through the interfaces. Making something global to avoid passing it around is a [code smell](https://en.wikipedia.org/wiki/Code_smell). 888 | 2. They violate the [single responsibility principle](#single-responsibility-principle-srp): by virtue of the fact that **they control their own creation and lifecycle**. 889 | 3. They inherently cause code to be tightly [coupled](https://en.wikipedia.org/wiki/Coupling_%28computer_programming%29). This makes faking them out under **test rather difficult** in many cases. 890 | 4. They carry state around for the lifetime of the application. Another hit to testing since **you can end up with a situation where tests need to be ordered** which is a big no for unit tests. Why? Because each unit test should be independent from the other. 891 | 892 | There is also very good thoughts by [Misko Hevery](http://misko.hevery.com/about/) about the [root of problem](http://misko.hevery.com/2008/08/25/root-cause-of-singletons/). 893 | 894 | **Bad:** 895 | 896 | ```php 897 | class DBConnection 898 | { 899 | private static $instance; 900 | 901 | private function __construct(string $dsn) 902 | { 903 | // ... 904 | } 905 | 906 | public static function getInstance(): self 907 | { 908 | if (self::$instance === null) { 909 | self::$instance = new self(); 910 | } 911 | 912 | return self::$instance; 913 | } 914 | 915 | // ... 916 | } 917 | 918 | $singleton = DBConnection::getInstance(); 919 | ``` 920 | 921 | **Good:** 922 | 923 | ```php 924 | class DBConnection 925 | { 926 | public function __construct(string $dsn) 927 | { 928 | // ... 929 | } 930 | 931 | // ... 932 | } 933 | ``` 934 | 935 | Create instance of `DBConnection` class and configure it with [DSN](http://php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters). 936 | 937 | ```php 938 | $connection = new DBConnection($dsn); 939 | ``` 940 | 941 | And now you must use instance of `DBConnection` in your application. 942 | 943 | **[⬆ back to top](#table-of-contents)** 944 | 945 | ### Encapsulate conditionals 946 | 947 | **Bad:** 948 | 949 | ```php 950 | if ($article->state === 'published') { 951 | // ... 952 | } 953 | ``` 954 | 955 | **Good:** 956 | 957 | ```php 958 | if ($article->isPublished()) { 959 | // ... 960 | } 961 | ``` 962 | 963 | **[⬆ back to top](#table-of-contents)** 964 | 965 | ### Avoid negative conditionals 966 | 967 | **Bad:** 968 | 969 | ```php 970 | function isDOMNodeNotPresent(DOMNode $node): bool 971 | { 972 | // ... 973 | } 974 | 975 | if (! isDOMNodeNotPresent($node)) { 976 | // ... 977 | } 978 | ``` 979 | 980 | **Good:** 981 | 982 | ```php 983 | function isDOMNodePresent(DOMNode $node): bool 984 | { 985 | // ... 986 | } 987 | 988 | if (isDOMNodePresent($node)) { 989 | // ... 990 | } 991 | ``` 992 | 993 | **[⬆ back to top](#table-of-contents)** 994 | 995 | ### Avoid conditionals 996 | 997 | This seems like an impossible task. Upon first hearing this, most people say, 998 | "how am I supposed to do anything without an `if` statement?" The answer is that 999 | you can use polymorphism to achieve the same task in many cases. The second 1000 | question is usually, "well that's great but why would I want to do that?" The 1001 | answer is a previous clean code concept we learned: a function should only do 1002 | one thing. When you have classes and functions that have `if` statements, you 1003 | are telling your user that your function does more than one thing. Remember, 1004 | just do one thing. 1005 | 1006 | **Bad:** 1007 | 1008 | ```php 1009 | class Airplane 1010 | { 1011 | // ... 1012 | 1013 | public function getCruisingAltitude(): int 1014 | { 1015 | switch ($this->type) { 1016 | case '777': 1017 | return $this->getMaxAltitude() - $this->getPassengerCount(); 1018 | case 'Air Force One': 1019 | return $this->getMaxAltitude(); 1020 | case 'Cessna': 1021 | return $this->getMaxAltitude() - $this->getFuelExpenditure(); 1022 | } 1023 | } 1024 | } 1025 | ``` 1026 | 1027 | **Good:** 1028 | 1029 | ```php 1030 | interface Airplane 1031 | { 1032 | // ... 1033 | 1034 | public function getCruisingAltitude(): int; 1035 | } 1036 | 1037 | class Boeing777 implements Airplane 1038 | { 1039 | // ... 1040 | 1041 | public function getCruisingAltitude(): int 1042 | { 1043 | return $this->getMaxAltitude() - $this->getPassengerCount(); 1044 | } 1045 | } 1046 | 1047 | class AirForceOne implements Airplane 1048 | { 1049 | // ... 1050 | 1051 | public function getCruisingAltitude(): int 1052 | { 1053 | return $this->getMaxAltitude(); 1054 | } 1055 | } 1056 | 1057 | class Cessna implements Airplane 1058 | { 1059 | // ... 1060 | 1061 | public function getCruisingAltitude(): int 1062 | { 1063 | return $this->getMaxAltitude() - $this->getFuelExpenditure(); 1064 | } 1065 | } 1066 | ``` 1067 | 1068 | **[⬆ back to top](#table-of-contents)** 1069 | 1070 | ### Avoid type-checking (part 1) 1071 | 1072 | PHP is untyped, which means your functions can take any type of argument. 1073 | Sometimes you are bitten by this freedom and it becomes tempting to do 1074 | type-checking in your functions. There are many ways to avoid having to do this. 1075 | The first thing to consider is consistent APIs. 1076 | 1077 | **Bad:** 1078 | 1079 | ```php 1080 | function travelToTexas($vehicle): void 1081 | { 1082 | if ($vehicle instanceof Bicycle) { 1083 | $vehicle->pedalTo(new Location('texas')); 1084 | } elseif ($vehicle instanceof Car) { 1085 | $vehicle->driveTo(new Location('texas')); 1086 | } 1087 | } 1088 | ``` 1089 | 1090 | **Good:** 1091 | 1092 | ```php 1093 | function travelToTexas(Vehicle $vehicle): void 1094 | { 1095 | $vehicle->travelTo(new Location('texas')); 1096 | } 1097 | ``` 1098 | 1099 | **[⬆ back to top](#table-of-contents)** 1100 | 1101 | ### Avoid type-checking (part 2) 1102 | 1103 | If you are working with basic primitive values like strings, integers, and arrays, 1104 | and you use PHP 7+ and you can't use polymorphism but you still feel the need to 1105 | type-check, you should consider 1106 | [type declaration](https://www.php.net/manual/en/language.types.declarations.php) 1107 | or strict mode. It provides you with static typing on top of standard PHP syntax. 1108 | The problem with manually type-checking is that doing it will require so much 1109 | extra verbiage that the faux "type-safety" you get doesn't make up for the lost 1110 | readability. Keep your PHP clean, write good tests, and have good code reviews. 1111 | Otherwise, do all of that but with PHP strict type declaration or strict mode. 1112 | 1113 | **Bad:** 1114 | 1115 | ```php 1116 | function combine($val1, $val2): int 1117 | { 1118 | if (! is_numeric($val1) || ! is_numeric($val2)) { 1119 | throw new Exception('Must be of type Number'); 1120 | } 1121 | 1122 | return $val1 + $val2; 1123 | } 1124 | ``` 1125 | 1126 | **Good:** 1127 | 1128 | ```php 1129 | function combine(int $val1, int $val2): int 1130 | { 1131 | return $val1 + $val2; 1132 | } 1133 | ``` 1134 | 1135 | **[⬆ back to top](#table-of-contents)** 1136 | 1137 | ### Remove dead code 1138 | 1139 | Dead code is just as bad as duplicate code. There's no reason to keep it in 1140 | your codebase. If it's not being called, get rid of it! It will still be safe 1141 | in your version history if you still need it. 1142 | 1143 | **Bad:** 1144 | 1145 | ```php 1146 | function oldRequestModule(string $url): void 1147 | { 1148 | // ... 1149 | } 1150 | 1151 | function newRequestModule(string $url): void 1152 | { 1153 | // ... 1154 | } 1155 | 1156 | $request = newRequestModule($requestUrl); 1157 | inventoryTracker('apples', $request, 'www.inventory-awesome.io'); 1158 | ``` 1159 | 1160 | **Good:** 1161 | 1162 | ```php 1163 | function requestModule(string $url): void 1164 | { 1165 | // ... 1166 | } 1167 | 1168 | $request = requestModule($requestUrl); 1169 | inventoryTracker('apples', $request, 'www.inventory-awesome.io'); 1170 | ``` 1171 | 1172 | **[⬆ back to top](#table-of-contents)** 1173 | 1174 | 1175 | ## Objects and Data Structures 1176 | 1177 | ### Use object encapsulation 1178 | 1179 | In PHP you can set `public`, `protected` and `private` keywords for methods. 1180 | Using it, you can control properties modification on an object. 1181 | 1182 | * When you want to do more beyond getting an object property, you don't have 1183 | to look up and change every accessor in your codebase. 1184 | * Makes adding validation simple when doing a `set`. 1185 | * Encapsulates the internal representation. 1186 | * Easy to add logging and error handling when getting and setting. 1187 | * Inheriting this class, you can override default functionality. 1188 | * You can lazy load your object's properties, let's say getting it from a 1189 | server. 1190 | 1191 | Additionally, this is part of [Open/Closed](#openclosed-principle-ocp) principle. 1192 | 1193 | **Bad:** 1194 | 1195 | ```php 1196 | class BankAccount 1197 | { 1198 | public $balance = 1000; 1199 | } 1200 | 1201 | $bankAccount = new BankAccount(); 1202 | 1203 | // Buy shoes... 1204 | $bankAccount->balance -= 100; 1205 | ``` 1206 | 1207 | **Good:** 1208 | 1209 | ```php 1210 | class BankAccount 1211 | { 1212 | private $balance; 1213 | 1214 | public function __construct(int $balance = 1000) 1215 | { 1216 | $this->balance = $balance; 1217 | } 1218 | 1219 | public function withdraw(int $amount): void 1220 | { 1221 | if ($amount > $this->balance) { 1222 | throw new \Exception('Amount greater than available balance.'); 1223 | } 1224 | 1225 | $this->balance -= $amount; 1226 | } 1227 | 1228 | public function deposit(int $amount): void 1229 | { 1230 | $this->balance += $amount; 1231 | } 1232 | 1233 |    public function getBalance(): int 1234 | { 1235 | return $this->balance; 1236 | } 1237 | } 1238 | 1239 | $bankAccount = new BankAccount(); 1240 | 1241 | // Buy shoes... 1242 | $bankAccount->withdraw($shoesPrice); 1243 | 1244 | // Get balance 1245 | $balance = $bankAccount->getBalance(); 1246 | ``` 1247 | 1248 | **[⬆ back to top](#table-of-contents)** 1249 | 1250 | ### Make objects have private/protected members 1251 | 1252 | * `public` methods and properties are most dangerous for changes, because some outside code may easily rely on them and you can't control what code relies on them. **Modifications in class are dangerous for all users of class.** 1253 | * `protected` modifier are as dangerous as public, because they are available in scope of any child class. This effectively means that difference between public and protected is only in access mechanism, but encapsulation guarantee remains the same. **Modifications in class are dangerous for all descendant classes.** 1254 | * `private` modifier guarantees that code is **dangerous to modify only in boundaries of single class** (you are safe for modifications and you won't have [Jenga effect](http://www.urbandictionary.com/define.php?term=Jengaphobia&defid=2494196)). 1255 | 1256 | Therefore, use `private` by default and `public/protected` when you need to provide access for external classes. 1257 | 1258 | For more information you can read the [blog post](http://fabien.potencier.org/pragmatism-over-theory-protected-vs-private.html) on this topic written by [Fabien Potencier](https://github.com/fabpot). 1259 | 1260 | **Bad:** 1261 | 1262 | ```php 1263 | class Employee 1264 | { 1265 | public $name; 1266 | 1267 | public function __construct(string $name) 1268 | { 1269 | $this->name = $name; 1270 | } 1271 | } 1272 | 1273 | $employee = new Employee('John Doe'); 1274 | // Employee name: John Doe 1275 | echo 'Employee name: ' . $employee->name; 1276 | ``` 1277 | 1278 | **Good:** 1279 | 1280 | ```php 1281 | class Employee 1282 | { 1283 | private $name; 1284 | 1285 | public function __construct(string $name) 1286 | { 1287 | $this->name = $name; 1288 | } 1289 | 1290 | public function getName(): string 1291 | { 1292 | return $this->name; 1293 | } 1294 | } 1295 | 1296 | $employee = new Employee('John Doe'); 1297 | // Employee name: John Doe 1298 | echo 'Employee name: ' . $employee->getName(); 1299 | ``` 1300 | 1301 | **[⬆ back to top](#table-of-contents)** 1302 | 1303 | ## Classes 1304 | 1305 | ### Prefer composition over inheritance 1306 | 1307 | As stated famously in [*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) by the Gang of Four, 1308 | you should prefer composition over inheritance where you can. There are lots of 1309 | good reasons to use inheritance and lots of good reasons to use composition. 1310 | The main point for this maxim is that if your mind instinctively goes for 1311 | inheritance, try to think if composition could model your problem better. In some 1312 | cases it can. 1313 | 1314 | You might be wondering then, "when should I use inheritance?" It 1315 | depends on your problem at hand, but this is a decent list of when inheritance 1316 | makes more sense than composition: 1317 | 1318 | 1. Your inheritance represents an "is-a" relationship and not a "has-a" 1319 | relationship (Human->Animal vs. User->UserDetails). 1320 | 2. You can reuse code from the base classes (Humans can move like all animals). 1321 | 3. You want to make global changes to derived classes by changing a base class. 1322 | (Change the caloric expenditure of all animals when they move). 1323 | 1324 | **Bad:** 1325 | 1326 | ```php 1327 | class Employee 1328 | { 1329 | private $name; 1330 | 1331 | private $email; 1332 | 1333 | public function __construct(string $name, string $email) 1334 | { 1335 | $this->name = $name; 1336 | $this->email = $email; 1337 | } 1338 | 1339 | // ... 1340 | } 1341 | 1342 | // Bad because Employees "have" tax data. 1343 | // EmployeeTaxData is not a type of Employee 1344 | 1345 | class EmployeeTaxData extends Employee 1346 | { 1347 | private $ssn; 1348 | 1349 | private $salary; 1350 | 1351 | public function __construct(string $name, string $email, string $ssn, string $salary) 1352 | { 1353 | parent::__construct($name, $email); 1354 | 1355 | $this->ssn = $ssn; 1356 | $this->salary = $salary; 1357 | } 1358 | 1359 | // ... 1360 | } 1361 | ``` 1362 | 1363 | **Good:** 1364 | 1365 | ```php 1366 | class EmployeeTaxData 1367 | { 1368 | private $ssn; 1369 | 1370 | private $salary; 1371 | 1372 | public function __construct(string $ssn, string $salary) 1373 | { 1374 | $this->ssn = $ssn; 1375 | $this->salary = $salary; 1376 | } 1377 | 1378 | // ... 1379 | } 1380 | 1381 | class Employee 1382 | { 1383 | private $name; 1384 | 1385 | private $email; 1386 | 1387 | private $taxData; 1388 | 1389 | public function __construct(string $name, string $email) 1390 | { 1391 | $this->name = $name; 1392 | $this->email = $email; 1393 | } 1394 | 1395 | public function setTaxData(EmployeeTaxData $taxData): void 1396 | { 1397 | $this->taxData = $taxData; 1398 | } 1399 | 1400 | // ... 1401 | } 1402 | ``` 1403 | 1404 | **[⬆ back to top](#table-of-contents)** 1405 | 1406 | ### Avoid fluent interfaces 1407 | 1408 | A [Fluent interface](https://en.wikipedia.org/wiki/Fluent_interface) is an object 1409 | oriented API that aims to improve the readability of the source code by using 1410 | [Method chaining](https://en.wikipedia.org/wiki/Method_chaining). 1411 | 1412 | While there can be some contexts, frequently builder objects, where this 1413 | pattern reduces the verbosity of the code (for example the [PHPUnit Mock Builder](https://phpunit.de/manual/current/en/test-doubles.html) 1414 | or the [Doctrine Query Builder](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/query-builder.html)), 1415 | more often it comes at some costs: 1416 | 1417 | 1. Breaks [Encapsulation](https://en.wikipedia.org/wiki/Encapsulation_%28object-oriented_programming%29). 1418 | 2. Breaks [Decorators](https://en.wikipedia.org/wiki/Decorator_pattern). 1419 | 3. Is harder to [mock](https://en.wikipedia.org/wiki/Mock_object) in a test suite. 1420 | 4. Makes diffs of commits harder to read. 1421 | 1422 | For more information you can read the full [blog post](https://ocramius.github.io/blog/fluent-interfaces-are-evil/) 1423 | on this topic written by [Marco Pivetta](https://github.com/Ocramius). 1424 | 1425 | **Bad:** 1426 | 1427 | ```php 1428 | class Car 1429 | { 1430 | private $make = 'Honda'; 1431 | 1432 | private $model = 'Accord'; 1433 | 1434 | private $color = 'white'; 1435 | 1436 | public function setMake(string $make): self 1437 | { 1438 | $this->make = $make; 1439 | 1440 | // NOTE: Returning this for chaining 1441 | return $this; 1442 | } 1443 | 1444 | public function setModel(string $model): self 1445 | { 1446 | $this->model = $model; 1447 | 1448 | // NOTE: Returning this for chaining 1449 | return $this; 1450 | } 1451 | 1452 | public function setColor(string $color): self 1453 | { 1454 | $this->color = $color; 1455 | 1456 | // NOTE: Returning this for chaining 1457 | return $this; 1458 | } 1459 | 1460 | public function dump(): void 1461 | { 1462 | var_dump($this->make, $this->model, $this->color); 1463 | } 1464 | } 1465 | 1466 | $car = (new Car()) 1467 | ->setColor('pink') 1468 | ->setMake('Ford') 1469 | ->setModel('F-150') 1470 | ->dump(); 1471 | ``` 1472 | 1473 | **Good:** 1474 | 1475 | ```php 1476 | class Car 1477 | { 1478 | private $make = 'Honda'; 1479 | 1480 | private $model = 'Accord'; 1481 | 1482 | private $color = 'white'; 1483 | 1484 | public function setMake(string $make): void 1485 | { 1486 | $this->make = $make; 1487 | } 1488 | 1489 | public function setModel(string $model): void 1490 | { 1491 | $this->model = $model; 1492 | } 1493 | 1494 | public function setColor(string $color): void 1495 | { 1496 | $this->color = $color; 1497 | } 1498 | 1499 | public function dump(): void 1500 | { 1501 | var_dump($this->make, $this->model, $this->color); 1502 | } 1503 | } 1504 | 1505 | $car = new Car(); 1506 | $car->setColor('pink'); 1507 | $car->setMake('Ford'); 1508 | $car->setModel('F-150'); 1509 | $car->dump(); 1510 | ``` 1511 | 1512 | **[⬆ back to top](#table-of-contents)** 1513 | 1514 | ### Prefer final classes 1515 | 1516 | The `final` keyword should be used whenever possible: 1517 | 1518 | 1. It prevents an uncontrolled inheritance chain. 1519 | 2. It encourages [composition](#prefer-composition-over-inheritance). 1520 | 3. It encourages the [Single Responsibility Principle](#single-responsibility-principle-srp). 1521 | 4. It encourages developers to use your public methods instead of extending the class to get access to protected ones. 1522 | 5. It allows you to change your code without breaking applications that use your class. 1523 | 1524 | The only condition is that your class should implement an interface and no other public methods are defined. 1525 | 1526 | For more informations you can read [the blog post](https://ocramius.github.io/blog/when-to-declare-classes-final/) on this topic written by [Marco Pivetta (Ocramius)](https://ocramius.github.io/). 1527 | 1528 | **Bad:** 1529 | 1530 | ```php 1531 | final class Car 1532 | { 1533 | private $color; 1534 | 1535 | public function __construct($color) 1536 | { 1537 | $this->color = $color; 1538 | } 1539 | 1540 | /** 1541 | * @return string The color of the vehicle 1542 | */ 1543 | public function getColor() 1544 | { 1545 | return $this->color; 1546 | } 1547 | } 1548 | ``` 1549 | 1550 | **Good:** 1551 | 1552 | ```php 1553 | interface Vehicle 1554 | { 1555 | /** 1556 | * @return string The color of the vehicle 1557 | */ 1558 | public function getColor(); 1559 | } 1560 | 1561 | final class Car implements Vehicle 1562 | { 1563 | private $color; 1564 | 1565 | public function __construct($color) 1566 | { 1567 | $this->color = $color; 1568 | } 1569 | 1570 | public function getColor() 1571 | { 1572 | return $this->color; 1573 | } 1574 | } 1575 | ``` 1576 | 1577 | **[⬆ back to top](#table-of-contents)** 1578 | 1579 | ## SOLID 1580 | 1581 | **SOLID** is the mnemonic acronym introduced by Michael Feathers for the first five principles named by Robert Martin, which meant five basic principles of object-oriented programming and design. 1582 | 1583 | * [S: Single Responsibility Principle (SRP)](#single-responsibility-principle-srp) 1584 | * [O: Open/Closed Principle (OCP)](#openclosed-principle-ocp) 1585 | * [L: Liskov Substitution Principle (LSP)](#liskov-substitution-principle-lsp) 1586 | * [I: Interface Segregation Principle (ISP)](#interface-segregation-principle-isp) 1587 | * [D: Dependency Inversion Principle (DIP)](#dependency-inversion-principle-dip) 1588 | 1589 | ### Single Responsibility Principle (SRP) 1590 | 1591 | As stated in Clean Code, "There should never be more than one reason for a class 1592 | to change". It's tempting to jam-pack a class with a lot of functionality, like 1593 | when you can only take one suitcase on your flight. The issue with this is 1594 | that your class won't be conceptually cohesive and it will give it many reasons 1595 | to change. Minimizing the amount of times you need to change a class is important. 1596 | It's important because if too much functionality is in one class and you modify a piece of it, 1597 | it can be difficult to understand how that will affect other dependent modules in 1598 | your codebase. 1599 | 1600 | **Bad:** 1601 | 1602 | ```php 1603 | class UserSettings 1604 | { 1605 | private $user; 1606 | 1607 | public function __construct(User $user) 1608 | { 1609 | $this->user = $user; 1610 | } 1611 | 1612 | public function changeSettings(array $settings): void 1613 | { 1614 | if ($this->verifyCredentials()) { 1615 | // ... 1616 | } 1617 | } 1618 | 1619 | private function verifyCredentials(): bool 1620 | { 1621 | // ... 1622 | } 1623 | } 1624 | ``` 1625 | 1626 | **Good:** 1627 | 1628 | ```php 1629 | class UserAuth 1630 | { 1631 | private $user; 1632 | 1633 | public function __construct(User $user) 1634 | { 1635 | $this->user = $user; 1636 | } 1637 | 1638 | public function verifyCredentials(): bool 1639 | { 1640 | // ... 1641 | } 1642 | } 1643 | 1644 | class UserSettings 1645 | { 1646 | private $user; 1647 | 1648 | private $auth; 1649 | 1650 | public function __construct(User $user) 1651 | { 1652 | $this->user = $user; 1653 | $this->auth = new UserAuth($user); 1654 | } 1655 | 1656 | public function changeSettings(array $settings): void 1657 | { 1658 | if ($this->auth->verifyCredentials()) { 1659 | // ... 1660 | } 1661 | } 1662 | } 1663 | ``` 1664 | 1665 | **[⬆ back to top](#table-of-contents)** 1666 | 1667 | ### Open/Closed Principle (OCP) 1668 | 1669 | As stated by Bertrand Meyer, "software entities (classes, modules, functions, 1670 | etc.) should be open for extension, but closed for modification." What does that 1671 | mean though? This principle basically states that you should allow users to 1672 | add new functionalities without changing existing code. 1673 | 1674 | **Bad:** 1675 | 1676 | ```php 1677 | abstract class Adapter 1678 | { 1679 | protected $name; 1680 | 1681 | public function getName(): string 1682 | { 1683 | return $this->name; 1684 | } 1685 | } 1686 | 1687 | class AjaxAdapter extends Adapter 1688 | { 1689 | public function __construct() 1690 | { 1691 | parent::__construct(); 1692 | 1693 | $this->name = 'ajaxAdapter'; 1694 | } 1695 | } 1696 | 1697 | class NodeAdapter extends Adapter 1698 | { 1699 | public function __construct() 1700 | { 1701 | parent::__construct(); 1702 | 1703 | $this->name = 'nodeAdapter'; 1704 | } 1705 | } 1706 | 1707 | class HttpRequester 1708 | { 1709 | private $adapter; 1710 | 1711 | public function __construct(Adapter $adapter) 1712 | { 1713 | $this->adapter = $adapter; 1714 | } 1715 | 1716 | public function fetch(string $url): Promise 1717 | { 1718 | $adapterName = $this->adapter->getName(); 1719 | 1720 | if ($adapterName === 'ajaxAdapter') { 1721 | return $this->makeAjaxCall($url); 1722 | } elseif ($adapterName === 'httpNodeAdapter') { 1723 | return $this->makeHttpCall($url); 1724 | } 1725 | } 1726 | 1727 | private function makeAjaxCall(string $url): Promise 1728 | { 1729 | // request and return promise 1730 | } 1731 | 1732 | private function makeHttpCall(string $url): Promise 1733 | { 1734 | // request and return promise 1735 | } 1736 | } 1737 | ``` 1738 | 1739 | **Good:** 1740 | 1741 | ```php 1742 | interface Adapter 1743 | { 1744 | public function request(string $url): Promise; 1745 | } 1746 | 1747 | class AjaxAdapter implements Adapter 1748 | { 1749 | public function request(string $url): Promise 1750 | { 1751 | // request and return promise 1752 | } 1753 | } 1754 | 1755 | class NodeAdapter implements Adapter 1756 | { 1757 | public function request(string $url): Promise 1758 | { 1759 | // request and return promise 1760 | } 1761 | } 1762 | 1763 | class HttpRequester 1764 | { 1765 | private $adapter; 1766 | 1767 | public function __construct(Adapter $adapter) 1768 | { 1769 | $this->adapter = $adapter; 1770 | } 1771 | 1772 | public function fetch(string $url): Promise 1773 | { 1774 | return $this->adapter->request($url); 1775 | } 1776 | } 1777 | ``` 1778 | 1779 | **[⬆ back to top](#table-of-contents)** 1780 | 1781 | ### Liskov Substitution Principle (LSP) 1782 | 1783 | This is a scary term for a very simple concept. It's formally defined as "If S 1784 | is a subtype of T, then objects of type T may be replaced with objects of type S 1785 | (i.e., objects of type S may substitute objects of type T) without altering any 1786 | of the desirable properties of that program (correctness, task performed, 1787 | etc.)." That's an even scarier definition. 1788 | 1789 | The best explanation for this is if you have a parent class and a child class, 1790 | then the base class and child class can be used interchangeably without getting 1791 | incorrect results. This might still be confusing, so let's take a look at the 1792 | classic Square-Rectangle example. Mathematically, a square is a rectangle, but 1793 | if you model it using the "is-a" relationship via inheritance, you quickly 1794 | get into trouble. 1795 | 1796 | **Bad:** 1797 | 1798 | ```php 1799 | class Rectangle 1800 | { 1801 | protected $width = 0; 1802 | 1803 | protected $height = 0; 1804 | 1805 | public function setWidth(int $width): void 1806 | { 1807 | $this->width = $width; 1808 | } 1809 | 1810 | public function setHeight(int $height): void 1811 | { 1812 | $this->height = $height; 1813 | } 1814 | 1815 | public function getArea(): int 1816 | { 1817 | return $this->width * $this->height; 1818 | } 1819 | } 1820 | 1821 | class Square extends Rectangle 1822 | { 1823 | public function setWidth(int $width): void 1824 | { 1825 | $this->width = $this->height = $width; 1826 | } 1827 | 1828 | public function setHeight(int $height): void 1829 | { 1830 | $this->width = $this->height = $height; 1831 | } 1832 | } 1833 | 1834 | function printArea(Rectangle $rectangle): void 1835 | { 1836 | $rectangle->setWidth(4); 1837 | $rectangle->setHeight(5); 1838 | 1839 | // BAD: Will return 25 for Square. Should be 20. 1840 | echo sprintf('%s has area %d.', get_class($rectangle), $rectangle->getArea()) . PHP_EOL; 1841 | } 1842 | 1843 | $rectangles = [new Rectangle(), new Square()]; 1844 | 1845 | foreach ($rectangles as $rectangle) { 1846 | printArea($rectangle); 1847 | } 1848 | ``` 1849 | 1850 | **Good:** 1851 | 1852 | The best way is separate the quadrangles and allocation of a more general subtype for both shapes. 1853 | 1854 | Despite the apparent similarity of the square and the rectangle, they are different. 1855 | A square has much in common with a rhombus, and a rectangle with a parallelogram, but they are not subtypes. 1856 | A square, a rectangle, a rhombus and a parallelogram are separate shapes with their own properties, albeit similar. 1857 | 1858 | ```php 1859 | interface Shape 1860 | { 1861 | public function getArea(): int; 1862 | } 1863 | 1864 | class Rectangle implements Shape 1865 | { 1866 | private $width = 0; 1867 | private $height = 0; 1868 | 1869 | public function __construct(int $width, int $height) 1870 | { 1871 | $this->width = $width; 1872 | $this->height = $height; 1873 | } 1874 | 1875 | public function getArea(): int 1876 | { 1877 | return $this->width * $this->height; 1878 | } 1879 | } 1880 | 1881 | class Square implements Shape 1882 | { 1883 | private $length = 0; 1884 | 1885 | public function __construct(int $length) 1886 | { 1887 | $this->length = $length; 1888 | } 1889 | 1890 | public function getArea(): int 1891 | { 1892 |        return $this->length ** 2; 1893 |    } 1894 | } 1895 | 1896 | function printArea(Shape $shape): void 1897 | { 1898 | echo sprintf('%s has area %d.', get_class($shape), $shape->getArea()).PHP_EOL; 1899 | } 1900 | 1901 | $shapes = [new Rectangle(4, 5), new Square(5)]; 1902 | 1903 | foreach ($shapes as $shape) { 1904 | printArea($shape); 1905 | } 1906 | ``` 1907 | 1908 | **[⬆ back to top](#table-of-contents)** 1909 | 1910 | ### Interface Segregation Principle (ISP) 1911 | 1912 | ISP states that "Clients should not be forced to depend upon interfaces that 1913 | they do not use." 1914 | 1915 | A good example to look at that demonstrates this principle is for 1916 | classes that require large settings objects. Not requiring clients to set up 1917 | huge amounts of options is beneficial, because most of the time they won't need 1918 | all of the settings. Making them optional helps prevent having a "fat interface". 1919 | 1920 | **Bad:** 1921 | 1922 | ```php 1923 | interface Employee 1924 | { 1925 | public function work(): void; 1926 | 1927 | public function eat(): void; 1928 | } 1929 | 1930 | class HumanEmployee implements Employee 1931 | { 1932 | public function work(): void 1933 | { 1934 | // ....working 1935 | } 1936 | 1937 | public function eat(): void 1938 | { 1939 | // ...... eating in lunch break 1940 | } 1941 | } 1942 | 1943 | class RobotEmployee implements Employee 1944 | { 1945 | public function work(): void 1946 | { 1947 | //.... working much more 1948 | } 1949 | 1950 | public function eat(): void 1951 | { 1952 | //.... robot can't eat, but it must implement this method 1953 | } 1954 | } 1955 | ``` 1956 | 1957 | **Good:** 1958 | 1959 | Not every worker is an employee, but every employee is a worker. 1960 | 1961 | ```php 1962 | interface Workable 1963 | { 1964 | public function work(): void; 1965 | } 1966 | 1967 | interface Feedable 1968 | { 1969 | public function eat(): void; 1970 | } 1971 | 1972 | interface Employee extends Feedable, Workable 1973 | { 1974 | } 1975 | 1976 | class HumanEmployee implements Employee 1977 | { 1978 | public function work(): void 1979 | { 1980 | // ....working 1981 | } 1982 | 1983 | public function eat(): void 1984 | { 1985 | //.... eating in lunch break 1986 | } 1987 | } 1988 | 1989 | // robot can only work 1990 | class RobotEmployee implements Workable 1991 | { 1992 | public function work(): void 1993 | { 1994 | // ....working 1995 | } 1996 | } 1997 | ``` 1998 | 1999 | **[⬆ back to top](#table-of-contents)** 2000 | 2001 | ### Dependency Inversion Principle (DIP) 2002 | 2003 | This principle states two essential things: 2004 | 1. High-level modules should not depend on low-level modules. Both should 2005 | depend on abstractions. 2006 | 2. Abstractions should not depend upon details. Details should depend on 2007 | abstractions. 2008 | 2009 | This can be hard to understand at first, but if you've worked with PHP frameworks (like Symfony), you've seen an implementation of this principle in the form of Dependency 2010 | Injection (DI). While they are not identical concepts, DIP keeps high-level 2011 | modules from knowing the details of its low-level modules and setting them up. 2012 | It can accomplish this through DI. A huge benefit of this is that it reduces 2013 | the coupling between modules. Coupling is a very bad development pattern because 2014 | it makes your code hard to refactor. 2015 | 2016 | **Bad:** 2017 | 2018 | ```php 2019 | class Employee 2020 | { 2021 | public function work(): void 2022 | { 2023 | // ....working 2024 | } 2025 | } 2026 | 2027 | class Robot extends Employee 2028 | { 2029 | public function work(): void 2030 | { 2031 | //.... working much more 2032 | } 2033 | } 2034 | 2035 | class Manager 2036 | { 2037 | private $employee; 2038 | 2039 | public function __construct(Employee $employee) 2040 | { 2041 | $this->employee = $employee; 2042 | } 2043 | 2044 | public function manage(): void 2045 | { 2046 | $this->employee->work(); 2047 | } 2048 | } 2049 | ``` 2050 | 2051 | **Good:** 2052 | 2053 | ```php 2054 | interface Employee 2055 | { 2056 | public function work(): void; 2057 | } 2058 | 2059 | class Human implements Employee 2060 | { 2061 | public function work(): void 2062 | { 2063 | // ....working 2064 | } 2065 | } 2066 | 2067 | class Robot implements Employee 2068 | { 2069 | public function work(): void 2070 | { 2071 | //.... working much more 2072 | } 2073 | } 2074 | 2075 | class Manager 2076 | { 2077 | private $employee; 2078 | 2079 | public function __construct(Employee $employee) 2080 | { 2081 | $this->employee = $employee; 2082 | } 2083 | 2084 | public function manage(): void 2085 | { 2086 | $this->employee->work(); 2087 | } 2088 | } 2089 | ``` 2090 | 2091 | **[⬆ back to top](#table-of-contents)** 2092 | 2093 | ## Don’t repeat yourself (DRY) 2094 | 2095 | Try to observe the [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) principle. 2096 | 2097 | Do your absolute best to avoid duplicate code. Duplicate code is bad because 2098 | it means that there's more than one place to alter something if you need to 2099 | change some logic. 2100 | 2101 | Imagine if you run a restaurant and you keep track of your inventory: all your 2102 | tomatoes, onions, garlic, spices, etc. If you have multiple lists that 2103 | you keep this on, then all have to be updated when you serve a dish with 2104 | tomatoes in them. If you only have one list, there's only one place to update! 2105 | 2106 | Often you have duplicate code because you have two or more slightly 2107 | different things, that share a lot in common, but their differences force you 2108 | to have two or more separate functions that do much of the same things. Removing 2109 | duplicate code means creating an abstraction that can handle this set of different 2110 | things with just one function/module/class. 2111 | 2112 | Getting the abstraction right is critical, that's why you should follow the 2113 | SOLID principles laid out in the [Classes](#classes) section. Bad abstractions can be 2114 | worse than duplicate code, so be careful! Having said this, if you can make 2115 | a good abstraction, do it! Don't repeat yourself, otherwise you'll find yourself 2116 | updating multiple places any time you want to change one thing. 2117 | 2118 | **Bad:** 2119 | 2120 | ```php 2121 | function showDeveloperList(array $developers): void 2122 | { 2123 | foreach ($developers as $developer) { 2124 | $expectedSalary = $developer->calculateExpectedSalary(); 2125 | $experience = $developer->getExperience(); 2126 | $githubLink = $developer->getGithubLink(); 2127 | $data = [$expectedSalary, $experience, $githubLink]; 2128 | 2129 | render($data); 2130 | } 2131 | } 2132 | 2133 | function showManagerList(array $managers): void 2134 | { 2135 | foreach ($managers as $manager) { 2136 | $expectedSalary = $manager->calculateExpectedSalary(); 2137 | $experience = $manager->getExperience(); 2138 | $githubLink = $manager->getGithubLink(); 2139 | $data = [$expectedSalary, $experience, $githubLink]; 2140 | 2141 | render($data); 2142 | } 2143 | } 2144 | ``` 2145 | 2146 | **Good:** 2147 | 2148 | ```php 2149 | function showList(array $employees): void 2150 | { 2151 | foreach ($employees as $employee) { 2152 | $expectedSalary = $employee->calculateExpectedSalary(); 2153 | $experience = $employee->getExperience(); 2154 | $githubLink = $employee->getGithubLink(); 2155 | $data = [$expectedSalary, $experience, $githubLink]; 2156 | 2157 | render($data); 2158 | } 2159 | } 2160 | ``` 2161 | 2162 | **Very good:** 2163 | 2164 | It is better to use a compact version of the code. 2165 | 2166 | ```php 2167 | function showList(array $employees): void 2168 | { 2169 | foreach ($employees as $employee) { 2170 | render([$employee->calculateExpectedSalary(), $employee->getExperience(), $employee->getGithubLink()]); 2171 | } 2172 | } 2173 | ``` 2174 | 2175 | **[⬆ back to top](#table-of-contents)** 2176 | 2177 | ## Translations 2178 | 2179 | This is also available in other languages: 2180 | 2181 | * :cn: **Chinese:** 2182 | * [php-cpm/clean-code-php](https://github.com/php-cpm/clean-code-php) 2183 | * :ru: **Russian:** 2184 | * [peter-gribanov/clean-code-php](https://github.com/peter-gribanov/clean-code-php) 2185 | * :es: **Spanish:** 2186 | * [fikoborquez/clean-code-php](https://github.com/fikoborquez/clean-code-php) 2187 | * :brazil: **Portuguese:** 2188 | * [fabioars/clean-code-php](https://github.com/fabioars/clean-code-php) 2189 | * [jeanjar/clean-code-php](https://github.com/jeanjar/clean-code-php/tree/pt-br) 2190 | * :thailand: **Thai:** 2191 | * [panuwizzle/clean-code-php](https://github.com/panuwizzle/clean-code-php) 2192 | * :fr: **French:** 2193 | * [errorname/clean-code-php](https://github.com/errorname/clean-code-php) 2194 | * :vietnam: **Vietnamese:** 2195 | * [viethuongdev/clean-code-php](https://github.com/viethuongdev/clean-code-php) 2196 | * :kr: **Korean:** 2197 | * [yujineeee/clean-code-php](https://github.com/yujineeee/clean-code-php) 2198 | * :tr: **Turkish:** 2199 | * [anilozmen/clean-code-php](https://github.com/anilozmen/clean-code-php) 2200 | * :iran: **Persian:** 2201 | * [amirshnll/clean-code-php](https://github.com/amirshnll/clean-code-php) 2202 | * :bangladesh: **Bangla:** 2203 | * [nayeemdev/clean-code-php](https://github.com/nayeemdev/clean-code-php) 2204 | * :egypt: **Arabic:** 2205 | * [ahmedalmory/clean-code-php](https://github.com/ahmedalmory/clean-code-php) 2206 | * :jp: **Japanese:** 2207 | * [hayato07/clean-code-php](https://github.com/hayato07/clean-code-php) 2208 | 2209 | **[⬆ back to top](#table-of-contents)** 2210 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jupeter/clean-code-php", 3 | "description": "Clean Code concepts adapted for PHP", 4 | "require": { 5 | "php": ">=7.2", 6 | "symplify/easy-coding-standard": "^9.3" 7 | }, 8 | "scripts": { 9 | "check-cs": "vendor/bin/ecs check-markdown README.md", 10 | "fix-cs": "vendor/bin/ecs check-markdown README.md --fix" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ecs.php: -------------------------------------------------------------------------------- 1 | import(SetList::COMMON); 15 | $containerConfigurator->import(SetList::CLEAN_CODE); 16 | $containerConfigurator->import(SetList::PSR_12); 17 | $containerConfigurator->import(SetList::SYMPLIFY); 18 | 19 | $parameters = $containerConfigurator->parameters(); 20 | $parameters->set(Option::PATHS, [__DIR__ . '/src', __DIR__ . '/config', __DIR__ . '/ecs.php']); 21 | 22 | $parameters->set(Option::SKIP, [ 23 | BlankLineAfterOpeningTagFixer::class => null, 24 | StrictComparisonFixer::class => null, 25 | DeclareStrictTypesFixer::class => null, 26 | ]); 27 | }; 28 | --------------------------------------------------------------------------------