Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

S O L I D Introduction to SOLID - @garethellis

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

S O L I D Introduction to SOLID - @garethellis

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

S O L I D Introduction to SOLID - @garethellis

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

S O L I D Introduction to SOLID - @garethellis

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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"

Slide 40

Slide 40 text

S O L I D Introduction to SOLID - @garethellis

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

S O L I D Introduction to SOLID - @garethellis

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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 }

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

S O L I D Introduction to SOLID - @garethellis

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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