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); } }
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(); } } }
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); } }
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 {}
class RubberDuck extends Duck { public function swim() { /** float */ } public function quack() { /** squeak */ } public function fly() { /** do nothing */ } public function debug($code) { /** debug the code! */ } }
class WoodenToyDuck extends Duck { public function swim() { /** float */ } public function quack() { /** do nothing */ } public function fly() { /** do nothing */ } }
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 */ } }
class Ooooooh implements Quacking { public function quack() { /** make an awesome ooooh noise like an eider duck */ } } new Eider(new SwimmingDuck, new Ooooooh, new FlyingDuck);
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
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
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); } }
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);
LISKOV SUBSTITUTION PRINCIPLE (LSP) “Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”
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"
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; } }
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
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; } }
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
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
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); } }
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; } }
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);
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(); } }
THANKS! by Derick Bailey Licensed under a @garethellis [email protected] SOLID Motivational Posters Creative Commons Attribution-Share Alike 3.0 United States License
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