Defensive programming

Defensive programming

Tips & tricks how anybody can defend own PHP code against misuse by others - PHPUG Dresden 5th Meetup 2017

C739f65cecbb38923d95e9b7cc0d2e63?s=128

Tommy Mühle

May 24, 2017
Tweet

Transcript

  1. Tommy Mühle | tommy-muehle.io Defensive programming (with PHP) 1

  2. Tommy Mühle | tommy-muehle.io Tommy Mühle 
 Software Engineer and

    Author 2
  3. Tommy Mühle | tommy-muehle.io Why defensive programming? 3

  4. Tommy Mühle | tommy-muehle.io 4

  5. Tommy Mühle | tommy-muehle.io Defend your code against misuse by

    others 5
  6. Tommy Mühle | tommy-muehle.io Let’s go defense 6

  7. Tommy Mühle | tommy-muehle.io Use PHP7 7

  8. Tommy Mühle | tommy-muehle.io Scalar type declarations 8

  9. Tommy Mühle | tommy-muehle.io 9 class Counter { private $count

    = 0; public function add(int $count) { $this->count += $count; } }
  10. Tommy Mühle | tommy-muehle.io Return type declarations 10

  11. Tommy Mühle | tommy-muehle.io 11 class Customer { private $isVerified

    = false; public function verify() : void { $this->isVerified = true; } public function isVerified() : bool { return $this->isVerified; } }
  12. Tommy Mühle | tommy-muehle.io declare(strict_types=1); 12

  13. Tommy Mühle | tommy-muehle.io 13 https:/ /github.com/krakjoe/autostrict

  14. Tommy Mühle | tommy-muehle.io If you haven’t PHP7 14

  15. Tommy Mühle | tommy-muehle.io Check parameters by yourself 15

  16. Tommy Mühle | tommy-muehle.io 16 class Counter { public function

    add($count) { $this->ensureIsInteger($count); // ... } private function ensureIsInteger($value) { // ... throw new \RuntimeException('...'); } }
  17. Tommy Mühle | tommy-muehle.io 17 https:/ /github.com/beberlei/assert

  18. Tommy Mühle | tommy-muehle.io 18 use Assert\Assertion; class Counter {

    private $count; /** * @param int $count */ public function add($count) { Assertion::integer($count); $this->count += $count; } }
  19. Tommy Mühle | tommy-muehle.io Options for all versions 19

  20. Tommy Mühle | tommy-muehle.io Injections only via constructor 20

  21. Tommy Mühle | tommy-muehle.io 21 class Person { private $address;

    /** * @param Address $address */ public function __construct(Address $address) { $this->address = $address; } }
  22. Tommy Mühle | tommy-muehle.io You can also use named constructors

    22
  23. Tommy Mühle | tommy-muehle.io 23 class Person { // ...

    /** * @param Address $address */ public function __construct(Address $address){ ... } public static function createDetailedPerson( Address $address, Details $details) { $person = new self($address); $person->details = $details; return $person; } }
  24. Tommy Mühle | tommy-muehle.io Setter for everything 24

  25. Tommy Mühle | tommy-muehle.io https:/ /speakerdeck.com/ tommymuehle/why-setters-are-bad 25

  26. Tommy Mühle | tommy-muehle.io Few public methods 26

  27. Tommy Mühle | tommy-muehle.io Use objects instead of primitives 27

  28. Tommy Mühle | tommy-muehle.io What? 28

  29. Tommy Mühle | tommy-muehle.io 29 Avoid primitive types class Person

    { // ... public function addEmail(string $email) { // ... } public function addAddress(array $address) { // ... } }
  30. Tommy Mühle | tommy-muehle.io 30 class Person { // ...

    public function addEmail(EmailAddress $email) { // ... } public function addAddress(Address $address) { // ... } } Use value objects
  31. Tommy Mühle | tommy-muehle.io 31 final class Email { private

    $mailbox; private $host; public function __construct(string $email) { if (false === strpos($email, '@')) { throw new \InvalidArgumentException( 'This does not look like an email!'); } list($this->mailbox, $this->host) = explode('@', $email); } public function __toString() { return sprintf('%s@%s', $this->mailbox, $this->host); } }
  32. Tommy Mühle | tommy-muehle.io Mark classes as final 32

  33. Tommy Mühle | tommy-muehle.io 33 abstract class User { //

    ... } final class GuestUser extends User { // ... }
  34. Tommy Mühle | tommy-muehle.io 33 abstract class User { //

    ... } final class GuestUser extends User { // ... }
  35. Tommy Mühle | tommy-muehle.io Private properties by default 34

  36. Tommy Mühle | tommy-muehle.io No uninitialized properties 35

  37. Tommy Mühle | tommy-muehle.io No mixed properties 36

  38. Tommy Mühle | tommy-muehle.io Avoid traits if you’re unsure 37

  39. Tommy Mühle | tommy-muehle.io 38 trait EventGenerator { private $events;

    public function releaseEvents() { $pendingEvents = $this->events; $this->events = []; return $pendingEvents; } protected function raiseEvent(DomainEvent $event) { $this->events[] = $event; } }
  40. Tommy Mühle | tommy-muehle.io Extremely defensive 39

  41. Tommy Mühle | tommy-muehle.io No mixed return types 40

  42. class Person { private $birthday; /** * @return null|int */

    public function getAge() { if (!isset($this->birthday)) { return null; } // Calculate $age ... return $age; } } Tommy Mühle | tommy-muehle.io 41
  43. Tommy Mühle | tommy-muehle.io 42 class Person { private $birthday;

    /** * @return int * @throws InvalidArgumentException */ public function getAge() { if (!isset($this->birthday)) { throw new InvalidArgumentException('...'); } // Calculate and return $age ... } public function hasBirthday() { return isset($this->birthday); } }
  44. Tommy Mühle | tommy-muehle.io You can also use null objects

    instead 43
  45. Tommy Mühle | tommy-muehle.io Without null object 44

  46. Tommy Mühle | tommy-muehle.io 45 final class Order { /**

    @var Money */ private $price; /** @var Discount */ private $discount; public function getPrice() { $priceWithDiscount = $this->price; if (!$this->discount instanceof Discount) { return $priceWithDiscount; } // ... return new Money($priceWithDiscount); } }
  47. Tommy Mühle | tommy-muehle.io With null object 46

  48. Tommy Mühle | tommy-muehle.io 47 interface Amountable { public function

    getAmount(); } final class HeavyUserDiscount implements Amountable { public function getAmount() { $amount = 0.00; // ... return new Money($amount); } }
  49. Tommy Mühle | tommy-muehle.io 48 interface Amountable { public function

    getAmount(); } final class NoDiscount implements Amountable { public function getAmount() { return new Money(0.00); } }
  50. Tommy Mühle | tommy-muehle.io 49 final class Order { /**

    @var Money */ private $price; /** @var Discount */ private $discount; public function getPrice() { $amount = $this->price->getAmount() - $this->discount->getAmount(); return new Money($amount); } }
  51. Tommy Mühle | tommy-muehle.io No optional parameters 50

  52. class Person { private $address; private $details; public function __construct(

    Address $address, Details $details = null) { $this->address = $address; $this->details = $details; } } Tommy Mühle | tommy-muehle.io 51
  53. Tommy Mühle | tommy-muehle.io 52 class Person { private $address;

    private $details; // ... public function __construct(Address $address) { $this->address = $address; } public function addDetails(Details $details) { $this->details = $details; } }
  54. Tommy Mühle | tommy-muehle.io Block magic method usage 53

  55. Tommy Mühle | tommy-muehle.io 54 class Person { // ...

    public function __clone() { throw new LogicException( 'Why would you even clone me?'); } public function __sleep() { throw new BadMethodCallException( 'Serialize, really?'); } }
  56. Tommy Mühle | tommy-muehle.io Summary 55

  57. Tommy Mühle | tommy-muehle.io You can always think different 56

  58. Tommy Mühle | tommy-muehle.io 57 https:/ /laracasts.com/series/php-bits/episodes/1 Visual Debt

  59. Questions?

  60. Thank you! Slides http:/ /bit.ly/2qFHyQa Images https:/ /pixabay.com/ @tommy_muehle