Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction to SOLID - PHPOxford

Introduction to SOLID - PHPOxford

Talk presented at PHPOxford, January 2017

Gareth Ellis

January 25, 2017
Tweet

More Decks by Gareth Ellis

Other Decks in Technology

Transcript

  1. S O L I D Introduction to SOLID - @garethellis

    INTRODUCTION TO SOLID PHP OXFORD, JANUARY 2017 Gareth Ellis / @garethellis
  2. S O L I D Introduction to SOLID - @garethellis

    SOLID IS NOT SPECIFIC TO PHP
  3. S O L I D Introduction to SOLID - @garethellis

    SINGLE RESPONSIBILITY PRINCIPLE (SRP) "A class should have only one reason to change."
  4. S O L I D Introduction to SOLID - @garethellis

    class UserRegistrationController { public function register(Request $request, Response $response) { //create a user entity from the request data $user = $this­>createUserFromRequest($request); //save in database $this­>userRepository­>save($user); //send notification to user $this­>email ­>to($user­>email()) ­>subject("Welcome!") ­>send(); return $this­>successfulResponse($response); } }
  5. S O L I D Introduction to SOLID - @garethellis

    class UserRegistration { public function __construct( UserRepository $userRepository, Email $email ) { $this­>userRepository = $userRepository; $this­>email = $email; } public function register(User $user) { //save in database $this­>userRepository­>save($user); //send notification to user $this­>email ­>to($user­>email()) ­>subject("Welcome!") ­>send(); } } }
  6. S O L I D Introduction to SOLID - @garethellis

    class UserRegistration { public function __construct( UserRegistrationStorage $storage, UserRegistrationWelcomeEmail $welcomeEmail ) { $this­>storage = $storage; $this­>welcomeEmail = $welcomeEmail; } public function register(User $user) { //save in database $this­>storage­>save($user); //send notification to user $this­>welcomeEmail­>email($user); } }
  7. S O L I D Introduction to SOLID - @garethellis

    OPEN/CLOSED PRINCIPLE (OCP) “So ware entities … should be open for extension, but closed for modification.”
  8. S O L I D Introduction to SOLID - @garethellis

    IT'S NOT (NECESSARILY) ABOUT INHERITANCE
  9. S O L I D Introduction to SOLID - @garethellis

    FAVOUR COMPOSITION OVER INHERITANCE
  10. S O L I D Introduction to SOLID - @garethellis

    abstract class Duck { public function swim() { /** swim like a duck! */ } public function quack() { /** quack like a duck! */ } public function fly() { /** fly like a duck! */ } } class Mallard extends Duck {} class Eider extends Duck {} class Mandarin extends Duck {}
  11. S O L I D Introduction to SOLID - @garethellis

    class RubberDuck extends Duck { public function swim() { /** float */ } public function quack() { /** squeak */ } public function fly() { /** do nothing */ } public function debug($code) { /** debug the code! */ } }
  12. S O L I D Introduction to SOLID - @garethellis

    class WoodenToyDuck extends Duck { public function swim() { /** float */ } public function quack() { /** do nothing */ } public function fly() { /** do nothing */ } }
  13. S O L I D Introduction to SOLID - @garethellis

    interface Swimming { public function swim(); } interface Quacking { public function quack(); } interface Flying { public function fly(); }
  14. S O L I D Introduction to SOLID - @garethellis

    interface Quacking { public function quack(); } class QuackingDuck implements Quacking { public function quack() { /** quack like a real duck */ } } class MuteDuck implements Quacking { public function quack() { /** do nothing */ } } class SqueakingDuck implements Quacking { public function quack() { /** squeak like a rubber duck */ } }
  15. S O L I D Introduction to SOLID - @garethellis

    class Eider { function __construct( Swimming $swimming, Quacking $quacking, Flying $flying ) { $this­>swimming = $swimming; $this­>quacking = $quacking; $this­>flying = $flying; } public function swim() { $this­>swimming­>swim(); } public function quack() { $this­>quacking­>quack(); } public function fly() { $this­>flying­>fly(); } }
  16. S O L I D Introduction to SOLID - @garethellis

    class Ooooooh implements Quacking { public function quack() { /** make an awesome ooooh noise like an eider duck */ } } new Eider(new SwimmingDuck, new Ooooooh, new FlyingDuck);
  17. S O L I D Introduction to SOLID - @garethellis

    INTRODUCING THE DECORATOR PATTERN
  18. S O L I D Introduction to SOLID - @garethellis

    class ExampleMiddleware { public function __invoke($request, $response, $next) { $response­>getBody()­>write('BEFORE'); $response = $next($request, $response); $response­>getBody()­>write('AFTER'); return $response; } } From the SlimPHP documentation
  19. S O L I D Introduction to SOLID - @garethellis

    DECORATOR EXAMPLE Integrate an application with an API API uses OAuth2 for auth Have to send a valid OAuth2 token with each API call Tokens are valid for 48 hours
  20. S O L I D Introduction to SOLID - @garethellis

    interface AccessTokenRepository { get(): AccessToken } class ApiRepository implements AccessTokenRepository { function __construct(Provider $provider) { $this­>provider = $provider; } public function get(): AccessToken { return $this­>provider­>getAccessToken(); } }
  21. S O L I D Introduction to SOLID - @garethellis

    class StorageRepository implements AccessTokenRepository { function __construct( TokenStorage $storage, AccessTokenRepository $repository ) { $this­>storage = $provider; $this­>repository = $repository; } public function get(): AccessToken { $token = $this­>storage­>get(); if ($token­>isValid()) { return $token; } $token = $this­>repository­>get(); $this­>storage­>save($token); return $token; } }
  22. S O L I D Introduction to SOLID - @garethellis

    $tokenStorage = new SomeStorageImplementation; $apiRepository = new ApiRepository(new Provider); $storageRepository = new StorageRepository($tokenStorage, $apiRepository); $token = $storageRepository­>get();
  23. S O L I D Introduction to SOLID - @garethellis

    INTRODUCING THE OBSERVER PATTERN
  24. S O L I D Introduction to SOLID - @garethellis

    class UserRegistration { public function __construct( UserRegistrationStorage $storage, UserRegistrationWelcomeEmail $welcomeEmail ) { $this­>storage = $storage; $this­>welcomeEmail = $welcomeEmail; } public function register(User $user) { //save in database $this­>storage­>save($user); //send notification to user $this­>welcomeEmail­>email($user); } }
  25. S O L I D Introduction to SOLID - @garethellis

    interface UserRegistrationListener { public function handleNew(User $user) class UserRegistration { public function __construct(array $listeners) { $this­>listeners = $listeners; } public function register(User $user) { foreach ($this­>listeners as $listener) { $listener­>handleNew($user); } } } $service = new UserRegistrationService([ new UserRegistrationStorage, new UserRegistrationWelcomeEmail, new UserRegistrationWelcomeSMS ]); $service­>register($user);
  26. S O L I D Introduction to SOLID - @garethellis

    LISKOV SUBSTITUTION PRINCIPLE (LSP) “Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”
  27. S O L I D Introduction to SOLID - @garethellis

    LISKOV SUBSTITUTION PRINCIPLE (LSP) "If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program"
  28. S O L I D Introduction to SOLID - @garethellis

    BEHAVIOUR, BEHAVIOUR, BEHAVIOUR
  29. S O L I D Introduction to SOLID - @garethellis

    class Rectangle { private $height; private $width; function __construct(int $height, int $width) { $this­>height = $height; $this­>width = $width; } public function setHeight(int $height) { $this­>height = $height; } public function setWidth(int $width) { $this­>width = $width; } public function getHeight() { return $this­>height; } public function getWidth() { return $this­>width; } }
  30. S O L I D Introduction to SOLID - @garethellis

    function someFunction(Rectangle $rectangle) { $rectangle­>setHeight(10); } $rectangle = new Rectangle(30,20); someFunction($rectangle); var_dump($rectangle­>getHeight()); // now 10 var_dump($rectangle­>getWidth()); // still 20
  31. S O L I D Introduction to SOLID - @garethellis

    class Square extends Rectangle { private $heightAndWidth; function __construct(int $heightAndWidth) { $this­>heightAndWidth = $heightAndWidth; } public function setHeight(int $height) { $this­>heightAndWidth = $height; } public function setWidth(int $width) { $this­>heightAndWidth = $width; } public function getHeight() { return $this­>heightAndWidth; } public function getWidth() { return $this­>heightAndWidth; } }
  32. S O L I D Introduction to SOLID - @garethellis

    function someFunction(Rectangle $rectangle) { $rectangle­>setHeight(10); } $square = new Square(20); someFunction($square); var_dump($square­>getHeight()); // 10 var_dump($square­>getWidth()); // also 10
  33. S O L I D Introduction to SOLID - @garethellis

    POST-CONDITIONS OF Rectangle::setHeight() METHOD The height of the rectangle has changed. The width of the rectangle remains unchanged. PRE-CONDITIONS OF Rectangle::setHeight() METHOD There is a rectangle with a given height and width
  34. S O L I D Introduction to SOLID - @garethellis

    FAVOUR COMPOSITION OVER INHERITANCE
  35. S O L I D Introduction to SOLID - @garethellis

    INTERFACE SEGREGATION PRINCIPLE (ISP) “Many client-specific interfaces are better than one general-purpose interface.”
  36. S O L I D Introduction to SOLID - @garethellis

    SEARCH FILTERS EXAMPLE App contains pages with lists of "things" We had to include various search options on each list Option types included dropdowns, free text, date ranges, etc
  37. S O L I D Introduction to SOLID - @garethellis

    interface Filter { /** * @param \Cake\Network\Request $request * @param \Cake\ORM\Query $query */ public function buildQueryFrom(Request $request, Query $query): Query }
  38. S O L I D Introduction to SOLID - @garethellis

    class FilterCollection implements Filter { /** * @var Filter[] */ private $filters; public function add(Filter $filter) { $this­>filters[] = $filter; } public function buildQueryFrom(Request $request, Query $query): Query foreach ($this­>filters as $filter) { $query = $filter­>buildQueryFrom($request, $query); } return $query; } }
  39. S O L I D Introduction to SOLID - @garethellis

    class FreeText implements Filter { function __construct(string $tableAlias, string $fieldName) { $this­>tableAlias = $tableAlias; $this­>fieldName = $fieldName; } public function buildQueryFrom(Request $request, Query $query): Query { if ($request­>query($this­>fieldName)) { return $query­>where([ "{$this­>tableAlias}.{$this­>fieldName} LIKE" => "%". $request­>query($this­>fieldName) . "%" ]); } return $query; } }
  40. S O L I D Introduction to SOLID - @garethellis

    $filterCollection­>add( new FreeText("BlogPosts", "title") );
  41. S O L I D Introduction to SOLID - @garethellis

    WHAT IF THE USER SEARCHES FOR "0"? if ($request­>query($this­>fieldName))
  42. S O L I D Introduction to SOLID - @garethellis

    WHAT IF THE USER SEARCHES FOR "0"? if ( $request­>query($this­>fieldName) !== null && $request­>query($this­>fieldName) !== "" )
  43. S O L I D Introduction to SOLID - @garethellis

    interface Filter { /** * @param \App\Filter\SearchParameters $searchParameters * @param \Cake\ORM\Query $query */ public function buildQueryFrom( SearchParameters $searchParameters, Query $query ): Query; } interface SearchParameters { public function has($parameterName): bool; public function value($parameterName); }
  44. S O L I D Introduction to SOLID - @garethellis

    class QueryStringSearchParameters implements SearchParameters { private $request; public function __construct(Request $request) { $this­>request = $request; } public function has(string $parameterName): bool { return $this­>request­>query($parameterName) !== null && $this­>request­>query($parameterName) !== ""; } public function value(string $parameterName) { if (!$this­>has($parameterName)) { throw new SearchParameterNotFound("No such search parameter" } return $this­>request­>query($parameterName); } }
  45. S O L I D Introduction to SOLID - @garethellis

    WHAT IF THE USER SEARCHES FOR "0"? if ($searchParams­>has($this­>fieldName))
  46. S O L I D Introduction to SOLID - @garethellis

    WHAT DOES "INTERFACE" MEAN ANYWAY? interface SearchParameters { public function has($parameterName): bool; public function value($parameterName); }
  47. S O L I D Introduction to SOLID - @garethellis

    class Eider { function __construct( Swimming $swimming, Quacking $quacking, Flying $flying ) { $this­>swimming = $swimming; $this­>quacking = $quacking; $this­>flying = $flying; } public function swim() { $this­>swimming­>swim(); } public function quack() { $this­>quacking­>quack(); } public function fly() { $this­>flying­>fly(); } }
  48. S O L I D Introduction to SOLID - @garethellis

    CLASSES HAVE IMPLICIT INTERFACES interface Of_Class_Eider { public function swim(); public function quack(); public function fly(); }
  49. S O L I D Introduction to SOLID - @garethellis

    “MANY CLIENT-SPECIFIC INTERFACES TYPES ARE BETTER THAN ONE GENERAL-PURPOSE INTERFACE TYPE.” - Dan Ackroyd, PHP NW 2016
  50. S O L I D Introduction to SOLID - @garethellis

    TYPES 101 ...a type is a classification of data which tells the compiler how the programmer intends to use the data... - Wikipedia
  51. S O L I D Introduction to SOLID - @garethellis

    PHP TYPES - OBJECTS Abstract types interface Filter abstract class Duck Concrete types (classes) class FreeText implements Filter class Mallard extends Duck class Eider
  52. S O L I D Introduction to SOLID - @garethellis

    PHP TYPES - SCALARS Integers Floats 1 3.14 Strings "ducks are awesome" Booleans true false Arrays [1, 3.14, "ducks are awesome"]
  53. S O L I D Introduction to SOLID - @garethellis

    class FormHelper { public function datePicker(string $fieldName, array $options = []) { if (isset($options["isRequiredField"]) && $options["isRequiredField"] === true) { //do something } //do something to render a datepicker field } } $formHelper­>datePicker("birthday", [ "isReqieredFeild" => true, //oops I mis­spelled it! ]);
  54. S O L I D Introduction to SOLID - @garethellis

    class DatePickerOptions { private $isRequiredField = false; public function setRequiredField(bool $isRequired) { $this­>isRequiredField = $isRequired; } public function isRequiredField(): bool { return $this­>isRequiredField; } private $label = "My date picker": public function setLabel(string $label) { if (strlen($label) > 25) { throw new InvalidArgumentException("Label too long!"); } $this­>label = $label; } public function label(): string { return $this­>label; } }
  55. S O L I D Introduction to SOLID - @garethellis

    class FormHelper { public function datePicker( string $fieldName, DatePickerOptions $options ) { if ($options­>isRequiredField()) { //do something } //do something to render a datepicker field } } $options = new DatePickerOptions; $options­>setIsRequiredField(true) $options­>setLabel("When is your birthday?"); $formHelper­>datePicker("birthday", $options);
  56. S O L I D Introduction to SOLID - @garethellis

    function connect( string $hostname, int $port, bool $persist = false, bool $enableAwesomeness = true, bool $disableFluxCapacitor = false ): Connection { return new Connection(/** do something with arguments */); } $connection = connect("myawesomeserver.com", 123, true, false, true);
  57. S O L I D Introduction to SOLID - @garethellis

    class ConnectionFactory { private $persist = false; function __construct(string $host, int $port) { $this­>host = $host; $this­>port = $port; } public function enablePersist(): ConnectionFactory { $this­>persist = true; return $this; } public function disablePersist(): ConnectionFactory { $this­>persist = false; return $this; } public function getConnection(): Connection { return connect($this­>host, $this­>port, $this­>persist, $this­>awesomeness, $this­>fluxCapacitor); } }
  58. S O L I D Introduction to SOLID - @garethellis

    (new ConnectionFactory("myawesomehost.com", 123)) ­>enablePersist() ­>enableAwesomeness() ­>disableFluxCapacitor() ­>getConnection();
  59. S O L I D Introduction to SOLID - @garethellis

    DEPENDENCY INVERSION (DI) PRINCIPLE “Depend upon abstractions, [not] concretions.”
  60. S O L I D Introduction to SOLID - @garethellis

    class Eider { function __construct() { $this­>swimming = new SwimmingDuck; $this­>quacking = new QuackingDuck; $this­>flying = new FlyingDuck; } public function swim() { $this­>swimming­>swim(); } public function quack() { $this­>quacking­>quack(); } public function fly() { $this­>flying­>fly(); } }
  61. S O L I D Introduction to SOLID - @garethellis

    class Eider { function __construct( SwimmingDuck $swimming, QuackingDuck $quacking, FlyingDuck $flying ) { $this­>swimming = $swimming; $this­>quacking = $quacking; $this­>flying = $flying; } }
  62. S O L I D Introduction to SOLID - @garethellis

    DEPENDENCY INVERSION IS NOT (NECESSARILY) ABOUT INTERFACES
  63. S O L I D Introduction to SOLID - @garethellis

    DEPENDENCY INVERSION IS NOT (NECESSARILY) ABOUT OBJECTS
  64. S O L I D Introduction to SOLID - @garethellis

    class MysqlConnectionFactory() { function __construct() { $this­>username = "database_username"; $this­>password = "my_password"; } public function create(): PDO { return new PDO( "mysql:host=database.server", $this­>username, $this­>password ); } }
  65. S O L I D Introduction to SOLID - @garethellis

    class MysqlConnectionFactory() { function __construct(string $username, string $password) { $this­>username = $username; $this­>password = $password; } public function create(): PDO { return new PDO( "mysql:host=database.server", $this­>username, $this­>password ); } }
  66. S O L I D Introduction to SOLID - @garethellis

    // Without dependency injection new Eider; vs // With dependency injection new Eider(new SwimmingDuck, new Ooooooh, new FlyingDuck);
  67. S O L I D Introduction to SOLID - @garethellis

    DEPENDENCY INVERSION IS NOT ABOUT DEPENDENCY INJECTION CONTAINERS
  68. S O L I D Introduction to SOLID - @garethellis

    KEEP CLASSES SMALL AND SPECIALIZED
  69. S O L I D Introduction to SOLID - @garethellis

    FAVOUR COMPOSITION OVER INHERITANCE
  70. S O L I D Introduction to SOLID - @garethellis

    KEEP COUPLING AS LOOSE AS POSSIBLE
  71. S O L I D Introduction to SOLID - @garethellis

    THANKS! by Derick Bailey Licensed under a @garethellis [email protected] SOLID Motivational Posters Creative Commons Attribution-Share Alike 3.0 United States License
  72. S O L I D Introduction to SOLID - @garethellis

    FURTHER READING book paper by Robert C. Martin talk by Dan Ackroyd @ PHPNW 2016 (slides) talk by Rob Allen @ PHPNW 2013 (video) (RSPB) Head-First Design Patterns The Liskov Substitution Principle Interface Segregation - the Forgotten "I" in SOLID Introducing Dependency Injection Eider duck