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

Introduction to SOLID - PHP UK 2017

Gareth Ellis
February 16, 2017

Introduction to SOLID - PHP UK 2017

Gareth Ellis

February 16, 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 UK CONFERENCE, FEBRUARY 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->mysql->query("INSERT INTO users ..."); //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( MySql $mysql, Email $email ) { $this->mysql = $mysql; $this->email = $email; } public function register(User $user) { //save in database $this->mysql->query("INSERT INTO users ..."); //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

    *ORNITHOLOGICAL BONUS CONTENT*
  17. 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);
  18. S O L I D Introduction to SOLID - @garethellis

    INTRODUCING THE DECORATOR PATTERN
  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 { public function 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

    LISKOV SUBSTITUTION PRINCIPLE (LSP) “Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”
  24. 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"
  25. S O L I D Introduction to SOLID - @garethellis

    BEHAVIOUR, BEHAVIOUR, BEHAVIOUR
  26. 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; } }
  27. S O L I D Introduction to SOLID - @garethellis

    function transform(Rectangle $rectangle) { $rectangle->setHeight(10); } $rectangle = new Rectangle(30,20); transform($rectangle); var_dump($rectangle->getHeight()); // now 10 var_dump($rectangle->getWidth()); // still 20
  28. 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; } }
  29. S O L I D Introduction to SOLID - @garethellis

    function transform(Rectangle $rectangle) { $rectangle->setHeight(10); } $square = new Square(20); transform($square); var_dump($square->getHeight()); // 10 var_dump($square->getWidth()); // also 10
  30. 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
  31. S O L I D Introduction to SOLID - @garethellis

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

    INTERFACE SEGREGATION PRINCIPLE (ISP) “Many client-specific interfaces are better than one general-purpose interface.”
  33. 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
  34. 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 }
  35. 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; } }
  36. 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; } }
  37. S O L I D Introduction to SOLID - @garethellis

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

    WHAT IF THE USER SEARCHES FOR "0"? if ($request->query($this->fieldName))
  39. 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) !== "" )
  40. 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); }
  41. 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); } }
  42. S O L I D Introduction to SOLID - @garethellis

    WHAT IF THE USER SEARCHES FOR "0"? if ($searchParams->has($this->fieldName))
  43. 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); }
  44. 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(); } }
  45. 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(); }
  46. 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
  47. 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
  48. 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
  49. S O L I D Introduction to SOLID - @garethellis

    PHP TYPES - SCALARS (& ARRAYS) Integers Floats 1 3.14 Strings "ducks are awesome" Booleans true false Arrays [1, 3.14, "ducks are awesome"]
  50. 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! ]);
  51. 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; } }
  52. 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);
  53. 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);
  54. 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); } }
  55. S O L I D Introduction to SOLID - @garethellis

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

    DEPENDENCY INVERSION (DI) PRINCIPLE “Depend upon abstractions, [not] concretions.”
  57. 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(); } }
  58. 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; } }
  59. S O L I D Introduction to SOLID - @garethellis

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

    DEPENDENCY INVERSION IS NOT (NECESSARILY) ABOUT OBJECTS
  61. 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 ); } }
  62. 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 ); } }
  63. 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);
  64. S O L I D Introduction to SOLID - @garethellis

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

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

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

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

    THANKS! by Derick Bailey Licensed under a @garethellis [email protected] https://joind.in/talk/ae763 SOLID Motivational Posters Creative Commons Attribution-Share Alike 3.0 United States License
  69. S O L I D Introduction to SOLID - @garethellis

    https://joind.in/talk/ae763 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