SlideShare a Scribd company logo
1 of 57
Download to read offline
SOLID In Practice
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
@jessicamauerhan
Senior Software Engineer
TDD/BDD Expert
jessicamauerhan@gmail.com
jmauerhan.wordpress.com
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
What is SOLID?
● Five principles for Object
Oriented Programming
● Guidelines which can help
ensure system is easy to
maintain
● Primarily focused on
communication between
dependencies or
collaborators
Let ϕ( ) be a property provable
about objects of type T.
Then ϕ( ) should be true for
objects of type S where S is a
subtype of T
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class Rectangle
{
protected $length;
protected $height;
public function area()
{
return $this->length * $this->area();
}
public function setLength($length)
{
$this->length = $length;
}
public function setHeight($height)
{
$this->height = $height;
}
}
Class Square extends Rectangle
{
public function setLength($length)
{
$this->length = $length;
$this->height = $length;
}
public function setHeight($height)
{
$this->length = $height;
$this->height = $height;
}
}
SOLID in Practice
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
S.O.L.I.D.
● Single Responsibility
● Open/Closed
● Liskov Substitution
● Interface Segregation
● Dependency Inversion
It's All About Change
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
User Registration
● Collect User Input
● Validate User Input
● Persist User Data
● Send Confirmation Email
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class UserRegistration
{
public function register(Request $request): Response
{
//Collect user input
if (!$request->has('email') || !$request->has('password')) {
return new Response('register', ['error' => 'Please provide an email and a password']);
}
$user = new User();
$user->email = $request->get('email');
$user->password = $request->get('password');
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class UserRegistration
{
public function register(Request $request): Response
{
[...]
}
private function emailIsRegistered(string $email): bool
{
$dsn = 'host='.$_ENV['DB_HOST'].' dbname='.$_ENV['DB_DB'].' password='.$_ENV['DB_PASS'].' user='.$_ENV['DB_USER'];
$db = pg_connect($dsn);
$result = pg_query($db, "SELECT id FROM users WHERE (email='{$email}')");
$rows = pg_num_rows($result);
return $rows > 0;
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class UserRegistration
{
public function register(Request $request): Response
{
//Collect user input
if (!$request->has('email') || !$request->has('password')) {
return new Response('register', ['error' => 'Please provide an email and a password']);
}
$user = new User();
$user->email = $request->get('email');
$user->password = $request->get('password');
//Validate user input
if ($this->emailIsRegistered($user->getEmail())) {
return new Response('register', ['error' => 'Your email address is already registered']);
}
}
private function emailIsRegistered(string $email): bool
{
$dsn = 'host='.$_ENV['DB_HOST'].' dbname='.$_ENV['DB_DB'].' password='.$_ENV['DB_PASS'].' user='.$_ENV['DB_USER'];
$db = pg_connect($dsn);
$result = pg_query($db, "SELECT id FROM users WHERE (email='{$email}')");
$rows = pg_num_rows($result);
return $rows > 0;
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class UserRegistration
{
public function register(Request $request): Response
{
[...]
}
private function saveUser(User $user)
{
$dsn = 'host='.$_ENV['DB_HOST'].' dbname='.$_ENV['DB_DB'].' password='.$_ENV['DB_PASS'].' user='.$_ENV['DB_USER'];
$db = pg_connect($dsn);
pg_query($db, "INSERT INTO users(email, password) VALUES('{$user->email}', '{$user->password}')");
}
private function emailIsRegistered(string $email): bool
{
$dsn = 'host='.$_ENV['DB_HOST'].' dbname='.$_ENV['DB_DB'].' password='.$_ENV['DB_PASS'].' user='.$_ENV['DB_USER'];
$db = pg_connect($dsn);
$result = pg_query($db, "SELECT id FROM users WHERE (email='{$email}')");
$rows = pg_num_rows($result);
return $rows > 0;
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class UserRegistration
{
public function register(Request $request): Response
{
[...]
}
private function saveUser(User $user)
{
DB::query("INSERT INTO users(email, password) VALUES('{$user->email}', '{$user->password}')");
}
private function emailIsRegistered(string $email): bool
{
$rows = DB::query("SELECT count(id) FROM users WHERE (email='{$email}')");
return $rows > 0;
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class UserRegistration
{
public function register(Request $request): Response
{
//Collect user input
if (!$request->has('email') || !$request->has('password')) {
return new Response('register', ['error' => 'Please provide an email and a password']);
}
$user = new User();
$user->email = $request->get('email');
$user->password = $request->get('password');
//Validate user input
if ($this->emailIsRegistered($user->getEmail())) {
return new Response('register', ['error' => 'Your email address is already registered']);
}
//Persist User
$this->saveUser($user);
//Send Confirmation Email
$this->sendConfirmationEmail($user->getEmail());
return new Response('register', ['success' => 'You are registered! Please check your email!']);
}
private function sendConfirmationEmail(string $email)
{
$subject = "Confirm Email";
$message = "Please <a>click here</a> to confirm your email!";
$headers = "From: mysite@email.com";
mail($email, $subject, $message, $headers);
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class UserRegistration
{
public function register(Request $request): Response {
//Collect user input
if (!$request->has('email') || !$request->has('password')) {
return new Response('register', ['error' => 'Please provide an email and a password']);
}
$user = new User();
$user->email = $request->get('email');
$user->password = $request->get('password');
//Validate user input
if ($this->emailIsRegistered($user->getEmail())) {
return new Response('register', ['error' => 'Your email address is already registered']);
}
//Persist User
$this->saveUser($user);
//Send Confirmation Email
$this->sendConfirmationEmail($user->getEmail());
return new Response('register', ['success' => 'You are registered! Please check your email!']);
}
private function sendConfirmationEmail(string $email) {
$subject = "Confirm Email";
$message = "Please <a>click here</a> to confirm your email!";
$headers = "From: mysite@email.com";
mail($email, $subject, $message, $headers);
}
private function saveUser(User $user) {
DB::query("INSERT INTO users(email, password) VALUES('{$user->email}', '{$user->password}')");
}
private function emailIsRegistered(string $email): bool {
$rows = DB::query("SELECT count(id) FROM users WHERE (email='{$email}')");
return $rows > 0;
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class UserRegistration
{
public function register(Request $request): Response {
//Collect user input
if (!$request->has('email') || !$request->has('password')) {
return new Response('register', ['error' => 'Please provide an email and a password']);
}
$user = new User();
$user->email = $request->get('email');
$user->password = $request->get('password');
//Validate user input
if ($this->emailIsRegistered($user->getEmail())) {
return new Response('register', ['error' => 'Your email address is already registered']);
}
//Persist User
$this->saveUser($user);
//Send Confirmation Email
$this->sendConfirmationEmail($user->getEmail());
return new Response('register', ['success' => 'You are registered! Please check your email!']);
}
private function sendConfirmationEmail(string $email) {
$subject = "Confirm Email";
$message = "Please <a>click here</a> to confirm your email!";
$headers = "From: mysite@email.com";
mail($email, $subject, $message, $headers);
}
private function saveUser(User $user) {
DB::query("INSERT INTO users(email, password) VALUES('{$user->email}', '{$user->password}')");
}
private function emailIsRegistered(string $email): bool {
$numRows = DB::query("SELECT count(id) FROM users WHERE (email='{$email}')");
return $rows > 0;
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Dependency Injection
● Collaborators are supplied to
class from outside - aka
"injected" into it
Service Location
● Collaborators are retrieved or
instantiated from inside the
class using them
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class UserRegistration
{
private $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function register(Request $request): Response
{
[...]
}
private function saveUser(User $user)
{
$sql = "INSERT INTO users(email, password) VALUES('{$user->email}', '{$user->password}')"
$this->db->query($sql);
}
private function emailIsRegistered(string $email): bool
{
$rows = $this->db->query("SELECT count(id) FROM users WHERE (email='{$email}')");
return $rows > 0;
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class UserRegistration
{
private $db;
private $mailer;
public function __construct(Database $db, Mailer $mailer)
{
$this->db = $db;
$this->mailer = $mailer;
}
public function register(Request $request): Response
{
[...]
}
private function sendConfirmationEmail(string $email)
{
//Convert our email to Mandrill's email
$mandrillEmail = [
'to' => ['email' => $email->getTo()],
'from_email' => $email->getFrom(),
'subject' => $email->getSubject(),
'text' => $email->getMessage()
];
$this->mandrill->send($mandrillEmail);
}
Dependency Inversion
Single Responsibility Principle
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class UserRegistration
{
private $factory;
public function __construct(UserFactory $factory)
{
$this->factory = $factory;
}
public function register(Request $request): Response
{
try {
$user = $this->factory->createUser($request);
} catch (UnableToParseUser $exception) {
return new InvalidRequestResponse($exception->getErrors());
}
}
}
Class UserRegistration
{
private $factory, $validator;
public function __construct(UserFactory $factory, UserValidator $validator)
{
$this->factory = $factory;
$this->validator = $validator;
}
public function register(Request $request): Response
{
try {
$user = $this->factory->createUser($request);
} catch (UnableToParseUser $exception) {
return new InvalidRequestResponse($exception->getErrors());
}
if ($this->validator->isValid($user) === false) {
$invalidProperties = $this->validator
->getInvalidProperties($user);
return new InvalidUserResponse($invalidProperties);
}
}
}
Class UserRegistration
{
private $factory, $validator, $repository;
public function __construct(UserFactory $factory, UserValidator $validator,
UserRepository $repository)
{
$this->factory = $factory;
$this->validator = $validator;
$this->repository = $repository;
}
public function register(Request $request): Response
{
try {
$user = $this->factory->createUser($request);
} catch (UnableToParseUser $exception) {
return new InvalidRequestResponse($exception->getErrors());
}
if ($this->validator->isValid($user) === false) {
$invalidProperties = $this->validator
->getInvalidProperties($user);
return new InvalidUserResponse($invalidProperties);
}
try {
$this->repository->create($user);
return UserCreatedResponse($user);
} catch (UnableToCreateUser $exception) {
return UnableToCreateUserResponse($user);
}
}
}
Class UserRegistration
{
private $factory, $validator, $repository, $emailer;
public function __construct(UserFactory $factory, UserValidator $validator,
UserRepository $repository, ConfirmationEmailer $emailer)
{
$this->factory = $factory;
$this->validator = $validator;
$this->repository = $repository;
$this->emailer = $emailer;
}
public function register(Request $request): Response
{
try {
$user = $this->factory->createUser($request);
} catch (UnableToParseUser $exception) {
return new InvalidRequestResponse($exception->getErrors());
}
if ($this->validator->isValid($user) === false) {
$invalidProperties = $this->validator
->getInvalidProperties($user);
return new InvalidUserResponse($invalidProperties);
}
try {
$this->repository->create($user);
$this->emailer->sendConfirmationEmail($user);
return UserCreatedResponse($user);
} catch (UnableToCreateUser $exception) {
return UnableToCreateUserResponse($user);
} catch (MailerException $mailerException) {
return UnableToSendConfirmationEmailResponse($user);
}
}
}
interface UserFactory
{
/**
* @throws UnableToParseUser
*/
public function createRegisteringUser(Request $request): User;
}
interface UserValidator
{
public function isValid(User $user): bool;
public function getInvalidProperties(User $user):array;
}
interface UserRepository
{
/**
* @throws UnableToCreateUser
*/
public function create(User $user): bool;
}
interface ConfirmationEmailer
{
/**
* @throws MailerException
*/
public function send(string $to);
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class UserRegistration
{
private $factory;
public function __construct(UserFactory $factory)
{
$this->factory = $factory;
}
public function register(Request $request): Response
{
try {
$user = $this->factory->createUser($request);
} catch (UnableToParseUser $exception) {
return new InvalidRequestResponse($exception->getErrors());
}
}
}
Class UserRegistration
{
private $factory, $validator;
public function __construct(UserFactory $factory, UserValidator $validator)
{
$this->factory = $factory;
$this->validator = $validator;
}
public function register(Request $request): Response
{
try {
$user = $this->factory->createUser($request);
} catch (UnableToParseUser $exception) {
return new InvalidRequestResponse($exception->getErrors());
}
if ($this->validator->isValid($user) === false) {
$invalidProperties = $this->validator
->getInvalidProperties($user);
return new InvalidUserResponse($invalidProperties);
}
}
}
Class UserRegistration
{
private $factory, $validator, $repository;
public function __construct(UserFactory $factory, UserValidator $validator,
UserRepository $repository)
{
$this->factory = $factory;
$this->validator = $validator;
$this->repository = $repository;
}
public function register(Request $request): Response
{
try {
$user = $this->factory->createUser($request);
} catch (UnableToParseUser $exception) {
return new InvalidRequestResponse($exception->getErrors());
}
if ($this->validator->isValid($user) === false) {
$invalidProperties = $this->validator
->getInvalidProperties($user);
return new InvalidUserResponse($invalidProperties);
}
try {
$this->repository->create($user);
return UserCreatedResponse($user);
} catch (UnableToCreateUser $exception) {
return UnableToCreateUserResponse($user);
}
}
}
Class UserRegistration
{
private $factory, $validator, $repository, $emailer;
public function __construct(UserFactory $factory, UserValidator $validator,
UserRepository $repository, ConfirmationEmailer $emailer)
{
$this->factory = $factory;
$this->validator = $validator;
$this->repository = $repository;
$this->emailer = $emailer;
}
public function register(Request $request): Response
{
try {
$user = $this->factory->createUser($request);
} catch (UnableToParseUser $exception) {
return new InvalidRequestResponse($exception->getErrors());
}
if ($this->validator->isValid($user) === false) {
$invalidProperties = $this->validator
->getInvalidProperties($user);
return new InvalidUserResponse($invalidProperties);
}
try {
$this->repository->create($user);
$this->emailer->sendConfirmationEmail($user);
return UserCreatedResponse($user);
} catch (UnableToCreateUser $exception) {
return UnableToCreateUserResponse($user);
} catch (MailerException $mailerException) {
return UnableToSendConfirmationEmailResponse($user);
}
}
}
interface UserFactory
{
/**
* @throws UnableToParseUser
*/
public function createRegisteringUser(Request $request): User;
}
interface UserValidator
{
public function isValid(User $user): bool;
public function getInvalidProperties(User $user):array;
}
interface UserRepository
{
/**
* @throws UnableToCreateUser
*/
public function create(User $user): bool;
}
interface ConfirmationEmailer
{
/**
* @throws MailerException
*/
public function send(string $to);
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
interface UserFactory
{
/**
* @throws UnableToParseUser
*/
public function createUser(Request $request): User;
}
class RegisterFormUserFactory implements UserFactory
{
/**
* @throws UnableToParseUser
*/
public function createUser(Request $request): User
{
if(!$request->has('email') || !$request->has('password')){
throw new UnableToParseUser();
}
$email = $request->get('email');
$password = $request->get('password');
return new User($email, $password);
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
interface UserValidator
{
public function isValid(User $user): bool;
public function getInvalidProperties(User $user):array;
}
class NewUserValidator implements UserValidator
{
/** @var UserRepository */
private $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function isValid(User $user): bool
{
$emailExists = $this->userRepository->emailExists($user->getEmail());
if ($emailExists === true) {
return false;
}
return true;
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
interface UserRepository
{
/**
* @param User $user
* @return bool
*
* @throws UnableToCreateUser
*/
public function create(User $user): bool;
/**
* @param string $email
* @return bool
*
* @throws DataSourceError
*/
public function emailExists(string $email): bool;
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
class PdoUserRepository implements UserRepository
{
/** @var Pdo */
private $pdo;
public function __construct(Pdo $pdo) {
$this->pdo = $pdo;
}
public function create(User $user): bool {
try {
$statement = $this->pdo->prepare("INSERT INTO users(email, password) VALUES(:email, :password)");
$statement->execute(['email' => $user->getEmail(), 'password' => $user->getPassword()]);
} catch (PDOException $PDOException) {
throw new UnableToCreateUser($user->getEmail());
}
return true;
}
public function emailExists(string $email): bool {
try {
$statement = $this->pdo->prepare("SELECT count(id) AS numRows FROM users WHERE email = :email");
$statement->execute([email => $email]);
$result = $statement->fetchColumn();
return ($result > 0);
} catch (PDOException $PDOException) {
throw new DataSourceError();
}
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
interface ConfirmationEmailer
{
/**
* @throws MailerException
*/
public function send(string $to);
}
class MandrillConfirmationEmailer implements ConfirmationEmailer
{
private $mandrill;
public function __construct(Mandrill $mandrill)
{
$this->mandrill = $mandrill;
}
public function send(User $user)
{
$message = "Please <a>click here</a> to confirm your account.";
$from = "app@example.com";
$subject = "Confirm your account";
$email = [
'to' => [['EmailTemplate' => $user->getEmail()]],
'from_email' => $from,
'subject' => $subject,
'text' => $message
];
$this->mandrill->send($email);
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
interface ConfirmationEmailer
{
public function send(string $to); /** @throws MailerException */
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
interface ConfirmationEmailer
{
public function send(string $to); /** @throws MailerException */
}
interface Mailer
{
public function send(Email $email);
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
interface ConfirmationEmailer
{
public function send(string $to); /** @throws MailerException */
}
interface Mailer
{
public function send(Email $email);
}
class Email
{
private $to, $from, $subject, $message;
public function __construct(string $to, string $from,
string $subject, string $message) {
$this->to = $to;
$this->from = $from;
$this->subject = $subject;
$this->message = $message;
}
public function getTo(): string {
return $this->to;
}
public function getFrom(): string {
return $this->from;
}
public function getSubject(): string {
return $this->subject;
}
public function getMessage(): string {
return $this->message;
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
interface ConfirmationEmailer
{
public function send(string $to); /** @throws MailerException */
}
interface Mailer
{
public function send(Email $email);
}
class Email
{
private $to, $from, $subject, $message;
public function __construct(string $to, string $from,
string $subject, string $message) {
$this->to = $to;
$this->from = $from;
$this->subject = $subject;
$this->message = $message;
}
public function getTo(): string {
return $this->to;
}
public function getFrom(): string {
return $this->from;
}
public function getSubject(): string {
return $this->subject;
}
public function getMessage(): string {
return $this->message;
}
}
class ConfirmationEmailService implements ConfirmationEmailer
{
private $mailer;
private $from = 'mysite@example.com';
private $subject = 'Confirm Email';
private $message = 'Please click here to confirm your email';
public function __construct(Mailer $mailer) {
$this->mailer = $mailer;
}
public function send(string $to) {
$email = new Email($to, $this->from,
$this->subject, $this->message);
$this->mailer->send($email);
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
interface ConfirmationEmailer
{
public function send(string $to); /** @throws MailerException */
}
interface Mailer
{
public function send(Email $email);
}
class Email
{
private $to, $from, $subject, $message;
public function __construct(string $to, string $from,
string $subject, string $message) {
$this->to = $to;
$this->from = $from;
$this->subject = $subject;
$this->message = $message;
}
public function getTo(): string {
return $this->to;
}
public function getFrom(): string {
return $this->from;
}
public function getSubject(): string {
return $this->subject;
}
public function getMessage(): string {
return $this->message;
}
}
class ConfirmationEmailService implements ConfirmationEmailer
{
private $mailer;
private $from = 'mysite@example.com';
private $subject = 'Confirm Email';
private $message = 'Please click here to confirm your email';
public function __construct(Mailer $mailer) {
$this->mailer = $mailer;
}
public function send(string $to) {
$email = new Email($to, $this->from,
$this->subject, $this->message);
$this->mailer->send($email);
}
}
class MandrillMailer implements Mailer
{
private $mandrill;
public function __construct(Mandrill $mandrill) {
$this->mandrill = $mandrill;
}
public function send(Email $email) {
//Convert our email to Mandrill's email
$mandrillMail = ['to' => ['email' => $email->getTo()],
'from_email' => $email->getFrom(),
'subject' => $email->getSubject(),
'text' => $email->getMessage()];
$this->mandrill->send($mandrillEmail);
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
interface ConfirmationEmailer
{
public function send(string $to); /** @throws MailerException */
}
interface Mailer
{
public function send(Email $email);
}
class Email
{
private $to, $from, $subject, $message;
public function __construct(string $to, string $from,
string $subject, string $message) {
$this->to = $to;
$this->from = $from;
$this->subject = $subject;
$this->message = $message;
}
public function getTo(): string {
return $this->to;
}
public function getFrom(): string {
return $this->from;
}
public function getSubject(): string {
return $this->subject;
}
public function getMessage(): string {
return $this->message;
}
}
class ConfirmationEmailService implements ConfirmationEmailer
{
private $mailer;
private $from = 'mysite@example.com';
private $subject = 'Confirm Email';
private $message = 'Please click here to confirm your email';
public function __construct(Mailer $mailer) {
$this->mailer = $mailer;
}
public function send(string $to) {
$email = new Email($to, $this->from,
$this->subject, $this->message);
$this->mailer->send($email);
}
}
class SendGridMailer implements Mailer
{
private $sendgrid;
public function __construct(SendGrid $sendgrid) {
$this->sedngrid = $sendgrid;
}
public function send(Email $email) {
//Convert our email to SendGrid's email
$sgEmail = new SendGridEmail($email->getTo(),
$email->getFrom(),
$email->getSubject(),
$email->getMessage());
$this->sendgrid->queue($sgEmail);
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
class AccountOverdueListener
{
private $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function handle(OverdueEvent $event)
{
$account = $event->getAccount();
$email = new OverdueEmail($account->getEmailAddress(), $account->getAmountDue());
$this->mailer->send($email);
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
class AccountOverdueListener
{
private $mailer;
private $texter;
public function __construct(Mailer $mailer, Texter $texter)
{
$this->mailer = $mailer;
$this->texter = $texter;
}
public function handle(OverdueEvent $event)
{
$account = $event->getAccount();
$notifications = $account->getEnabledNotifications();
foreach ($notifications AS $notificationType) {
if ($notificationType === 'email') {
$email = new OverdueEmail($account->getEmailAddress(), $account->getAmountDue());
$this->mailer->send($email);
} elseif ($notificationType === 'text') {
$text = new OverdueText($account->getPhoneNumber(), $account->getAmountDue());
$this->texter->send($text);
}
}
}
}
Open/Closed Principle
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Interface Notifier
{
public function notify(ContactInfo $contact);
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Interface Notifier
{
public function notify(ContactInfo $contact);
}
Class EmailNotifier implements Notifier
{
private $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function notify(ContactInfo $contact)
{
$email = new OverdueEmail($contact->getEmailAddress());
$this->mailer->send($email);
}
}
Class TextNotifier implements Notifier
{
private $texter;
public function __construct(Texter $texter)
{
$this->texter = $texter;
}
public function notify(ContactInfo $contact)
{
$text = 'Your Account Is Overdue!';
$this->texter->send($text, $contact->getPhoneNumber());
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Interface Notifier
{
public function notify(ContactInfo $contact);
}
Class NotifierFactory
{
private $notifiers;
public function register($name, Notifier $notifier)
{
$this->notifiers[$name] = $notifier;
}
public function getNotifier(string $name): Notifier
{
return $this->notifiers[$name];
}
}
Class EmailNotifier implements Notifier
{
private $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function notify(ContactInfo $contact)
{
$email = new OverdueEmail($contact->getEmailAddress());
$this->mailer->send($email);
}
}
Class TextNotifier implements Notifier
{
private $texter;
public function __construct(Texter $texter)
{
$this->texter = $texter;
}
public function notify(ContactInfo $contact)
{
$text = 'Your Account Is Overdue!';
$this->texter->send($text, $contact->getPhoneNumber());
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
class AccountOverdueListener
{
private $notifierFactory;
public function __construct(NotifierFactory $notifierFactory)
{
$this->notifierFactory = $notifierFactory;
}
public function handle(OverdueEvent $event)
{
$account = $event->getAccount();
$contact = $account->getContactInfo();
$notifications = $account->getEnabledNotifications();
foreach ($notifications AS $notificationType) {
$notifier = $this->notifierFactory->getNotifier($notificationType);
$notifier->send($contact);
}
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
class AccountOverdueEmailListener
{
public function handle(Mailer $mailer, OverdueEvent $event)
{
$account = $event->getAccount();
$email = new OverdueEmail($account->getEmailAddress(), $account->getAmountDue());
$mailer->send($email);
}
}
class AccountOverdueTextListener
{
public function handle(Texter $texter, OverdueEvent $event)
{
$account = $event->getAccount();
$text = new OverdueText($account->getPhoneNumber(), $account->getAmountDue());
$texter->send($text);
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class PercentageDiscount
{
private $discount;
public function __construct(int $discount)
{
$this->discount = $discount;
}
public function calculateAmountOff(Item $item): float
{
return ($this->discount / 100) * $item->getCost();
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class PercentageDiscount
{
private $discount;
public function __construct(int $discount)
{
$this->discount = $discount;
}
public function calculateAmountOff(Item $item)
{
return ($this->discount / 100) * $item->getCost();
}
}
Class BuyItemsToGetDiscount extends PercentageDiscount
{
private $type;
private $countItemsRequired = 2;
public function setEligibleType(string $type)
{
$this->type = $type;
}
public function calculateAmountOff(Item $item)
{
if ($this->hasEnoughItems() && $item->isType($this->type)) {
return ($this->discount / 100) * $item->getCost();
}
return 'Error: Not enough items';
}
public function hasEnoughItems(array $items): bool
{
$found = 0;
foreach ($items AS $item) {
if ($item->isType($this->type)) {
$found++;
}
if ($found == $this->countItemsRequired) {
return true;
}
}
return false;
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class PercentageDiscount
{
private $discount;
public function __construct(int $discount)
{
$this->discount = $discount;
}
public function calculateAmountOff(Item $item): float
{
return ($this->discount / 100) * $item->getCost();
}
}
Class BuyItemsToGetDiscount extends PercentageDiscount
{
private $type;
private $countItemsRequired = 2;
public function setEligibleType(string $type)
{
$this->type = $type;
}
public function calculateAmountOff(Item $item): float
{
if ($this->hasEnoughItems() && $item->isType($this->type)) {
return ($this->discount / 100) * $item->getCost();
}
throw new NotEnoughItems();
}
public function hasEnoughItems(array $items): bool
{
$found = 0;
foreach ($items AS $item) {
if ($item->isType($this->type)) {
$found++;
}
if ($found == $this->countItemsRequired) {
return true;
}
}
return false;
}
}
Liskov Substitution Principle
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class PercentageDiscount
{
private $discount;
public function __construct(int $discount)
{
$this->discount = $discount;
}
public function calculateAmountOff(Item $item)
{
return ($this->discount / 100) * $item->getCost();
}
}
Class BuyItemsToGetDiscount extends PercentageDiscount
{
private $type;
private $countItemsRequired = 2;
public function setEligibleType(string $type)
{
$this->type = $type;
}
public function calculateAmountOff(Item $item)
{
if ($this->hasEnoughItems() && $item->isType($this->type)) {
return ($this->discount / 100) * $item->getCost();
}
throw new NotEnoughItems();
}
public function hasEnoughItems(array $items): bool
{
$found = 0;
foreach ($items AS $item) {
if ($item->isType($this->type)) {
$found++;
}
if ($found == $this->countItemsRequired) {
return true;
}
}
return false;
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Class BuyItemsToGetDiscount extends PercentageDiscount
{
private $type;
private $countItemsRequired = 2;
public function __construct($discount, $type)
{
$this->type = $type;
parent::__construct($discount);
}
public function calculateAmountOff(Item $item)
{
if ($this->hasEnoughItems() && $item->isType($this->type)) {
return ($this->discount / 100) * $item->getCost();
}
return 0;
}
public function hasEnoughItems(array $items): bool
{
$found = 0;
foreach ($items AS $item) {
if ($item->isType($this->type)) {
$found++;
}
if ($found == $this->countItemsRequired) {
return true;
}
}
return false;
}
}
Class PercentageDiscount
{
private $discount;
public function __construct(int $discount)
{
$this->discount = $discount;
}
public function calculateAmountOff(Item $item)
{
return ($this->discount / 100) * $item->getCost();
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Interface Discount
{
/** @throws DiscountException */
public function calculateAmountOff(Item $item): float;
}
Class PercentageDiscount implements Discount
{
private $discount;
public function __construct(int $discount)
{
$this->discount = $discount;
}
public function calculateAmountOff(Item $item): float
{
return ($this->discount / 100) * $item->getCost();
}
}
Class BuyEligibleItemsToGetDiscount extends PercentageDiscount
{
public function calculateAmountOff(Item $item): float { [...] }
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Interface Item
{
public function getName(): string;
public function getPrice(): float;
public function getShippingWeight(): float;
public function getShippingCost(): float;
}
class Cart
{
private $items;
private $total;
public function addItem(Item $item)
{
$this->items[] = $item;
}
public function calculateTotal()
{
$subtotal = 0;
$shippingTotal = 0;
foreach ($this->items AS $item) {
$subtotal += $item->getPrice();
$shippingTotal += $item->getShippingCost();
}
return $subtotal + $shippingTotal;
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Interface Item
{
public function getName(): string;
public function getPrice(): float;
public function getShippingWeight(): float;
public function getShippingCost(): float;
public function getWorkerAssigned(): Worker;
public function assignWorker(Worker $worker);
public function getServiceScheduledDate(): DateTime;
public function getServiceCompletedDate(): DateTime;
public function isCompleted(): bool;
}
Class ServiceManager
{
private $services;
public function addService(Item $item)
{
$this->services[] = $item;
}
public function getCompletedServices()
{
$completed = [];
foreach ($this->services AS $service) {
if ($service->isCompleted()) {
$completed[] = $service;
}
}
return $completed;
}
}
Interface Segregation
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Interface Buyable
{
public function getName(): string;
public function getPrice(): float;
}
Interface Product
{
public function getShippingWeight(): float;
public function getShippingCost(): float;
}
Interface Service
{
public function getWorkerAssigned(): Worker;
public function assignWorker(Worker $worker);
public function getServiceScheduledDate(): DateTime;
public function getServiceCompletedDate(): DateTime;
public function isCompleted(): bool;
}
class Cart
{
private $products;
public function addItem(Product $product){
$this->products[] = $product;
}
public function calculateTotal(){
$total = 0;
foreach ($this->products AS $p) {
$total += $p->getPrice() + $p->getShippingCost();
}
return $total;
}
}
Class ServiceManager
{
private $services;
public function addService(Service $service){
$this->services[] = $service;
}
public function getCompletedServices(){
$completed = [];
foreach ($this->services AS $service){
if ($service->isCompleted()) {
$completed[] = $service;
}
}
return $completed;
}
}
Class BuyableItem implements Buyable
{
protected $name, $price;
public function __construct(string $name, float $price) {
$this->name = $name;
$this->price = $price;
}
public function getName(): string {
return $this->name;
}
public function getPrice(): float {
return $this->price;
}
}
Class BuyableProduct extends BuyableItem implements Product
{
protected $shippingCost, $shippingWeight;
public function __construct(string $name, float $price,
float $shippingCost, float $shippingWeight) {
$this->shippingCost = $shippingCost;
$this->shippingWeight = $shippingWeight;
parent::__construct($name, $price);
}
public function getShippingCost(): float {
return $this->getShippingCost();
}
public function getShippingWeight(): float {
return $this->shippingWeight;
}
}
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
S.O.L.I.D.
● Single Responsibility - Registration Class delegating to Collaborators
● Open/Closed - Adding new Notifiers without always editing Listener
● Liskov Substitution - Ensured Discount subtypes could be used as Discount
● Interface Segregation - Items use Buyable, Product and Service interfaces
● Dependency Inversion - Registration Controller, Emailer, Shopping Cart,
Notification Factory - all use interfaces, not implementations
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
SLD: Apply Often
& Preemptively
● Single Responsibility
● Liskov Substitution
● Dependency Inversion
OI: Apply As Needed
When Changes Occur
● Open/Closed
● Interface Segregation
@jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
Thank You!
SOLID in Practice
Feedback & Questions?
Welcome & Encouraged!
@jessicamauerhan
jessicamauerhan@gmail.com
jmauerhan.wordpress.com

More Related Content

Recently uploaded

Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Allon Mureinik
 
Snow Chain-Integrated Tire for a Safe Drive on Winter Roads
Snow Chain-Integrated Tire for a Safe Drive on Winter RoadsSnow Chain-Integrated Tire for a Safe Drive on Winter Roads
Snow Chain-Integrated Tire for a Safe Drive on Winter RoadsHyundai Motor Group
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreternaman860154
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitecturePixlogix Infotech
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Patryk Bandurski
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Alan Dix
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
Pigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsMemoori
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
Maximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxMaximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxOnBoard
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
 

Recently uploaded (20)

Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)
 
Snow Chain-Integrated Tire for a Safe Drive on Winter Roads
Snow Chain-Integrated Tire for a Safe Drive on Winter RoadsSnow Chain-Integrated Tire for a Safe Drive on Winter Roads
Snow Chain-Integrated Tire for a Safe Drive on Winter Roads
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC Architecture
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
 
The transition to renewables in India.pdf
The transition to renewables in India.pdfThe transition to renewables in India.pdf
The transition to renewables in India.pdf
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
Pigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping Elbows
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial Buildings
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
Maximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxMaximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptx
 
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptxE-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
 

Featured

2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by HubspotMarius Sescu
 
Everything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTEverything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTExpeed Software
 
Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsPixeldarts
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthThinkNow
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfmarketingartwork
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024Neil Kimberley
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)contently
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024Albert Qian
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsKurio // The Social Media Age(ncy)
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Search Engine Journal
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summarySpeakerHub
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next Tessa Mero
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentLily Ray
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best PracticesVit Horky
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project managementMindGenius
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...RachelPearson36
 

Featured (20)

2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot
 
Everything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTEverything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPT
 
Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage Engineerings
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental Health
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
 
Skeleton Culture Code
Skeleton Culture CodeSkeleton Culture Code
Skeleton Culture Code
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie Insights
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search Intent
 
How to have difficult conversations
How to have difficult conversations How to have difficult conversations
How to have difficult conversations
 
Introduction to Data Science
Introduction to Data ScienceIntroduction to Data Science
Introduction to Data Science
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best Practices
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project management
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
 

SOLID in Practice

  • 1. SOLID In Practice @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest
  • 2. @jessicamauerhan Senior Software Engineer TDD/BDD Expert jessicamauerhan@gmail.com jmauerhan.wordpress.com
  • 3. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest What is SOLID? ● Five principles for Object Oriented Programming ● Guidelines which can help ensure system is easy to maintain ● Primarily focused on communication between dependencies or collaborators
  • 4. Let ϕ( ) be a property provable about objects of type T. Then ϕ( ) should be true for objects of type S where S is a subtype of T
  • 5. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class Rectangle { protected $length; protected $height; public function area() { return $this->length * $this->area(); } public function setLength($length) { $this->length = $length; } public function setHeight($height) { $this->height = $height; } } Class Square extends Rectangle { public function setLength($length) { $this->length = $length; $this->height = $length; } public function setHeight($height) { $this->length = $height; $this->height = $height; } }
  • 7. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest S.O.L.I.D. ● Single Responsibility ● Open/Closed ● Liskov Substitution ● Interface Segregation ● Dependency Inversion
  • 8. It's All About Change
  • 9. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest User Registration ● Collect User Input ● Validate User Input ● Persist User Data ● Send Confirmation Email
  • 10. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class UserRegistration { public function register(Request $request): Response { //Collect user input if (!$request->has('email') || !$request->has('password')) { return new Response('register', ['error' => 'Please provide an email and a password']); } $user = new User(); $user->email = $request->get('email'); $user->password = $request->get('password'); } }
  • 11. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class UserRegistration { public function register(Request $request): Response { [...] } private function emailIsRegistered(string $email): bool { $dsn = 'host='.$_ENV['DB_HOST'].' dbname='.$_ENV['DB_DB'].' password='.$_ENV['DB_PASS'].' user='.$_ENV['DB_USER']; $db = pg_connect($dsn); $result = pg_query($db, "SELECT id FROM users WHERE (email='{$email}')"); $rows = pg_num_rows($result); return $rows > 0; } }
  • 12. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class UserRegistration { public function register(Request $request): Response { //Collect user input if (!$request->has('email') || !$request->has('password')) { return new Response('register', ['error' => 'Please provide an email and a password']); } $user = new User(); $user->email = $request->get('email'); $user->password = $request->get('password'); //Validate user input if ($this->emailIsRegistered($user->getEmail())) { return new Response('register', ['error' => 'Your email address is already registered']); } } private function emailIsRegistered(string $email): bool { $dsn = 'host='.$_ENV['DB_HOST'].' dbname='.$_ENV['DB_DB'].' password='.$_ENV['DB_PASS'].' user='.$_ENV['DB_USER']; $db = pg_connect($dsn); $result = pg_query($db, "SELECT id FROM users WHERE (email='{$email}')"); $rows = pg_num_rows($result); return $rows > 0; } }
  • 13. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class UserRegistration { public function register(Request $request): Response { [...] } private function saveUser(User $user) { $dsn = 'host='.$_ENV['DB_HOST'].' dbname='.$_ENV['DB_DB'].' password='.$_ENV['DB_PASS'].' user='.$_ENV['DB_USER']; $db = pg_connect($dsn); pg_query($db, "INSERT INTO users(email, password) VALUES('{$user->email}', '{$user->password}')"); } private function emailIsRegistered(string $email): bool { $dsn = 'host='.$_ENV['DB_HOST'].' dbname='.$_ENV['DB_DB'].' password='.$_ENV['DB_PASS'].' user='.$_ENV['DB_USER']; $db = pg_connect($dsn); $result = pg_query($db, "SELECT id FROM users WHERE (email='{$email}')"); $rows = pg_num_rows($result); return $rows > 0; } }
  • 14. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class UserRegistration { public function register(Request $request): Response { [...] } private function saveUser(User $user) { DB::query("INSERT INTO users(email, password) VALUES('{$user->email}', '{$user->password}')"); } private function emailIsRegistered(string $email): bool { $rows = DB::query("SELECT count(id) FROM users WHERE (email='{$email}')"); return $rows > 0; } }
  • 15. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class UserRegistration { public function register(Request $request): Response { //Collect user input if (!$request->has('email') || !$request->has('password')) { return new Response('register', ['error' => 'Please provide an email and a password']); } $user = new User(); $user->email = $request->get('email'); $user->password = $request->get('password'); //Validate user input if ($this->emailIsRegistered($user->getEmail())) { return new Response('register', ['error' => 'Your email address is already registered']); } //Persist User $this->saveUser($user); //Send Confirmation Email $this->sendConfirmationEmail($user->getEmail()); return new Response('register', ['success' => 'You are registered! Please check your email!']); } private function sendConfirmationEmail(string $email) { $subject = "Confirm Email"; $message = "Please <a>click here</a> to confirm your email!"; $headers = "From: mysite@email.com"; mail($email, $subject, $message, $headers); } }
  • 16. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class UserRegistration { public function register(Request $request): Response { //Collect user input if (!$request->has('email') || !$request->has('password')) { return new Response('register', ['error' => 'Please provide an email and a password']); } $user = new User(); $user->email = $request->get('email'); $user->password = $request->get('password'); //Validate user input if ($this->emailIsRegistered($user->getEmail())) { return new Response('register', ['error' => 'Your email address is already registered']); } //Persist User $this->saveUser($user); //Send Confirmation Email $this->sendConfirmationEmail($user->getEmail()); return new Response('register', ['success' => 'You are registered! Please check your email!']); } private function sendConfirmationEmail(string $email) { $subject = "Confirm Email"; $message = "Please <a>click here</a> to confirm your email!"; $headers = "From: mysite@email.com"; mail($email, $subject, $message, $headers); } private function saveUser(User $user) { DB::query("INSERT INTO users(email, password) VALUES('{$user->email}', '{$user->password}')"); } private function emailIsRegistered(string $email): bool { $rows = DB::query("SELECT count(id) FROM users WHERE (email='{$email}')"); return $rows > 0; } }
  • 17. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class UserRegistration { public function register(Request $request): Response { //Collect user input if (!$request->has('email') || !$request->has('password')) { return new Response('register', ['error' => 'Please provide an email and a password']); } $user = new User(); $user->email = $request->get('email'); $user->password = $request->get('password'); //Validate user input if ($this->emailIsRegistered($user->getEmail())) { return new Response('register', ['error' => 'Your email address is already registered']); } //Persist User $this->saveUser($user); //Send Confirmation Email $this->sendConfirmationEmail($user->getEmail()); return new Response('register', ['success' => 'You are registered! Please check your email!']); } private function sendConfirmationEmail(string $email) { $subject = "Confirm Email"; $message = "Please <a>click here</a> to confirm your email!"; $headers = "From: mysite@email.com"; mail($email, $subject, $message, $headers); } private function saveUser(User $user) { DB::query("INSERT INTO users(email, password) VALUES('{$user->email}', '{$user->password}')"); } private function emailIsRegistered(string $email): bool { $numRows = DB::query("SELECT count(id) FROM users WHERE (email='{$email}')"); return $rows > 0; } }
  • 18. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Dependency Injection ● Collaborators are supplied to class from outside - aka "injected" into it Service Location ● Collaborators are retrieved or instantiated from inside the class using them
  • 19. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class UserRegistration { private $db; public function __construct(Database $db) { $this->db = $db; } public function register(Request $request): Response { [...] } private function saveUser(User $user) { $sql = "INSERT INTO users(email, password) VALUES('{$user->email}', '{$user->password}')" $this->db->query($sql); } private function emailIsRegistered(string $email): bool { $rows = $this->db->query("SELECT count(id) FROM users WHERE (email='{$email}')"); return $rows > 0; }
  • 20. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class UserRegistration { private $db; private $mailer; public function __construct(Database $db, Mailer $mailer) { $this->db = $db; $this->mailer = $mailer; } public function register(Request $request): Response { [...] } private function sendConfirmationEmail(string $email) { //Convert our email to Mandrill's email $mandrillEmail = [ 'to' => ['email' => $email->getTo()], 'from_email' => $email->getFrom(), 'subject' => $email->getSubject(), 'text' => $email->getMessage() ]; $this->mandrill->send($mandrillEmail); }
  • 23. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class UserRegistration { private $factory; public function __construct(UserFactory $factory) { $this->factory = $factory; } public function register(Request $request): Response { try { $user = $this->factory->createUser($request); } catch (UnableToParseUser $exception) { return new InvalidRequestResponse($exception->getErrors()); } } } Class UserRegistration { private $factory, $validator; public function __construct(UserFactory $factory, UserValidator $validator) { $this->factory = $factory; $this->validator = $validator; } public function register(Request $request): Response { try { $user = $this->factory->createUser($request); } catch (UnableToParseUser $exception) { return new InvalidRequestResponse($exception->getErrors()); } if ($this->validator->isValid($user) === false) { $invalidProperties = $this->validator ->getInvalidProperties($user); return new InvalidUserResponse($invalidProperties); } } } Class UserRegistration { private $factory, $validator, $repository; public function __construct(UserFactory $factory, UserValidator $validator, UserRepository $repository) { $this->factory = $factory; $this->validator = $validator; $this->repository = $repository; } public function register(Request $request): Response { try { $user = $this->factory->createUser($request); } catch (UnableToParseUser $exception) { return new InvalidRequestResponse($exception->getErrors()); } if ($this->validator->isValid($user) === false) { $invalidProperties = $this->validator ->getInvalidProperties($user); return new InvalidUserResponse($invalidProperties); } try { $this->repository->create($user); return UserCreatedResponse($user); } catch (UnableToCreateUser $exception) { return UnableToCreateUserResponse($user); } } } Class UserRegistration { private $factory, $validator, $repository, $emailer; public function __construct(UserFactory $factory, UserValidator $validator, UserRepository $repository, ConfirmationEmailer $emailer) { $this->factory = $factory; $this->validator = $validator; $this->repository = $repository; $this->emailer = $emailer; } public function register(Request $request): Response { try { $user = $this->factory->createUser($request); } catch (UnableToParseUser $exception) { return new InvalidRequestResponse($exception->getErrors()); } if ($this->validator->isValid($user) === false) { $invalidProperties = $this->validator ->getInvalidProperties($user); return new InvalidUserResponse($invalidProperties); } try { $this->repository->create($user); $this->emailer->sendConfirmationEmail($user); return UserCreatedResponse($user); } catch (UnableToCreateUser $exception) { return UnableToCreateUserResponse($user); } catch (MailerException $mailerException) { return UnableToSendConfirmationEmailResponse($user); } } } interface UserFactory { /** * @throws UnableToParseUser */ public function createRegisteringUser(Request $request): User; } interface UserValidator { public function isValid(User $user): bool; public function getInvalidProperties(User $user):array; } interface UserRepository { /** * @throws UnableToCreateUser */ public function create(User $user): bool; } interface ConfirmationEmailer { /** * @throws MailerException */ public function send(string $to); }
  • 24. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class UserRegistration { private $factory; public function __construct(UserFactory $factory) { $this->factory = $factory; } public function register(Request $request): Response { try { $user = $this->factory->createUser($request); } catch (UnableToParseUser $exception) { return new InvalidRequestResponse($exception->getErrors()); } } } Class UserRegistration { private $factory, $validator; public function __construct(UserFactory $factory, UserValidator $validator) { $this->factory = $factory; $this->validator = $validator; } public function register(Request $request): Response { try { $user = $this->factory->createUser($request); } catch (UnableToParseUser $exception) { return new InvalidRequestResponse($exception->getErrors()); } if ($this->validator->isValid($user) === false) { $invalidProperties = $this->validator ->getInvalidProperties($user); return new InvalidUserResponse($invalidProperties); } } } Class UserRegistration { private $factory, $validator, $repository; public function __construct(UserFactory $factory, UserValidator $validator, UserRepository $repository) { $this->factory = $factory; $this->validator = $validator; $this->repository = $repository; } public function register(Request $request): Response { try { $user = $this->factory->createUser($request); } catch (UnableToParseUser $exception) { return new InvalidRequestResponse($exception->getErrors()); } if ($this->validator->isValid($user) === false) { $invalidProperties = $this->validator ->getInvalidProperties($user); return new InvalidUserResponse($invalidProperties); } try { $this->repository->create($user); return UserCreatedResponse($user); } catch (UnableToCreateUser $exception) { return UnableToCreateUserResponse($user); } } } Class UserRegistration { private $factory, $validator, $repository, $emailer; public function __construct(UserFactory $factory, UserValidator $validator, UserRepository $repository, ConfirmationEmailer $emailer) { $this->factory = $factory; $this->validator = $validator; $this->repository = $repository; $this->emailer = $emailer; } public function register(Request $request): Response { try { $user = $this->factory->createUser($request); } catch (UnableToParseUser $exception) { return new InvalidRequestResponse($exception->getErrors()); } if ($this->validator->isValid($user) === false) { $invalidProperties = $this->validator ->getInvalidProperties($user); return new InvalidUserResponse($invalidProperties); } try { $this->repository->create($user); $this->emailer->sendConfirmationEmail($user); return UserCreatedResponse($user); } catch (UnableToCreateUser $exception) { return UnableToCreateUserResponse($user); } catch (MailerException $mailerException) { return UnableToSendConfirmationEmailResponse($user); } } } interface UserFactory { /** * @throws UnableToParseUser */ public function createRegisteringUser(Request $request): User; } interface UserValidator { public function isValid(User $user): bool; public function getInvalidProperties(User $user):array; } interface UserRepository { /** * @throws UnableToCreateUser */ public function create(User $user): bool; } interface ConfirmationEmailer { /** * @throws MailerException */ public function send(string $to); }
  • 25. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest interface UserFactory { /** * @throws UnableToParseUser */ public function createUser(Request $request): User; } class RegisterFormUserFactory implements UserFactory { /** * @throws UnableToParseUser */ public function createUser(Request $request): User { if(!$request->has('email') || !$request->has('password')){ throw new UnableToParseUser(); } $email = $request->get('email'); $password = $request->get('password'); return new User($email, $password); } }
  • 26. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest interface UserValidator { public function isValid(User $user): bool; public function getInvalidProperties(User $user):array; } class NewUserValidator implements UserValidator { /** @var UserRepository */ private $userRepository; public function __construct(UserRepository $userRepository) { $this->userRepository = $userRepository; } public function isValid(User $user): bool { $emailExists = $this->userRepository->emailExists($user->getEmail()); if ($emailExists === true) { return false; } return true; } }
  • 27. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest interface UserRepository { /** * @param User $user * @return bool * * @throws UnableToCreateUser */ public function create(User $user): bool; /** * @param string $email * @return bool * * @throws DataSourceError */ public function emailExists(string $email): bool; }
  • 28. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest class PdoUserRepository implements UserRepository { /** @var Pdo */ private $pdo; public function __construct(Pdo $pdo) { $this->pdo = $pdo; } public function create(User $user): bool { try { $statement = $this->pdo->prepare("INSERT INTO users(email, password) VALUES(:email, :password)"); $statement->execute(['email' => $user->getEmail(), 'password' => $user->getPassword()]); } catch (PDOException $PDOException) { throw new UnableToCreateUser($user->getEmail()); } return true; } public function emailExists(string $email): bool { try { $statement = $this->pdo->prepare("SELECT count(id) AS numRows FROM users WHERE email = :email"); $statement->execute([email => $email]); $result = $statement->fetchColumn(); return ($result > 0); } catch (PDOException $PDOException) { throw new DataSourceError(); } } }
  • 29. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest interface ConfirmationEmailer { /** * @throws MailerException */ public function send(string $to); } class MandrillConfirmationEmailer implements ConfirmationEmailer { private $mandrill; public function __construct(Mandrill $mandrill) { $this->mandrill = $mandrill; } public function send(User $user) { $message = "Please <a>click here</a> to confirm your account."; $from = "app@example.com"; $subject = "Confirm your account"; $email = [ 'to' => [['EmailTemplate' => $user->getEmail()]], 'from_email' => $from, 'subject' => $subject, 'text' => $message ]; $this->mandrill->send($email); } }
  • 30. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest interface ConfirmationEmailer { public function send(string $to); /** @throws MailerException */ }
  • 31. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest interface ConfirmationEmailer { public function send(string $to); /** @throws MailerException */ } interface Mailer { public function send(Email $email); }
  • 32. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest interface ConfirmationEmailer { public function send(string $to); /** @throws MailerException */ } interface Mailer { public function send(Email $email); } class Email { private $to, $from, $subject, $message; public function __construct(string $to, string $from, string $subject, string $message) { $this->to = $to; $this->from = $from; $this->subject = $subject; $this->message = $message; } public function getTo(): string { return $this->to; } public function getFrom(): string { return $this->from; } public function getSubject(): string { return $this->subject; } public function getMessage(): string { return $this->message; } }
  • 33. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest interface ConfirmationEmailer { public function send(string $to); /** @throws MailerException */ } interface Mailer { public function send(Email $email); } class Email { private $to, $from, $subject, $message; public function __construct(string $to, string $from, string $subject, string $message) { $this->to = $to; $this->from = $from; $this->subject = $subject; $this->message = $message; } public function getTo(): string { return $this->to; } public function getFrom(): string { return $this->from; } public function getSubject(): string { return $this->subject; } public function getMessage(): string { return $this->message; } } class ConfirmationEmailService implements ConfirmationEmailer { private $mailer; private $from = 'mysite@example.com'; private $subject = 'Confirm Email'; private $message = 'Please click here to confirm your email'; public function __construct(Mailer $mailer) { $this->mailer = $mailer; } public function send(string $to) { $email = new Email($to, $this->from, $this->subject, $this->message); $this->mailer->send($email); } }
  • 34. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest interface ConfirmationEmailer { public function send(string $to); /** @throws MailerException */ } interface Mailer { public function send(Email $email); } class Email { private $to, $from, $subject, $message; public function __construct(string $to, string $from, string $subject, string $message) { $this->to = $to; $this->from = $from; $this->subject = $subject; $this->message = $message; } public function getTo(): string { return $this->to; } public function getFrom(): string { return $this->from; } public function getSubject(): string { return $this->subject; } public function getMessage(): string { return $this->message; } } class ConfirmationEmailService implements ConfirmationEmailer { private $mailer; private $from = 'mysite@example.com'; private $subject = 'Confirm Email'; private $message = 'Please click here to confirm your email'; public function __construct(Mailer $mailer) { $this->mailer = $mailer; } public function send(string $to) { $email = new Email($to, $this->from, $this->subject, $this->message); $this->mailer->send($email); } } class MandrillMailer implements Mailer { private $mandrill; public function __construct(Mandrill $mandrill) { $this->mandrill = $mandrill; } public function send(Email $email) { //Convert our email to Mandrill's email $mandrillMail = ['to' => ['email' => $email->getTo()], 'from_email' => $email->getFrom(), 'subject' => $email->getSubject(), 'text' => $email->getMessage()]; $this->mandrill->send($mandrillEmail); } }
  • 35. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest interface ConfirmationEmailer { public function send(string $to); /** @throws MailerException */ } interface Mailer { public function send(Email $email); } class Email { private $to, $from, $subject, $message; public function __construct(string $to, string $from, string $subject, string $message) { $this->to = $to; $this->from = $from; $this->subject = $subject; $this->message = $message; } public function getTo(): string { return $this->to; } public function getFrom(): string { return $this->from; } public function getSubject(): string { return $this->subject; } public function getMessage(): string { return $this->message; } } class ConfirmationEmailService implements ConfirmationEmailer { private $mailer; private $from = 'mysite@example.com'; private $subject = 'Confirm Email'; private $message = 'Please click here to confirm your email'; public function __construct(Mailer $mailer) { $this->mailer = $mailer; } public function send(string $to) { $email = new Email($to, $this->from, $this->subject, $this->message); $this->mailer->send($email); } } class SendGridMailer implements Mailer { private $sendgrid; public function __construct(SendGrid $sendgrid) { $this->sedngrid = $sendgrid; } public function send(Email $email) { //Convert our email to SendGrid's email $sgEmail = new SendGridEmail($email->getTo(), $email->getFrom(), $email->getSubject(), $email->getMessage()); $this->sendgrid->queue($sgEmail); } }
  • 36. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest class AccountOverdueListener { private $mailer; public function __construct(Mailer $mailer) { $this->mailer = $mailer; } public function handle(OverdueEvent $event) { $account = $event->getAccount(); $email = new OverdueEmail($account->getEmailAddress(), $account->getAmountDue()); $this->mailer->send($email); } }
  • 37. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest class AccountOverdueListener { private $mailer; private $texter; public function __construct(Mailer $mailer, Texter $texter) { $this->mailer = $mailer; $this->texter = $texter; } public function handle(OverdueEvent $event) { $account = $event->getAccount(); $notifications = $account->getEnabledNotifications(); foreach ($notifications AS $notificationType) { if ($notificationType === 'email') { $email = new OverdueEmail($account->getEmailAddress(), $account->getAmountDue()); $this->mailer->send($email); } elseif ($notificationType === 'text') { $text = new OverdueText($account->getPhoneNumber(), $account->getAmountDue()); $this->texter->send($text); } } } }
  • 39. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Interface Notifier { public function notify(ContactInfo $contact); }
  • 40. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Interface Notifier { public function notify(ContactInfo $contact); } Class EmailNotifier implements Notifier { private $mailer; public function __construct(Mailer $mailer) { $this->mailer = $mailer; } public function notify(ContactInfo $contact) { $email = new OverdueEmail($contact->getEmailAddress()); $this->mailer->send($email); } } Class TextNotifier implements Notifier { private $texter; public function __construct(Texter $texter) { $this->texter = $texter; } public function notify(ContactInfo $contact) { $text = 'Your Account Is Overdue!'; $this->texter->send($text, $contact->getPhoneNumber()); } }
  • 41. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Interface Notifier { public function notify(ContactInfo $contact); } Class NotifierFactory { private $notifiers; public function register($name, Notifier $notifier) { $this->notifiers[$name] = $notifier; } public function getNotifier(string $name): Notifier { return $this->notifiers[$name]; } } Class EmailNotifier implements Notifier { private $mailer; public function __construct(Mailer $mailer) { $this->mailer = $mailer; } public function notify(ContactInfo $contact) { $email = new OverdueEmail($contact->getEmailAddress()); $this->mailer->send($email); } } Class TextNotifier implements Notifier { private $texter; public function __construct(Texter $texter) { $this->texter = $texter; } public function notify(ContactInfo $contact) { $text = 'Your Account Is Overdue!'; $this->texter->send($text, $contact->getPhoneNumber()); } }
  • 42. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest class AccountOverdueListener { private $notifierFactory; public function __construct(NotifierFactory $notifierFactory) { $this->notifierFactory = $notifierFactory; } public function handle(OverdueEvent $event) { $account = $event->getAccount(); $contact = $account->getContactInfo(); $notifications = $account->getEnabledNotifications(); foreach ($notifications AS $notificationType) { $notifier = $this->notifierFactory->getNotifier($notificationType); $notifier->send($contact); } } }
  • 43. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest class AccountOverdueEmailListener { public function handle(Mailer $mailer, OverdueEvent $event) { $account = $event->getAccount(); $email = new OverdueEmail($account->getEmailAddress(), $account->getAmountDue()); $mailer->send($email); } } class AccountOverdueTextListener { public function handle(Texter $texter, OverdueEvent $event) { $account = $event->getAccount(); $text = new OverdueText($account->getPhoneNumber(), $account->getAmountDue()); $texter->send($text); } }
  • 44. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class PercentageDiscount { private $discount; public function __construct(int $discount) { $this->discount = $discount; } public function calculateAmountOff(Item $item): float { return ($this->discount / 100) * $item->getCost(); } }
  • 45. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class PercentageDiscount { private $discount; public function __construct(int $discount) { $this->discount = $discount; } public function calculateAmountOff(Item $item) { return ($this->discount / 100) * $item->getCost(); } } Class BuyItemsToGetDiscount extends PercentageDiscount { private $type; private $countItemsRequired = 2; public function setEligibleType(string $type) { $this->type = $type; } public function calculateAmountOff(Item $item) { if ($this->hasEnoughItems() && $item->isType($this->type)) { return ($this->discount / 100) * $item->getCost(); } return 'Error: Not enough items'; } public function hasEnoughItems(array $items): bool { $found = 0; foreach ($items AS $item) { if ($item->isType($this->type)) { $found++; } if ($found == $this->countItemsRequired) { return true; } } return false; } }
  • 46. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class PercentageDiscount { private $discount; public function __construct(int $discount) { $this->discount = $discount; } public function calculateAmountOff(Item $item): float { return ($this->discount / 100) * $item->getCost(); } } Class BuyItemsToGetDiscount extends PercentageDiscount { private $type; private $countItemsRequired = 2; public function setEligibleType(string $type) { $this->type = $type; } public function calculateAmountOff(Item $item): float { if ($this->hasEnoughItems() && $item->isType($this->type)) { return ($this->discount / 100) * $item->getCost(); } throw new NotEnoughItems(); } public function hasEnoughItems(array $items): bool { $found = 0; foreach ($items AS $item) { if ($item->isType($this->type)) { $found++; } if ($found == $this->countItemsRequired) { return true; } } return false; } }
  • 48. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class PercentageDiscount { private $discount; public function __construct(int $discount) { $this->discount = $discount; } public function calculateAmountOff(Item $item) { return ($this->discount / 100) * $item->getCost(); } } Class BuyItemsToGetDiscount extends PercentageDiscount { private $type; private $countItemsRequired = 2; public function setEligibleType(string $type) { $this->type = $type; } public function calculateAmountOff(Item $item) { if ($this->hasEnoughItems() && $item->isType($this->type)) { return ($this->discount / 100) * $item->getCost(); } throw new NotEnoughItems(); } public function hasEnoughItems(array $items): bool { $found = 0; foreach ($items AS $item) { if ($item->isType($this->type)) { $found++; } if ($found == $this->countItemsRequired) { return true; } } return false; } }
  • 49. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Class BuyItemsToGetDiscount extends PercentageDiscount { private $type; private $countItemsRequired = 2; public function __construct($discount, $type) { $this->type = $type; parent::__construct($discount); } public function calculateAmountOff(Item $item) { if ($this->hasEnoughItems() && $item->isType($this->type)) { return ($this->discount / 100) * $item->getCost(); } return 0; } public function hasEnoughItems(array $items): bool { $found = 0; foreach ($items AS $item) { if ($item->isType($this->type)) { $found++; } if ($found == $this->countItemsRequired) { return true; } } return false; } } Class PercentageDiscount { private $discount; public function __construct(int $discount) { $this->discount = $discount; } public function calculateAmountOff(Item $item) { return ($this->discount / 100) * $item->getCost(); } }
  • 50. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Interface Discount { /** @throws DiscountException */ public function calculateAmountOff(Item $item): float; } Class PercentageDiscount implements Discount { private $discount; public function __construct(int $discount) { $this->discount = $discount; } public function calculateAmountOff(Item $item): float { return ($this->discount / 100) * $item->getCost(); } } Class BuyEligibleItemsToGetDiscount extends PercentageDiscount { public function calculateAmountOff(Item $item): float { [...] } }
  • 51. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Interface Item { public function getName(): string; public function getPrice(): float; public function getShippingWeight(): float; public function getShippingCost(): float; } class Cart { private $items; private $total; public function addItem(Item $item) { $this->items[] = $item; } public function calculateTotal() { $subtotal = 0; $shippingTotal = 0; foreach ($this->items AS $item) { $subtotal += $item->getPrice(); $shippingTotal += $item->getShippingCost(); } return $subtotal + $shippingTotal; } }
  • 52. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Interface Item { public function getName(): string; public function getPrice(): float; public function getShippingWeight(): float; public function getShippingCost(): float; public function getWorkerAssigned(): Worker; public function assignWorker(Worker $worker); public function getServiceScheduledDate(): DateTime; public function getServiceCompletedDate(): DateTime; public function isCompleted(): bool; } Class ServiceManager { private $services; public function addService(Item $item) { $this->services[] = $item; } public function getCompletedServices() { $completed = []; foreach ($this->services AS $service) { if ($service->isCompleted()) { $completed[] = $service; } } return $completed; } }
  • 54. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Interface Buyable { public function getName(): string; public function getPrice(): float; } Interface Product { public function getShippingWeight(): float; public function getShippingCost(): float; } Interface Service { public function getWorkerAssigned(): Worker; public function assignWorker(Worker $worker); public function getServiceScheduledDate(): DateTime; public function getServiceCompletedDate(): DateTime; public function isCompleted(): bool; } class Cart { private $products; public function addItem(Product $product){ $this->products[] = $product; } public function calculateTotal(){ $total = 0; foreach ($this->products AS $p) { $total += $p->getPrice() + $p->getShippingCost(); } return $total; } } Class ServiceManager { private $services; public function addService(Service $service){ $this->services[] = $service; } public function getCompletedServices(){ $completed = []; foreach ($this->services AS $service){ if ($service->isCompleted()) { $completed[] = $service; } } return $completed; } } Class BuyableItem implements Buyable { protected $name, $price; public function __construct(string $name, float $price) { $this->name = $name; $this->price = $price; } public function getName(): string { return $this->name; } public function getPrice(): float { return $this->price; } } Class BuyableProduct extends BuyableItem implements Product { protected $shippingCost, $shippingWeight; public function __construct(string $name, float $price, float $shippingCost, float $shippingWeight) { $this->shippingCost = $shippingCost; $this->shippingWeight = $shippingWeight; parent::__construct($name, $price); } public function getShippingCost(): float { return $this->getShippingCost(); } public function getShippingWeight(): float { return $this->shippingWeight; } }
  • 55. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest S.O.L.I.D. ● Single Responsibility - Registration Class delegating to Collaborators ● Open/Closed - Adding new Notifiers without always editing Listener ● Liskov Substitution - Ensured Discount subtypes could be used as Discount ● Interface Segregation - Items use Buyable, Product and Service interfaces ● Dependency Inversion - Registration Controller, Emailer, Shopping Cart, Notification Factory - all use interfaces, not implementations
  • 56. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest SLD: Apply Often & Preemptively ● Single Responsibility ● Liskov Substitution ● Dependency Inversion OI: Apply As Needed When Changes Occur ● Open/Closed ● Interface Segregation
  • 57. @jessicamauerhan | https://joind.in/talk/17a66 | @OpenWest Thank You! SOLID in Practice Feedback & Questions? Welcome & Encouraged! @jessicamauerhan jessicamauerhan@gmail.com jmauerhan.wordpress.com