Share
Embed
Transcript
89 slides
-
S O L I D Introduction to SOLID - @garethellis
INTRODUCTION TO SOLID PHP UK CONFERENCE, FEBRUARY 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->mysql->query("INSERT INTO users ..."); //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( 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(); } } } -
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
*ORNITHOLOGICAL BONUS CONTENT* -
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
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 { public function 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
THE OBSERVER PATTERN -
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 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 -
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 transform(Rectangle $rectangle) { $rectangle->setHeight(10); } $square = new Square(20); transform($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 (& ARRAYS) 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 mis-spelled 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] https://joind.in/talk/ae763 SOLID Motivational Posters Creative Commons Attribution-Share Alike 3.0 United States License -
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