SlideShare une entreprise Scribd logo
1  sur  68
Télécharger pour lire hors ligne
SOLID In Practice
@jessicamauerhan | http://bit.ly/solid-oop
@jessicamauerhan
Software Engineer
@HelpScout
jessicamauerhan@gmail.com
jmauerhan.wordpress.com
@jessicamauerhan | http://bit.ly/solid-oop
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 | http://bit.ly/solid-oop
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;
}
}
http://bit.ly/solid-oop
@jessicamauerhan | http://bit.ly/solid-oop
S.O.L.I.D.
● Single Responsibility
● Open/Closed
● Liskov Substitution
● Interface Segregation
● Dependency Inversion
It's All About Change
@jessicamauerhan | http://bit.ly/solid-oop
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 aclick 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 aclick 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 aclick 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 | http://bit.ly/solid-oop
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 aclick 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 aclick 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 aclick 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 | http://bit.ly/solid-oop
SLD: Apply Often
 Preemptively
● Single Responsibility
● Liskov Substitution
● Dependency Inversion
OI: Apply As Needed
When Changes Occur
● Open/Closed
● Interface Segregation
@jessicamauerhan | http://bit.ly/solid-oop
Thank You!
SOLID in Practice
http://bit.ly/solid-oop
Feedback  Questions?
Welcome  Encouraged!
@jessicamauerhan
jessicamauerhan@gmail.com
jmauerhan.wordpress.com

Contenu connexe

Tendances

Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteLeonardo Proietti
 
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.5Leonardo Proietti
 
The Art of Transduction
The Art of TransductionThe Art of Transduction
The Art of TransductionDavid Stockton
 
$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$
$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$
$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$UltraUploader
 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 MinutesAzim Kurt
 
Doctrator Symfony Live 2011 San Francisco
Doctrator Symfony Live 2011 San FranciscoDoctrator Symfony Live 2011 San Francisco
Doctrator Symfony Live 2011 San Franciscopablodip
 
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 needKacper Gunia
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsSam Hennessy
 
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...DevClub_lv
 
CoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love AffairCoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love AffairMark
 
究極のコントローラを目指す
究極のコントローラを目指す究極のコントローラを目指す
究極のコントローラを目指すYasuo Harada
 
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
 
MooseX::Datamodel - Barcelona Perl Workshop Lightning talk
MooseX::Datamodel - Barcelona Perl Workshop Lightning talkMooseX::Datamodel - Barcelona Perl Workshop Lightning talk
MooseX::Datamodel - Barcelona Perl Workshop Lightning talkJose Luis Martínez
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosDivante
 

Tendances (20)

Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
 
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
 
The Art of Transduction
The Art of TransductionThe Art of Transduction
The Art of Transduction
 
Values
ValuesValues
Values
 
$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$
$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$
$$$ +$$+$$ _+_$+$$_$+$$$_+$$_$
 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes
 
Drupal 8 database api
Drupal 8 database apiDrupal 8 database api
Drupal 8 database api
 
Doctrator Symfony Live 2011 San Francisco
Doctrator Symfony Live 2011 San FranciscoDoctrator Symfony Live 2011 San Francisco
Doctrator Symfony Live 2011 San Francisco
 
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
 
Presentation1
Presentation1Presentation1
Presentation1
 
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
 
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...
 
Separation of concerns - DPC12
Separation of concerns - DPC12Separation of concerns - DPC12
Separation of concerns - DPC12
 
BEAR DI
BEAR DIBEAR DI
BEAR DI
 
CoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love AffairCoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love Affair
 
究極のコントローラを目指す
究極のコントローラを目指す究極のコントローラを目指す
究極のコントローラを目指す
 
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)
 
MooseX::Datamodel - Barcelona Perl Workshop Lightning talk
MooseX::Datamodel - Barcelona Perl Workshop Lightning talkMooseX::Datamodel - Barcelona Perl Workshop Lightning talk
MooseX::Datamodel - Barcelona Perl Workshop Lightning talk
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
 

Similaire à SOLID In Practice: User Registration Example

PHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにPHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにYuya Takeyama
 
laravel tricks in 50minutes
laravel tricks in 50minuteslaravel tricks in 50minutes
laravel tricks in 50minutesBarang CK
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your CodeAbbas Ali
 
Dependency Injection
Dependency InjectionDependency Injection
Dependency InjectionRifat Nabi
 
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.comWebScience1
 
Eden Hackathon Benilde (Mysql & SMTP)
Eden Hackathon Benilde (Mysql & SMTP)Eden Hackathon Benilde (Mysql & SMTP)
Eden Hackathon Benilde (Mysql & SMTP)Dan Michael Molina
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For BeginnersJonathan Wage
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & RESTHugo Hamon
 
Php update and delet operation
Php update and delet operationPhp update and delet operation
Php update and delet operationsyeda zoya mehdi
 
Command-Oriented Architecture
Command-Oriented ArchitectureCommand-Oriented Architecture
Command-Oriented ArchitectureLuiz Messias
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of LithiumNate Abele
 
PHPUnit with Mocking and Crawling
PHPUnit with Mocking and CrawlingPHPUnit with Mocking and Crawling
PHPUnit with Mocking and CrawlingTrung x
 
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 patternsSamuel ROZE
 
Drupal 8: Forms
Drupal 8: FormsDrupal 8: Forms
Drupal 8: Formsdrubb
 
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 CalisthenicsGuilherme Blanco
 
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
 
Dependency injection-zendcon-2010
Dependency injection-zendcon-2010Dependency injection-zendcon-2010
Dependency injection-zendcon-2010Fabien Potencier
 
Dependency injection in PHP 5.3/5.4
Dependency injection in PHP 5.3/5.4Dependency injection in PHP 5.3/5.4
Dependency injection in PHP 5.3/5.4Fabien Potencier
 

Similaire à SOLID In Practice: User Registration Example (20)

PHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにPHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くために
 
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
 
Dependency Injection
Dependency InjectionDependency Injection
Dependency Injection
 
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
 
Eden Hackathon Benilde (Mysql & SMTP)
Eden Hackathon Benilde (Mysql & SMTP)Eden Hackathon Benilde (Mysql & SMTP)
Eden Hackathon Benilde (Mysql & SMTP)
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
 
Php update and delet operation
Php update and delet operationPhp update and delet operation
Php update and delet operation
 
Command-Oriented Architecture
Command-Oriented ArchitectureCommand-Oriented Architecture
Command-Oriented Architecture
 
PHP || [Student Result Management System]
PHP || [Student Result Management System]PHP || [Student Result Management System]
PHP || [Student Result Management System]
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of Lithium
 
PHPUnit with Mocking and Crawling
PHPUnit with Mocking and CrawlingPHPUnit with Mocking and Crawling
PHPUnit with Mocking and Crawling
 
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
 
Oops in php
Oops in phpOops in php
Oops in php
 
Drupal 8: Forms
Drupal 8: FormsDrupal 8: Forms
Drupal 8: Forms
 
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
 
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 ...
 
Dependency injection-zendcon-2010
Dependency injection-zendcon-2010Dependency injection-zendcon-2010
Dependency injection-zendcon-2010
 
Dependency injection in PHP 5.3/5.4
Dependency injection in PHP 5.3/5.4Dependency injection in PHP 5.3/5.4
Dependency injection in PHP 5.3/5.4
 

Plus de Jessica Mauerhan

Double Loop: TDD & BDD Done Right
Double Loop: TDD & BDD Done RightDouble Loop: TDD & BDD Done Right
Double Loop: TDD & BDD Done RightJessica 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
 
Legacy: A Retrospective - Open West 2016
Legacy: A Retrospective - Open West 2016Legacy: A Retrospective - Open West 2016
Legacy: A Retrospective - Open West 2016Jessica 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
 

Plus de 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
 

Dernier

Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxUse of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxLoriGlavin3
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfLoriGlavin3
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
unit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxunit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxBkGupta21
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxLoriGlavin3
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
What is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfWhat is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfMounikaPolabathina
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningLars Bell
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxLoriGlavin3
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubKalema Edgar
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 

Dernier (20)

Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxUse of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdf
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
unit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptxunit 4 immunoblotting technique complete.pptx
unit 4 immunoblotting technique complete.pptx
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
DMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special EditionDMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special Edition
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
What is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfWhat is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdf
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine Tuning
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding Club
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 

SOLID In Practice: User Registration Example

  • 1. SOLID In Practice @jessicamauerhan | http://bit.ly/solid-oop
  • 3. @jessicamauerhan | http://bit.ly/solid-oop 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 | http://bit.ly/solid-oop Class Rectangle { protected $length; protected $height; public function area() { return $this-length * $this-area(); } public function setLength($length) { $this-length = $length; } public function setHeight($height) { $this-height = $height; } } Class Square extends Rectangle { public function setLength($length) { $this-length = $length; $this-height = $length; } public function setHeight($height) { $this-length = $height; $this-height = $height; } }
  • 7. @jessicamauerhan | http://bit.ly/solid-oop S.O.L.I.D. ● Single Responsibility ● Open/Closed ● Liskov Substitution ● Interface Segregation ● Dependency Inversion
  • 8. It's All About Change
  • 9. @jessicamauerhan | http://bit.ly/solid-oop 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 aclick 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 aclick 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 aclick 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 | http://bit.ly/solid-oop 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 aclick 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 aclick 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 aclick 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 | http://bit.ly/solid-oop SLD: Apply Often Preemptively ● Single Responsibility ● Liskov Substitution ● Dependency Inversion OI: Apply As Needed When Changes Occur ● Open/Closed ● Interface Segregation
  • 68. @jessicamauerhan | http://bit.ly/solid-oop Thank You! SOLID in Practice http://bit.ly/solid-oop Feedback Questions? Welcome Encouraged! @jessicamauerhan jessicamauerhan@gmail.com jmauerhan.wordpress.com