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); } }
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(); } } }
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); } }
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.”
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 {}
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! */ } }
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 */ } }
S O L I D Introduction to SOLID - @garethellis interface Swimming { public function swim(); } interface Quacking { public function quack(); } interface Flying { public function fly(); }
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 */ } }
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(); } }
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);
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
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
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(); } }
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; } }
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();
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); } }
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);
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.”
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"
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; } }
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
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; } }
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
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
S O L I D Introduction to SOLID - @garethellis INTERFACE SEGREGATION PRINCIPLE (ISP) “Many client-specific interfaces are better than one general-purpose interface.”
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
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 }
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; } }
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; } }
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) !== "" )
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); }
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); } }
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); }
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(); } }
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(); }
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
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
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
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"]
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 misspelled it! ]);
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; } }
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);
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);
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); } }
S O L I D Introduction to SOLID - @garethellis (new ConnectionFactory("myawesomehost.com", 123)) >enablePersist() >enableAwesomeness() >disableFluxCapacitor() >getConnection();
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(); } }
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; } }
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 ); } }
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 ); } }
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);
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
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