SlideShare a Scribd company logo
SOLID In Practice
@jessicamauerhan | https://joind.in/talk/34870 | @PHPDet
@jessicamauerhan
Software Engineer
@HelpScout
jessicamauerhan@gmail.com
jmauerhan.wordpress.com
@jessicamauerhan | https://joind.in/talk/34870 | @PHPDet
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/34870 | @PHPDet
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;
}
}
@jessicamauerhan | https://joind.in/talk/34870 | @PHPDet
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/34870 | @PHPDet
User Registration
● Collect User Input
● Validate User Input
● Persist User Data
● Send Confirmation Email
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');
}
}
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');
}
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;
}
}
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;
}
}
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);
}
private function emailIsRegistered(string $email): bool { [...] }
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}')");
}
}
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;
}
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}')");
}
}
Class UserRegistration
{
public function register(Request $request): Response
{
[...]
}
private function emailIsRegistered(string $email): bool
{
$rows = DB::query("SELECT count(id) FROM users WHERE (email='{$email}')");
return $rows > 0;
}
private function saveUser(User $user)
{
DB::query("INSERT INTO users(email, password) VALUES('{$user->email}', '{$user->password}')");
}
}
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);
}
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;
}
}
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/34870 | @PHPDet
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
Class UserRegistration
{
public function register(Request $request): Response { [...] }
private function sendConfirmationEmail(string $email) { [...] }
private function saveUser(User $user)
{
$sql = "INSERT INTO users(email, password) VALUES('{$user->email}', '{$user->password}')"
DB::query($sql);
}
private function emailIsRegistered(string $email): bool
{
$rows = DB::query("SELECT count(id) FROM users WHERE (email='{$email}')");
return $rows > 0;
}
}
Class UserRegistration
{
private $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function register(Request $request): Response { [...] }
private function sendConfirmationEmail(string $email) { [...] }
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;
}
}
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);
}
private function saveUser(User $user) { [...] }
private function emailIsRegistered(string $email): bool { [...] }
Dependency Inversion
Single Responsibility Principle
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());
}
}
}
interface UserFactory
{
/**
* @throws UnableToParseUser
*/
public function createRegisteringUser(Request $request): 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;
}
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);
}
}
}
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 UserFactory
{
/**
* @throws UnableToParseUser
*/
public function createRegisteringUser(Request $request): User;
}
interface UserValidator
{
public function isValid(User $user): bool;
public function getInvalidProperties(User $user):array;
}
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);
}
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 UserFactory
{
/**
* @throws UnableToParseUser
*/
public function createRegisteringUser(Request $request): User;
}
interface UserValidator
{
public function isValid(User $user): bool;
public function getInvalidProperties(User $user):array;
}
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);
}
interface UserFactory
{
/**
* @throws UnableToParseUser
*/
public function createUser(Request $request): User;
}
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);
}
}
interface UserValidator
{
public function isValid(User $user): bool;
public function getInvalidProperties(User $user):array;
}
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;
}
}
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;
}
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();
}
}
}
interface ConfirmationEmailer
{
/**
* @throws MailerException
*/
public function send(string $to);
}
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);
}
}
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);
}
}
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);
}
}
interface ConfirmationEmailer
{
public function send(string $to); /** @throws MailerException */
}
interface ConfirmationEmailer
{
public function send(string $to); /** @throws MailerException */
}
interface Mailer
{
public function send(Email $email);
}
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;
}
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
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
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);
}
}
E-Commerce App
Class PercentageDiscount
{
private $discount;
public function __construct(int $discount)
{
$this->discount = $discount;
}
public function calculateAmountOff(Item $item)
{
return ($this->discount / 100) * $item->getCost();
}
}
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;
}
}
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
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;
}
}
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 __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;
}
Interface Discount
{
/** @throws DiscountException */
public function calculateAmountOff(Item $item): float;
}
Class DiscountException extends Exception
{
}
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 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();
}
throw new DiscountException ();
}
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;
}
Shopping Cart
Interface Item
{
public function getName(): string;
public function getPrice(): float;
public function getShippingWeight(): float;
public function getShippingCost(): float;
}
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;
}
}
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 Cart { [...] }
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
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;
}
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;
}
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 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;
}
}
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 BuyableItem implements Buyable { [...] }
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;
}
}
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/34870 | @PHPDet
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/34870 | @PHPDet
Thank You!
SOLID in Practice
https://joind.in/talk/34870
Feedback & Questions?
Welcome & Encouraged!
@jessicamauerhan
jessicamauerhan@gmail.com
jmauerhan.wordpress.com

More Related Content

What's hot

Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5
Leonardo Proietti
 
Presentation1
Presentation1Presentation1
Presentation1
Rahadyan Gusti
 
Doctrator Symfony Live 2011 San Francisco
Doctrator Symfony Live 2011 San FranciscoDoctrator Symfony Live 2011 San Francisco
Doctrator Symfony Live 2011 San Franciscopablodip
 
Type safe embedded domain-specific languages
Type safe embedded domain-specific languagesType safe embedded domain-specific languages
Type safe embedded domain-specific languages
Arthur Xavier
 
Drupal 8 database api
Drupal 8 database apiDrupal 8 database api
Drupal 8 database api
Viswanath Polaki
 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Kacper Gunia
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
Sam Hennessy
 
Taming Command Bus
Taming Command BusTaming Command Bus
Taming Command Bus
Krzysztof Menżyk
 
$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$
$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$
$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$UltraUploader
 
Devs for Leokz e 7Masters - WTF Oriented Programming
Devs for Leokz e 7Masters - WTF Oriented ProgrammingDevs for Leokz e 7Masters - WTF Oriented Programming
Devs for Leokz e 7Masters - WTF Oriented Programming
Fabio Akita
 
Data::FormValidator Simplified
Data::FormValidator SimplifiedData::FormValidator Simplified
Data::FormValidator Simplified
Fred Moyer
 
Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)
Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)
Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)
Krzysztof Menżyk
 
The Art of Transduction
The Art of TransductionThe Art of Transduction
The Art of Transduction
David Stockton
 
Leveraging Symfony2 Forms
Leveraging Symfony2 FormsLeveraging Symfony2 Forms
Leveraging Symfony2 Forms
Bernhard Schussek
 
The IoC Hydra
The IoC HydraThe IoC Hydra
The IoC Hydra
Kacper Gunia
 
Injection de dépendances dans Symfony >= 3.3
Injection de dépendances dans Symfony >= 3.3Injection de dépendances dans Symfony >= 3.3
Injection de dépendances dans Symfony >= 3.3
Vladyslav Riabchenko
 
Everything you always wanted to know about forms* *but were afraid to ask
Everything you always wanted to know about forms* *but were afraid to askEverything you always wanted to know about forms* *but were afraid to ask
Everything you always wanted to know about forms* *but were afraid to ask
Andrea Giuliano
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and HobgoblinsModels and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
Ross Tuck
 
How te bring common UI patterns to ADF
How te bring common UI patterns to ADFHow te bring common UI patterns to ADF
How te bring common UI patterns to ADF
Getting value from IoT, Integration and Data Analytics
 
Design Patterns in PHP5
Design Patterns in PHP5 Design Patterns in PHP5
Design Patterns in PHP5 Wildan Maulana
 

What's hot (20)

Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5
 
Presentation1
Presentation1Presentation1
Presentation1
 
Doctrator Symfony Live 2011 San Francisco
Doctrator Symfony Live 2011 San FranciscoDoctrator Symfony Live 2011 San Francisco
Doctrator Symfony Live 2011 San Francisco
 
Type safe embedded domain-specific languages
Type safe embedded domain-specific languagesType safe embedded domain-specific languages
Type safe embedded domain-specific languages
 
Drupal 8 database api
Drupal 8 database apiDrupal 8 database api
Drupal 8 database api
 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
 
Taming Command Bus
Taming Command BusTaming Command Bus
Taming Command Bus
 
$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$
$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$
$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$
 
Devs for Leokz e 7Masters - WTF Oriented Programming
Devs for Leokz e 7Masters - WTF Oriented ProgrammingDevs for Leokz e 7Masters - WTF Oriented Programming
Devs for Leokz e 7Masters - WTF Oriented Programming
 
Data::FormValidator Simplified
Data::FormValidator SimplifiedData::FormValidator Simplified
Data::FormValidator Simplified
 
Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)
Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)
Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)
 
The Art of Transduction
The Art of TransductionThe Art of Transduction
The Art of Transduction
 
Leveraging Symfony2 Forms
Leveraging Symfony2 FormsLeveraging Symfony2 Forms
Leveraging Symfony2 Forms
 
The IoC Hydra
The IoC HydraThe IoC Hydra
The IoC Hydra
 
Injection de dépendances dans Symfony >= 3.3
Injection de dépendances dans Symfony >= 3.3Injection de dépendances dans Symfony >= 3.3
Injection de dépendances dans Symfony >= 3.3
 
Everything you always wanted to know about forms* *but were afraid to ask
Everything you always wanted to know about forms* *but were afraid to askEverything you always wanted to know about forms* *but were afraid to ask
Everything you always wanted to know about forms* *but were afraid to ask
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and HobgoblinsModels and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
 
How te bring common UI patterns to ADF
How te bring common UI patterns to ADFHow te bring common UI patterns to ADF
How te bring common UI patterns to ADF
 
Design Patterns in PHP5
Design Patterns in PHP5 Design Patterns in PHP5
Design Patterns in PHP5
 

Similar to Solid in practice

50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes
Azim Kurt
 
laravel tricks in 50minutes
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutes
Barang CK
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your Code
Abbas Ali
 
PHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにPHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くために
Yuya Takeyama
 
Php update and delet operation
Php update and delet operationPhp update and delet operation
Php update and delet operation
syeda zoya mehdi
 
Dependency Injection
Dependency InjectionDependency Injection
Dependency Injection
Rifat Nabi
 
PHP || [Student Result Management System]
PHP || [Student Result Management System]PHP || [Student Result Management System]
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
Hugo Hamon
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
Jonathan Wage
 
Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.com
Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.comJak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.com
Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.com
WebScience1
 
Command-Oriented Architecture
Command-Oriented ArchitectureCommand-Oriented Architecture
Command-Oriented Architecture
Luiz Messias
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of Lithium
Nate Abele
 
PHP for Adults: Clean Code and Object Calisthenics
PHP for Adults: Clean Code and Object CalisthenicsPHP for Adults: Clean Code and Object Calisthenics
PHP for Adults: Clean Code and Object Calisthenics
Guilherme Blanco
 
Eden Hackathon Benilde (Mysql & SMTP)
Eden Hackathon Benilde (Mysql & SMTP)Eden Hackathon Benilde (Mysql & SMTP)
Eden Hackathon Benilde (Mysql & SMTP)
Dan Michael Molina
 
Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Building Persona: federated and privacy-sensitive identity for the Web (Open ...Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Francois Marier
 
Oops in php
Oops in phpOops in php
BEAR DI
BEAR DIBEAR DI
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
Samuel ROZE
 
PHPUnit with Mocking and Crawling
PHPUnit with Mocking and CrawlingPHPUnit with Mocking and Crawling
PHPUnit with Mocking and Crawling
Trung x
 

Similar to Solid in practice (20)

50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes
 
laravel tricks in 50minutes
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutes
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your Code
 
PHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにPHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くために
 
Php update and delet operation
Php update and delet operationPhp update and delet operation
Php update and delet operation
 
Dependency Injection
Dependency InjectionDependency Injection
Dependency Injection
 
PHP || [Student Result Management System]
PHP || [Student Result Management System]PHP || [Student Result Management System]
PHP || [Student Result Management System]
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.com
Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.comJak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.com
Jak neopakovat kód, ale nepo**** abstrakci | Jiří Pudil | 15. 2. 2023 – Kiwi.com
 
Command-Oriented Architecture
Command-Oriented ArchitectureCommand-Oriented Architecture
Command-Oriented Architecture
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of Lithium
 
PHP for Adults: Clean Code and Object Calisthenics
PHP for Adults: Clean Code and Object CalisthenicsPHP for Adults: Clean Code and Object Calisthenics
PHP for Adults: Clean Code and Object Calisthenics
 
Eden Hackathon Benilde (Mysql & SMTP)
Eden Hackathon Benilde (Mysql & SMTP)Eden Hackathon Benilde (Mysql & SMTP)
Eden Hackathon Benilde (Mysql & SMTP)
 
Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Building Persona: federated and privacy-sensitive identity for the Web (Open ...Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Building Persona: federated and privacy-sensitive identity for the Web (Open ...
 
Java day9n
Java day9nJava day9n
Java day9n
 
Oops in php
Oops in phpOops in php
Oops in php
 
BEAR DI
BEAR DIBEAR DI
BEAR DI
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
PHPUnit with Mocking and Crawling
PHPUnit with Mocking and CrawlingPHPUnit with Mocking and Crawling
PHPUnit with Mocking and Crawling
 

More from Jessica Mauerhan

Double Loop: TDD & BDD Done Right
Double Loop: TDD & BDD Done RightDouble Loop: TDD & BDD Done Right
Double Loop: TDD & BDD Done Right
Jessica Mauerhan
 
Double Loop: TDD & BDD Done Right!
Double Loop: TDD & BDD Done Right!Double Loop: TDD & BDD Done Right!
Double Loop: TDD & BDD Done Right!
Jessica Mauerhan
 
Double Loop
Double LoopDouble Loop
Double Loop
Jessica Mauerhan
 
Legacy: A Retrospective - Open West 2016
Legacy: A Retrospective - Open West 2016Legacy: A Retrospective - Open West 2016
Legacy: A Retrospective - Open West 2016
Jessica Mauerhan
 
Behat - Beyond the Basics (2016 - SunshinePHP)
Behat - Beyond the Basics (2016 - SunshinePHP)Behat - Beyond the Basics (2016 - SunshinePHP)
Behat - Beyond the Basics (2016 - SunshinePHP)
Jessica Mauerhan
 
Rebuilding our Foundation
Rebuilding our FoundationRebuilding our Foundation
Rebuilding our Foundation
Jessica Mauerhan
 
Behat: Beyond the Basics
Behat: Beyond the BasicsBehat: Beyond the Basics
Behat: Beyond the Basics
Jessica Mauerhan
 

More from Jessica Mauerhan (7)

Double Loop: TDD & BDD Done Right
Double Loop: TDD & BDD Done RightDouble Loop: TDD & BDD Done Right
Double Loop: TDD & BDD Done Right
 
Double Loop: TDD & BDD Done Right!
Double Loop: TDD & BDD Done Right!Double Loop: TDD & BDD Done Right!
Double Loop: TDD & BDD Done Right!
 
Double Loop
Double LoopDouble Loop
Double Loop
 
Legacy: A Retrospective - Open West 2016
Legacy: A Retrospective - Open West 2016Legacy: A Retrospective - Open West 2016
Legacy: A Retrospective - Open West 2016
 
Behat - Beyond the Basics (2016 - SunshinePHP)
Behat - Beyond the Basics (2016 - SunshinePHP)Behat - Beyond the Basics (2016 - SunshinePHP)
Behat - Beyond the Basics (2016 - SunshinePHP)
 
Rebuilding our Foundation
Rebuilding our FoundationRebuilding our Foundation
Rebuilding our Foundation
 
Behat: Beyond the Basics
Behat: Beyond the BasicsBehat: Beyond the Basics
Behat: Beyond the Basics
 

Recently uploaded

Essentials of Automations: Optimizing FME Workflows with Parameters
Essentials of Automations: Optimizing FME Workflows with ParametersEssentials of Automations: Optimizing FME Workflows with Parameters
Essentials of Automations: Optimizing FME Workflows with Parameters
Safe Software
 
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
Product School
 
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
James Anderson
 
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Tobias Schneck
 
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
UiPathCommunity
 
Designing Great Products: The Power of Design and Leadership by Chief Designe...
Designing Great Products: The Power of Design and Leadership by Chief Designe...Designing Great Products: The Power of Design and Leadership by Chief Designe...
Designing Great Products: The Power of Design and Leadership by Chief Designe...
Product School
 
Epistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI supportEpistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI support
Alan Dix
 
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered QualitySoftware Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Inflectra
 
FIDO Alliance Osaka Seminar: Overview.pdf
FIDO Alliance Osaka Seminar: Overview.pdfFIDO Alliance Osaka Seminar: Overview.pdf
FIDO Alliance Osaka Seminar: Overview.pdf
FIDO Alliance
 
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
Product School
 
Neuro-symbolic is not enough, we need neuro-*semantic*
Neuro-symbolic is not enough, we need neuro-*semantic*Neuro-symbolic is not enough, we need neuro-*semantic*
Neuro-symbolic is not enough, we need neuro-*semantic*
Frank van Harmelen
 
Bits & Pixels using AI for Good.........
Bits & Pixels using AI for Good.........Bits & Pixels using AI for Good.........
Bits & Pixels using AI for Good.........
Alison B. Lowndes
 
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Thierry Lestable
 
Elevating Tactical DDD Patterns Through Object Calisthenics
Elevating Tactical DDD Patterns Through Object CalisthenicsElevating Tactical DDD Patterns Through Object Calisthenics
Elevating Tactical DDD Patterns Through Object Calisthenics
Dorra BARTAGUIZ
 
When stars align: studies in data quality, knowledge graphs, and machine lear...
When stars align: studies in data quality, knowledge graphs, and machine lear...When stars align: studies in data quality, knowledge graphs, and machine lear...
When stars align: studies in data quality, knowledge graphs, and machine lear...
Elena Simperl
 
The Art of the Pitch: WordPress Relationships and Sales
The Art of the Pitch: WordPress Relationships and SalesThe Art of the Pitch: WordPress Relationships and Sales
The Art of the Pitch: WordPress Relationships and Sales
Laura Byrne
 
Knowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and backKnowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and back
Elena Simperl
 
Encryption in Microsoft 365 - ExpertsLive Netherlands 2024
Encryption in Microsoft 365 - ExpertsLive Netherlands 2024Encryption in Microsoft 365 - ExpertsLive Netherlands 2024
Encryption in Microsoft 365 - ExpertsLive Netherlands 2024
Albert Hoitingh
 
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdfFIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance
 
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdfFIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance
 

Recently uploaded (20)

Essentials of Automations: Optimizing FME Workflows with Parameters
Essentials of Automations: Optimizing FME Workflows with ParametersEssentials of Automations: Optimizing FME Workflows with Parameters
Essentials of Automations: Optimizing FME Workflows with Parameters
 
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
 
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
 
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
 
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
 
Designing Great Products: The Power of Design and Leadership by Chief Designe...
Designing Great Products: The Power of Design and Leadership by Chief Designe...Designing Great Products: The Power of Design and Leadership by Chief Designe...
Designing Great Products: The Power of Design and Leadership by Chief Designe...
 
Epistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI supportEpistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI support
 
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered QualitySoftware Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
 
FIDO Alliance Osaka Seminar: Overview.pdf
FIDO Alliance Osaka Seminar: Overview.pdfFIDO Alliance Osaka Seminar: Overview.pdf
FIDO Alliance Osaka Seminar: Overview.pdf
 
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
 
Neuro-symbolic is not enough, we need neuro-*semantic*
Neuro-symbolic is not enough, we need neuro-*semantic*Neuro-symbolic is not enough, we need neuro-*semantic*
Neuro-symbolic is not enough, we need neuro-*semantic*
 
Bits & Pixels using AI for Good.........
Bits & Pixels using AI for Good.........Bits & Pixels using AI for Good.........
Bits & Pixels using AI for Good.........
 
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
 
Elevating Tactical DDD Patterns Through Object Calisthenics
Elevating Tactical DDD Patterns Through Object CalisthenicsElevating Tactical DDD Patterns Through Object Calisthenics
Elevating Tactical DDD Patterns Through Object Calisthenics
 
When stars align: studies in data quality, knowledge graphs, and machine lear...
When stars align: studies in data quality, knowledge graphs, and machine lear...When stars align: studies in data quality, knowledge graphs, and machine lear...
When stars align: studies in data quality, knowledge graphs, and machine lear...
 
The Art of the Pitch: WordPress Relationships and Sales
The Art of the Pitch: WordPress Relationships and SalesThe Art of the Pitch: WordPress Relationships and Sales
The Art of the Pitch: WordPress Relationships and Sales
 
Knowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and backKnowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and back
 
Encryption in Microsoft 365 - ExpertsLive Netherlands 2024
Encryption in Microsoft 365 - ExpertsLive Netherlands 2024Encryption in Microsoft 365 - ExpertsLive Netherlands 2024
Encryption in Microsoft 365 - ExpertsLive Netherlands 2024
 
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdfFIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
 
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdfFIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
 

Solid in practice

  • 1. SOLID In Practice @jessicamauerhan | https://joind.in/talk/34870 | @PHPDet
  • 3. @jessicamauerhan | https://joind.in/talk/34870 | @PHPDet 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/34870 | @PHPDet 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; } }
  • 6.
  • 7. @jessicamauerhan | https://joind.in/talk/34870 | @PHPDet 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/34870 | @PHPDet User Registration ● Collect User Input ● Validate User Input ● Persist User Data ● Send Confirmation Email
  • 10. 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. 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'); } 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. 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. 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); } private function emailIsRegistered(string $email): bool { [...] } 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}')"); } }
  • 14. 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; } 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}')"); } }
  • 15. Class UserRegistration { public function register(Request $request): Response { [...] } private function emailIsRegistered(string $email): bool { $rows = DB::query("SELECT count(id) FROM users WHERE (email='{$email}')"); return $rows > 0; } private function saveUser(User $user) { DB::query("INSERT INTO users(email, password) VALUES('{$user->email}', '{$user->password}')"); } }
  • 16. 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); }
  • 17. 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; } }
  • 18. 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; } }
  • 19. @jessicamauerhan | https://joind.in/talk/34870 | @PHPDet 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
  • 20. Class UserRegistration { public function register(Request $request): Response { [...] } private function sendConfirmationEmail(string $email) { [...] } private function saveUser(User $user) { $sql = "INSERT INTO users(email, password) VALUES('{$user->email}', '{$user->password}')" DB::query($sql); } private function emailIsRegistered(string $email): bool { $rows = DB::query("SELECT count(id) FROM users WHERE (email='{$email}')"); return $rows > 0; } }
  • 21. Class UserRegistration { private $db; public function __construct(Database $db) { $this->db = $db; } public function register(Request $request): Response { [...] } private function sendConfirmationEmail(string $email) { [...] } 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; } }
  • 22. 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); } private function saveUser(User $user) { [...] } private function emailIsRegistered(string $email): bool { [...] }
  • 25. 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()); } } } interface UserFactory { /** * @throws UnableToParseUser */ public function createRegisteringUser(Request $request): User; }
  • 26. interface UserFactory { /** * @throws UnableToParseUser */ public function createRegisteringUser(Request $request): User; } interface UserValidator { public function isValid(User $user): bool; public function getInvalidProperties(User $user):array; } 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); } } }
  • 27. 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); } } } 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 UserFactory { /** * @throws UnableToParseUser */ public function createRegisteringUser(Request $request): User; } interface UserValidator { public function isValid(User $user): bool; public function getInvalidProperties(User $user):array; }
  • 28. 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); } 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 UserFactory { /** * @throws UnableToParseUser */ public function createRegisteringUser(Request $request): User; } interface UserValidator { public function isValid(User $user): bool; public function getInvalidProperties(User $user):array; }
  • 29. 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); }
  • 30. interface UserFactory { /** * @throws UnableToParseUser */ public function createUser(Request $request): User; }
  • 31. 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); } }
  • 32. interface UserValidator { public function isValid(User $user): bool; public function getInvalidProperties(User $user):array; }
  • 33. 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; } }
  • 34. 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; }
  • 35. 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(); } } }
  • 36. interface ConfirmationEmailer { /** * @throws MailerException */ public function send(string $to); }
  • 37. 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); } }
  • 38. 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); } }
  • 39. 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); } }
  • 40. interface ConfirmationEmailer { public function send(string $to); /** @throws MailerException */ }
  • 41. interface ConfirmationEmailer { public function send(string $to); /** @throws MailerException */ } interface Mailer { public function send(Email $email); }
  • 42. 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; } }
  • 43. 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); } }
  • 44. 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); } }
  • 45. 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); } }
  • 46. 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); } }
  • 47. 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); } } } }
  • 49. 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); } }
  • 51. Class PercentageDiscount { private $discount; public function __construct(int $discount) { $this->discount = $discount; } public function calculateAmountOff(Item $item) { return ($this->discount / 100) * $item->getCost(); } }
  • 52. 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; } }
  • 53. 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; } }
  • 55. 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; } }
  • 56. 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 __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; }
  • 57. Interface Discount { /** @throws DiscountException */ public function calculateAmountOff(Item $item): float; } Class DiscountException extends Exception { } 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 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(); } throw new DiscountException (); } 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; }
  • 59. Interface Item { public function getName(): string; public function getPrice(): float; public function getShippingWeight(): float; public function getShippingCost(): float; }
  • 60. 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; } }
  • 61. 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 Cart { [...] } 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; } }
  • 63. 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; } 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; }
  • 64. 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 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; } }
  • 65. 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 BuyableItem implements Buyable { [...] } 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; } }
  • 66. 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
  • 67. @jessicamauerhan | https://joind.in/talk/34870 | @PHPDet SLD: Apply Often & Preemptively ● Single Responsibility ● Liskov Substitution ● Dependency Inversion OI: Apply As Needed When Changes Occur ● Open/Closed ● Interface Segregation
  • 68. @jessicamauerhan | https://joind.in/talk/34870 | @PHPDet Thank You! SOLID in Practice https://joind.in/talk/34870 Feedback & Questions? Welcome & Encouraged! @jessicamauerhan jessicamauerhan@gmail.com jmauerhan.wordpress.com