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

    View Slide

  2. S O L I D
    Introduction to SOLID - @garethellis
    FIVE LITTLE LETTERS

    View Slide

  3. S O L I D
    Introduction to SOLID - @garethellis
    WHAT IS SOLID?

    View Slide

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

    View Slide

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

    View Slide

  6. S O L I D
    Introduction to SOLID - @garethellis
    READING VS WRITING

    View Slide

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

    View Slide

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

    View Slide

  9. 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);
    }
    }

    View Slide

  10. 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();
    }
    }
    }

    View Slide

  11. 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);
    }
    }

    View Slide

  12. 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.”

    View Slide

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

    View Slide

  14. S O L I D
    Introduction to SOLID - @garethellis
    "CLOSED FOR MODIFICATION"

    View Slide

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

    View Slide

  16. S O L I D
    Introduction to SOLID - @garethellis
    "OPEN FOR EXTENSION"

    View Slide

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

    View Slide

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

    View Slide

  19. 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 {}

    View Slide

  20. 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! */ }
    }

    View Slide

  21. 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 */ }
    }

    View Slide

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

    View Slide

  23. 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 */ }
    }

    View Slide

  24. 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();
    }
    }

    View Slide

  25. 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);

    View Slide

  26. S O L I D
    Introduction to SOLID - @garethellis
    THAT'S THE STRATEGY PATTERN

    View Slide

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

    View Slide

  28. 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

    View Slide

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

    View Slide

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

    View Slide

  31. 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

    View Slide

  32. 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();
    }
    }

    View Slide

  33. 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;
    }
    }

    View Slide

  34. 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();

    View Slide

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

    View Slide

  36. 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);
    }
    }

    View Slide

  37. 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);

    View Slide

  38. 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.”

    View Slide

  39. 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"

    View Slide

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

    View Slide

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

    View Slide

  42. 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; }
    }

    View Slide

  43. 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

    View Slide

  44. 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; }
    }

    View Slide

  45. 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

    View Slide

  46. S O L I D
    Introduction to SOLID - @garethellis
    DESIGN BY CONTRACT

    View Slide

  47. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  51. 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

    View Slide

  52. 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
    }

    View Slide

  53. 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;
    }
    }

    View Slide

  54. 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;
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  57. 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) !== ""
    )

    View Slide

  58. 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);
    }

    View Slide

  59. 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);
    }
    }

    View Slide

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

    View Slide

  61. 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);
    }

    View Slide

  62. 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();
    }
    }

    View Slide

  63. 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();
    }

    View Slide

  64. 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

    View Slide

  65. 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

    View Slide

  66. 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

    View Slide

  67. 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"]

    View Slide

  68. 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!
    ]);

    View Slide

  69. 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;
    }
    }

    View Slide

  70. 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);

    View Slide

  71. 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);

    View Slide

  72. 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);
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  76. 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();
    }
    }

    View Slide

  77. 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;
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  80. 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

    }
    }

    View Slide

  81. 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

    }
    }

    View Slide

  82. 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);

    View Slide

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

    View Slide

  84. S O L I D
    Introduction to SOLID - @garethellis
    IN SUMMARY

    View Slide

  85. S O L I D
    Introduction to SOLID - @garethellis
    OPTIMIZE FOR READING

    View Slide

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

    View Slide

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

    View Slide

  88. S O L I D
    Introduction to SOLID - @garethellis
    USE SPECIFIC TYPES

    View Slide

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

    View Slide

  90. S O L I D
    Introduction to SOLID - @garethellis
    KNOW YOUR TRADE-OFFS

    View Slide

  91. 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

    View Slide

  92. 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

    View Slide