Share
Embed
Introduction to SOLID - PHPOxford
Talk presented at PHPOxford, January 2017
Gareth Ellis
January 25, 2017
Transcript
92 slides
-
S O L I D Introduction to SOLID - @garethellis
INTRODUCTION TO SOLID PHP OXFORD, JANUARY 2017 Gareth Ellis / @garethellis -
S O L I D Introduction to SOLID - @garethellis
FIVE LITTLE LETTERS -
S O L I D Introduction to SOLID - @garethellis
WHAT IS SOLID? -
S O L I D Introduction to SOLID - @garethellis
SOLID IS NOT SPECIFIC TO PHP -
S O L I D Introduction to SOLID - @garethellis
READING VS WRITING -
S O L I D Introduction to SOLID - @garethellis
SINGLE RESPONSIBILITY PRINCIPLE (SRP) "A class should have only one reason to change." -
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
"CLOSED FOR MODIFICATION" -
S O L I D Introduction to SOLID - @garethellis
"OPEN FOR EXTENSION" -
S O L I D Introduction to SOLID - @garethellis
IT'S NOT (NECESSARILY) ABOUT INHERITANCE -
S O L I D Introduction to SOLID - @garethellis
FAVOUR COMPOSITION OVER INHERITANCE -
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
THAT'S THE STRATEGY PATTERN -
S O L I D Introduction to SOLID - @garethellis
INTRODUCING THE DECORATOR PATTERN -
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
INTRODUCING THE OBSERVER PATTERN -
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
BEHAVIOUR, BEHAVIOUR, BEHAVIOUR -
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
DESIGN BY CONTRACT -
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
FAVOUR COMPOSITION OVER INHERITANCE -
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
$filterCollection>add( new FreeText("BlogPosts", "title") ); -
S O L I D Introduction to SOLID - @garethellis
WHAT IF THE USER SEARCHES FOR "0"? if ($request>query($this>fieldName)) -
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 IF THE USER SEARCHES FOR "0"? if ($searchParams>has($this>fieldName)) -
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
DEPENDENCY INVERSION (DI) PRINCIPLE “Depend upon abstractions, [not] concretions.” -
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
DEPENDENCY INVERSION IS NOT (NECESSARILY) ABOUT INTERFACES -
S O L I D Introduction to SOLID - @garethellis
DEPENDENCY INVERSION IS NOT (NECESSARILY) ABOUT OBJECTS -
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
DEPENDENCY INVERSION IS NOT ABOUT DEPENDENCY INJECTION CONTAINERS -
S O L I D Introduction to SOLID - @garethellis
OPTIMIZE FOR READING -
S O L I D Introduction to SOLID - @garethellis
KEEP CLASSES SMALL AND SPECIALIZED -
S O L I D Introduction to SOLID - @garethellis
FAVOUR COMPOSITION OVER INHERITANCE -
S O L I D Introduction to SOLID - @garethellis
USE SPECIFIC TYPES -
S O L I D Introduction to SOLID - @garethellis
KEEP COUPLING AS LOOSE AS POSSIBLE -
S O L I D Introduction to SOLID - @garethellis
KNOW YOUR TRADE-OFFS -
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