SOLID PRINCIPLES
LUCIANO QUEIROZ
DECISIONS
SOLID
PRINCIPLES
Robert Martin aka Uncle Bob
CHANGES
SINGLE RESPONSIBILITY PRINCIPLE
“A CLASS SHOULD HAVE ONE, 

AND ONLY ONE, REASON TO
CHANGE.”
SOLID PRINCIPLES
<?php
class ConfirmationMailMailer
{
private $templating;
private $mailer;
public function __construct(
ITemplatingEngine $templating,
IMailer $mailer
) {
$this->templating = $templating;
$this->mailer = $mailer;
}
public function sendTo(User $user)
{
$message = $this->createMessageFor($user);
$this->sendMessage($message);
}
private function createMessageFor(User $user)
{
$subject = 'Confirm your email address';
$body = $this->templating
->render(
'confirmationMail.html.tpl', [
'confirmationCode' => $user->getConfirmationCode()
]
);
$message = new Message($subject, $body);
$message->setTo($user->getEmailAdress());
return $message;
}
private function sendMessage(IMessage $message)
{
$this->mailer->send($message);
}
SOLID PRINCIPLES
INITIAL SITUATION
ConfirmationMailMailer.php
IMailer.php
usesuses
ITemplatingEngine.php
SOLID PRINCIPLES
RESPONSIBILITIES
▸ Create Confirmation Message
▸ Send Confirmation Message to user
SINGLE RESPONSIBILITY PRINCIPLE
“RESPONSIBILITIES ARE
REASONS TO CHANGE”
SOLID PRINCIPLES
SOLID PRINCIPLES
SINGLE RESPONSIBILITY PRINCIPLE VIOLATIONS
▸ Many instance variables
▸ Many public methods
▸ Each method use variables from other instance
▸ Specific tasks are done by private methods
COLLABORATOR
CLASSES
<?php
class ConfirmationMailFactory
{
private $templating;
public function __construct(ITemplatingEngine $templating)
{
$this->templating = $templating;
}
public function createMessageFor(User $user)
{
$subject = 'Confirm your email address';
$body = $this->templating
->render(
'confirmationMail.html.tpl', [
'confirmationCode' => $user->getConfirmationCode()
]
);
$message = new Message($subject, $body);
$message->setTo($user->getEmailAdress());
return $message;
}
}
<?php
class ConfirmationMailMailer
{
private $confirmationMailFactory;
private $mailer;
public function __construct(
ConfirmationMailFactory $confirmationMailFactory,
IMailer $mailer
) {
$this->confirmationMailFactory = $confirmationMailFactory;
$this->mailer = $mailer;
}
public function sendTo(User $user)
{
$message = $this->confirmationMailFactory->createMessageFor($user);
$this->sendMessage($message);
}
private function sendMessage(IMessage $message)
{
$this->mailer->send($message);
}
}
SOLID PRINCIPLES
REFACTORED
ConfirmationMailMailer.php IMailer.phpuses
ConfirmationMailFactory.php
uses
ITemplatingEngine.phpuses
SOLID PRINCIPLES
SINGLE RESPONSIBILITY PRINCIPLE ADVANTAGES
▸ Smaller classes
▸ Classes easier to understand
▸ Classes easier to test
▸ Classes simpler to maintain
OPEN/CLOSED PRINCIPLE
“YOU SHOULD BE ABLE TO
EXTEND A CLASS`S BEHAVIOR,
WITHOUT MODIFYING IT.”
SOLID PRINCIPLES
<?php
class GenericEncoder
{
public function encodeToFormat($data, $format)
{
if ($format === 'json') {
$encoder = new JsonEncoder();
} elseif ($format === 'xml') {
$encoder = new XmlEncoder();
} else {
throw new InvalidArgumentException('Unknown format');
}
$data = $this->prepareData($data, $format);
return $encoder->encode($data);
}
private function prepareData($data, $format)
{
switch ($format) {
case 'json':
$data = $this->forceArrayKeys($data);
$data = $this->fixKeys($data);
break;
case 'xml':
$data = $this->fixAttributes($data);
break;
default:
throw new InvalidArgumentException(
'Format not supported'
);
}
return $data;
}
}
SOLID PRINCIPLES
INITIAL SITUATION
GenericEncoder.php
XmlEncoder.php
uses
uses
JsonEncoder.php
<?php
class GenericEncoder
{
public function encodeToFormat($data, $format)
{
if (…) {
…
} elseif (…) {
…
} elseif ($format === 'yaml') {
$encoder = new YamlEncoder();
} else {
throw new InvalidArgumentException('Unknown format');
}
}
…
}
SOLID PRINCIPLES
NEW REQUIREMENT SITUATION
GenericEncoder.php
XmlEncoder.php
uses
uses
JsonEncoder.php
YamlEncoder.php
uses
OPEN/CLOSED PRINCIPLE
“A UNIT OF CODE CAN BE
CONSIDERED ‘OPEN’ FOR EXTENSION
WHEN ITS BEHAVIOR CAN BE EASILY
CHANGED WITHOUT MODIFYING IT”
SOLID PRINCIPLES
SOLID PRINCIPLES
OPEN/CLOSED PRINCIPLE VIOLATIONS
▸ Conditions to determine a strategy
▸ Conditions using the same variables or constants are
recurring inside the class or related classes
▸ Contains hard-coded references to other classes or class
names
▸ Objects are being created using the new operator
ABSTRACT
FACTORY
<?php
interface EncoderInterface
{
public function encode($data);
}
class JsonEncoder implements EncoderInterface
{
public function encode($data)
{
// TODO: Implement encode() method.
}
}
class XmlEncoder implements EncoderInterface
{
public function encode($data)
{
// TODO: Implement encode() method.
}
}
class YamlEncoder implements EncoderInterface
{
public function encode($data)
{
// TODO: Implement encode() method.
}
}
SOLID PRINCIPLES
INTRODUCING ENCODER INTERFACE
GenericEncoder.php
XmlEncoder.php
uses
JsonEncoder.phpYamlEncoder.php
implements
EncoderInterface.php
implements
implements
<?php
class EncoderFactory
{
public function createForFormat($format)
{
if ($format === 'json') {
return new JsonEncoder();
} elseif ($format === 'xml') {
return new XmlEncoder();
} elseif ($format === 'yaml') {
return new YamlEncoder();
}
throw new InvalidArgumentException('Unknown format');
}
}
<?php
class GenericEncoder
{
private $encoderFactory;
public function __construct(EncoderFactory $encoderFactory)
{
$this->encoderFactory = $encoderFactory;
}
public function encodeToFormat($data, $format)
{
$encoder = $this->encoderFactory->createForFormat($format);
$data = $this->prepareData($data, $format);
return $encoder->encode($data);
}
}
ABSTRACT FACTORY
OPEN FOR EXTENSION
<?php
interface EncoderFactoryInterface
{
/**
* Create an encoder for the given format
*
* @param string $format
* @return EncoderInterface
*/
public function createForFormat($format)
}
class EncoderFactory implements EncoderFactoryInterface
{
...
}
class GenericEncoder
{
public function __construct(
EncoderFactoryInterface $encoderFactory
) {
...
}
...
}
INTRODUCING DEPENDENCY INVERSION PRINCIPLE
SOLID PRINCIPLES
GenericEncoder.php
XmlEncoder.php
uses
JsonEncoder.phpYamlEncoder.php
implements
EncoderInterface.php
implements
implements
EncoderFactoryInterface.php
uses
creates
<?php
class EncoderFactory implements EncoderFactoryInterface
{
private $factories = [];
/**
* Register a callable that returns an instance of
* EncoderInterface for the given format.
*
* @param string $format
* @param callable $factory
*/
public function addEncoderFactory($format, callable $factory)
{
$this->factories[$format] = $factory;
}
public function createForFormat($format)
{
$factory = $this->factories[$format];
$encoder = $factory;
return $encoder;
}
}
<?php
$encoderFactory = new EncoderFactory();
$encoderFactory->addEncoderFactory(
'xml',
function () {
return new XmlEncoder();
}
);
$encoderFactory->addEncoderFactory(
'json',
function () {
return new JsonEncoder();
}
);
$genericEncoder = new GenericEncoder($encoderFactory);
$data = ...
$jsonEncodedData = $genericEncoder->encode($data, 'json');
<?php
class GenericEncoder
{
private function prepareData($data, $format)
{
switch ($format) {
case 'json':
$data = $this->forceArray($data);
$data = $this->fixKeys($data);
break;
case 'xml':
$data = $this->fixAttributes($data);
break
default:
# code...
break;
}
return $data;
}
}
PORLYMORPHISM
<?php
class GenericEncoder
{
private $encoderFactory;
public function __construct(
EncoderFactoryInterface $encoderFactory
) {
$this->encoderFactory;
}
public function encodeToFormat($data, $format)
{
$encoder = $this->encoderFactory->createForFormat($format);
$data = $encoder->prepareData($data);
return $encoder->encode($data);
}
}
<?php
class JsonEncoder implements EncoderInterface
{
public function encode($data)
{
...
}
public function prepareData($data)
{
$data = $this->forceArray($data);
$data = $this->fixKeys($data);
return $data;
}
}
<?php
class GenericEncoder
{
private $encoderFactory;
public function __construct(
EncoderFactoryInterface $encoderFactory
) {
$this->encoderFactory;
}
public function encodeToFormat($data, $format)
{
$encoder = $this->encoderFactory->createForFormat($format);
$data = $encoder->prepareData($data);
return $encoder->encode($data);
}
}
<?php
class JsonEncoder implements EncoderInterface
{
public function encode($data)
{
$data = $this->prepareData($data);
return json_encode($data);
}
private function prepareData($data)
{
$data = $this->forceArray($data);
$data = $this->fixKeys($data);
return $data;
}
}
<?php
interface EncoderInterface
{
public function encode($data);
}
<?php
class GenericEncoder
{
public function encodeToFormat($data, $format)
{
if ($format === 'json') {
$encoder = new JsonEncoder();
} elseif ($format === 'xml') {
$encoder = new XmlEncoder();
} else {
throw new InvalidArgumentException('Unknown format');
}
$data = $this->prepareData($data, $format);
return $encoder->encode($data);
}
private function prepareData($data, $format)
{
switch ($format) {
case 'json':
$data = $this->forceArrayKeys($data);
$data = $this->fixKeys($data);
break;
case 'xml':
$data = $this->fixAttributes($data);
break;
default:
throw new InvalidArgumentException(
'Format not supported'
);
}
return $data;
}
}
<?php
class GenericEncoder
{
private $encoderFactory;
public function __construct(
EncoderFactoryInterface $encoderFactory
) {
$this->encoderFactory;
}
public function encodeToFormat($data, $format)
{
$encoder = $this->encoderFactory->createForFormat($format);
return $encoder->encode($data);
}
}
LISKOV SUBSTITUTION PRINCIPLE
“DERIVED CLASSES MUST
BE SUBSTITUTABLE FOR
THEIR BASE CLASSES”
SOLID PRINCIPLES
SOLID PRINCIPLES
WE NEED TO UNDERSTAND
▸ Derived classes
▸ Being substitutable
DERIVED
CLASSES
<?php
/**
* A concrete class, all methods are implemented, but can be
* overridden by derived classes
*/
class ConcreteClass
{
public function implementedMethod()
{
}
}
/**
* An abstract class, some methods need to be implemented by
* by derived classes
*/
abstract class AbstractClass
{
abstract public function abstractMethod();
public function implementedMethod()
{
}
}
/**
* An interface, all methods need to be implemented by derived
* classes
*/
interface AnInterface
{
public function abstractMethod();
}
BEING
SUBSTITUTABLE
VIOLATIONS
LISKOV SUBSTITUTION PRINCIPLE
“A DERIVED CLASS DOES NOT
HAVE AN IMPLEMENTATION
FOR ALL METHODS”
SOLID PRINCIPLES
<?php
interface FileInterface
{
public function rename($name);
public function changeOwner($user, $group);
}
<?php
class DropboxFile implements FileInterface
{
public function rename($name)
{
...
}
public function changeOwner($user, $group)
{
throw new BadMethodCallException(
"Not implemented for Dropbox files"
);
}
}
<?php
class DropboxFile implements FileInterface
{
public function rename($name)
{
...
}
public function changeOwner($user, $group)
{
throw new BadMethodCallException(
"Not implemented for Dropbox files"
);
}
}
//ask the user to check if $file is an instance of DropboxFile
if (!($file instanceof DropboxFile)) {
$file->changeOwner(...);
}
<?php
class DropboxFile implements FileInterface
{
...
public function changeOwner($user, $group)
{
//let's do nothing :)
}
}
INTERFACE SEGREGATION PRINCIPLE
“MAKE FINE-GRAINED
INTERFACES THAT ARE
CLIENT SPECIFIC”
SOLID PRINCIPLES
<?php
interface FileInterface
{
public function rename($name);
}
interface FileWithOwnerInterface extends FileInterface
{
public function changeOwner($user, $group);
}
<?php
class DropboxFile implements FileInterface
{
public function rename($name)
{
...
}
}
class LocalFile implements FileWithOwnerInterface
{
public function rename($name)
{
...
}
public function changeOwner($user, $group)
{
...
}
}
SOLID PRINCIPLES
NEW HIERARCHY OF FILE CLASSES
DropboxFile.php
extends
LocalFile.php
implements
FileInterface.php
implements
FileWithOwnerInterface.php
LISKOV SUBSTITUTION PRINCIPLE
“DIFFERENT SUBSTITUTES
RETURN THINGS OF
DIFFERENT TYPES”
SOLID PRINCIPLES
<?php
interface RouterInterface
{
public function getRoutes() : RouterCollection;
}
class SimpleRouter implements RouterInterface
{
public function getRoutes()
{
$routes = [];
//add Route objects to $routes
$routes[] = ...;
return $routes
}
}
class AdvancedRouter implements RouterInterface
{
public function getRoutes()
{
$routeCollection = new RouteCollection();
...
return $routeCollection;
}
}
<?php
interface RouterInterface
{
public function getRoutes() : RouterCollection;
}
class SimpleRouter implements RouterInterface
{
public function getRoutes() : RouteCollection
{
$routes = [];
//add Route objects to $routes2
$routes[] = ...;
return $routes
}
}
class AdvancedRouter implements RouterInterface
{
public function getRoutes() : RouteCollection
{
$routeCollection = new RouteCollection();
...
return $routeCollection;
}
}
LISKOV SUBSTITUTION PRINCIPLE
“A DERIVED CLASS IS LESS
PERMISSIVE WITH REGARD
TO METHOD ARGUMENTS”
SOLID PRINCIPLES
<?php
interface MassMailerInterface
{
public function sendMail(
TransportInterface $transport,
MessageInterface $message,
RecipientsInterface $recipients
);
}
<?php
class SmtpMassMailer implements MassMailerInterface
{
public function sendMail(
TransportInterface $transport,
MessageInterface $message,
RecipientsInterface $recipients
) {
if (!($transport instanceof SmtpTransport)) {
throw new InvalidArgumentException(
'SmtpMassMailer only works with SMTP'
);
}
}
}
LISKOV SUBSTITUTION PRINCIPLE
“NOT EVERY KIND OF MAIL
TRANSPORT IS SUITABLE
FOR MASS MAILING”
SOLID PRINCIPLES
SOLID PRINCIPLES
NEW CLASS DIAGRAM
SmtpTransport.php
TransportInterface.php
extends
TransportWithMassMailSupportInterface.php
implements
<?php
interface MassMailerInterface
{
public function sendMail(
TransportWithMassMailSupportInterface $transport,
MessageInterface $message,
RecipientsInterface $recipients
);
}
<?php
class SmtpMassMailer implements MassMailerInterface
{
public function sendMail(
TransportWithMassMailSupportInterface $transport,
MessageInterface $message,
RecipientsInterface $recipients
) {
…
}
}
SOLID PRINCIPLES
A GOOD SUBSTITUTE
▸ Provides an implementation for all the methods of the
base class.
▸ Returns the type of things the base class prescribes.
▸ Doesn’t put extra constraints on arguments for methods.
INTERFACE SEGREGATION PRINCIPLE
“MAKE FINE-GRAINED
INTERFACES THAT ARE
CLIENT SPECIFIC”
SOLID PRINCIPLES
DEPENDENCY INVERSION PRINCIPLE
“DEPEND ON ABSTRACTIONS,
NOT CONCRETIONS”
SOLID PRINCIPLES
SOLID PRINCIPLES
FIZZ BUZZ ASSIGNMENT
▸ Generate a list of integers, from 1 to n.
▸ Numbers that are divisible by 3 should be replace with
“Fizz”
▸ Numbers that are divisible by 5 should be replaced with
“Buzz”
▸ Numbers that are both divisible by 3 and by 5 should be
replaced with “FizzBuzz”
<?php
class FizzBuzz
{
public static function generateList($limit)
{
$list = [];
for ($number = 1; $number <= $limit; $number++) {
$list[] = self::generateElement($number);
}
return $list;
}
private static function generateElement($number)
{
if ($number % 3 === 0 && $number % 5 === 0) {
return 'FizzBuzz';
}
if ($number % 3 === 0) {
return 'Fizz';
}
if ($number % 5 === 0) {
return 'Buzz';
}
return $number;
}
}
SOLID PRINCIPLES
THEN…
▸ It should be possible to add another rule, without
modifying the FizzBuzz class.
MAKING FIZZBUZZ
OPEN FOR EXTENSION
<?php
class FizzBuzz
{
public static function generateList($limit)
{
...
}
private static function generateElement($number)
{
$fizzBuzzRule = new FizzBuzzRule();
if ($fizzBuzzRule->matches($number)) {
return $fizzBuzzRule->getReplacement();
}
$fizzRule = new FizzRule();
if ($fizzRule->matches($number)) {
return $fizzRule->getReplacement();
}
$buzzRule = new BuzzRule();
if ($buzzRule->matches($number)) {
return $buzzRule->getReplacement();
}
return $number;
}
}
<?php
interface RuleInterface
{
public function matches($number);
public function getReplacement();
}
<?php
class FizzBuzz
{
private $rules = [];
public function addRule(RuleInterface $rule) {
$this->rules[] = $rule;
}
public static function generateList($limit)
{
...
}
private static function generateElement($number)
{
foreach ($this->rules as $rule) {
if ($rule->matches($number)) {
return $rule->getReplacement();
}
}
return $number;
}
}
<?php
class FizzBuzzRule implements RuleInterface
{
...
}
class FizzRule implements RuleInterface
{
...
}
class BuzzRule implements RuleInterface
{
...
}
<?php
$fizzBuzz = new FizzBuzz();
$fizzBuzz->addRule(new FizzBuzzRule());
$fizzBuzz->addRule(new FizzRule());
$fizzBuzz->addRule(new BuzzRule());
...
$list = $fizzBuzz->generateList(100);
SOLID PRINCIPLES
HAVING CONCRETE DEPENDENCIES
FizzBuzz.php
FizzRule.php
BuzzRule.php
FizzBuzzRule.php
SOLID PRINCIPLES
HAVING ABSTRACT DEPENDENCIES
FizzBuzz.php RuleInterface.php
BuzzRule.phpFizzBuzzRule.php FizzRule.php
DEPENDENCY INVERSION PRINCIPLE VIOLATION
“MIXING DIFFERENT
LEVELS OF ABSTRACTION”
SOLID PRINCIPLES
<?php
use DoctrineDBALConnection;
class Authentication
{
private $connection;
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
public function checkCredentials($username, $password)
{
$user = $this->connection->fetchAssoc(
'SELECT * FROM users WHERE username = ?',
[$username]
);
if ($user === null) {
throw new InvalidCredentialsException(
"User not found."
);
}
//validate password
...
}
}
SOLID PRINCIPLES
AUTHENTICATION CLASS DEPENDS ON CONNECTION
Authentication.php Connection.php
<?php
class Authentication
{
private $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function checkCredentials($username, $password)
{
$user = $this->userRepository->ofUsername($username);
if ($user === null) {
throw new InvalidCredentialsException(
"User not found."
);
}
//validate password
...
}
}
<?php
class UserRepository
{
private $connection;
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
public function ofUsername($username)
{
return $this->connection->fetchAssoc(
'SELECT * FROM users WHERE username = ?',
[$username]
);
}
}
SOLID PRINCIPLES
AUTHENTICATION CLASS DEPENDS ON USER REPOSITORY
Authentication.php UserRepository.php Connection.php
<?php
interface UserRepositoryInterface
{
public function ofUsername($username);
}
<?php
class DoctrineDbalUserRepository implements UserRepositoryInterface
{
...
}
class TextFileUserRepository implements UserRepositoryInterface
{
...
}
class InMemoryUserRepository implements UserRepositoryInterface
{
...
}
class MongoDbUserRepository implements UserRepositoryInterface
{
...
}
<?php
class Authentication
{
private $userRepository;
public function __construct(UserRepositoryInterface $userRepository)
{
$this->userRepository = $userRepository;
}
}
SOLID PRINCIPLES
AUTHENTICATION CLASS DEPENDS ON USER REPOSITORY INTERFACE
Authentication.php UserRepositoryInterface.php
TextFileUserRepository.php DoctrineDbalUserRepository.php Connection.php

SOLID PRINCIPLES

  • 1.
  • 3.
  • 5.
  • 6.
  • 9.
    SINGLE RESPONSIBILITY PRINCIPLE “ACLASS SHOULD HAVE ONE, 
 AND ONLY ONE, REASON TO CHANGE.” SOLID PRINCIPLES
  • 10.
    <?php class ConfirmationMailMailer { private $templating; private$mailer; public function __construct( ITemplatingEngine $templating, IMailer $mailer ) { $this->templating = $templating; $this->mailer = $mailer; } public function sendTo(User $user) { $message = $this->createMessageFor($user); $this->sendMessage($message); } private function createMessageFor(User $user) { $subject = 'Confirm your email address'; $body = $this->templating ->render( 'confirmationMail.html.tpl', [ 'confirmationCode' => $user->getConfirmationCode() ] ); $message = new Message($subject, $body); $message->setTo($user->getEmailAdress()); return $message; } private function sendMessage(IMessage $message) { $this->mailer->send($message); }
  • 11.
  • 12.
    SOLID PRINCIPLES RESPONSIBILITIES ▸ CreateConfirmation Message ▸ Send Confirmation Message to user
  • 13.
    SINGLE RESPONSIBILITY PRINCIPLE “RESPONSIBILITIESARE REASONS TO CHANGE” SOLID PRINCIPLES
  • 14.
    SOLID PRINCIPLES SINGLE RESPONSIBILITYPRINCIPLE VIOLATIONS ▸ Many instance variables ▸ Many public methods ▸ Each method use variables from other instance ▸ Specific tasks are done by private methods
  • 15.
  • 16.
    <?php class ConfirmationMailFactory { private $templating; publicfunction __construct(ITemplatingEngine $templating) { $this->templating = $templating; } public function createMessageFor(User $user) { $subject = 'Confirm your email address'; $body = $this->templating ->render( 'confirmationMail.html.tpl', [ 'confirmationCode' => $user->getConfirmationCode() ] ); $message = new Message($subject, $body); $message->setTo($user->getEmailAdress()); return $message; } }
  • 17.
    <?php class ConfirmationMailMailer { private $confirmationMailFactory; private$mailer; public function __construct( ConfirmationMailFactory $confirmationMailFactory, IMailer $mailer ) { $this->confirmationMailFactory = $confirmationMailFactory; $this->mailer = $mailer; } public function sendTo(User $user) { $message = $this->confirmationMailFactory->createMessageFor($user); $this->sendMessage($message); } private function sendMessage(IMessage $message) { $this->mailer->send($message); } }
  • 18.
  • 19.
    SOLID PRINCIPLES SINGLE RESPONSIBILITYPRINCIPLE ADVANTAGES ▸ Smaller classes ▸ Classes easier to understand ▸ Classes easier to test ▸ Classes simpler to maintain
  • 20.
    OPEN/CLOSED PRINCIPLE “YOU SHOULDBE ABLE TO EXTEND A CLASS`S BEHAVIOR, WITHOUT MODIFYING IT.” SOLID PRINCIPLES
  • 21.
    <?php class GenericEncoder { public functionencodeToFormat($data, $format) { if ($format === 'json') { $encoder = new JsonEncoder(); } elseif ($format === 'xml') { $encoder = new XmlEncoder(); } else { throw new InvalidArgumentException('Unknown format'); } $data = $this->prepareData($data, $format); return $encoder->encode($data); } private function prepareData($data, $format) { switch ($format) { case 'json': $data = $this->forceArrayKeys($data); $data = $this->fixKeys($data); break; case 'xml': $data = $this->fixAttributes($data); break; default: throw new InvalidArgumentException( 'Format not supported' ); } return $data; } }
  • 22.
  • 23.
    <?php class GenericEncoder { public functionencodeToFormat($data, $format) { if (…) { … } elseif (…) { … } elseif ($format === 'yaml') { $encoder = new YamlEncoder(); } else { throw new InvalidArgumentException('Unknown format'); } } … }
  • 24.
    SOLID PRINCIPLES NEW REQUIREMENTSITUATION GenericEncoder.php XmlEncoder.php uses uses JsonEncoder.php YamlEncoder.php uses
  • 25.
    OPEN/CLOSED PRINCIPLE “A UNITOF CODE CAN BE CONSIDERED ‘OPEN’ FOR EXTENSION WHEN ITS BEHAVIOR CAN BE EASILY CHANGED WITHOUT MODIFYING IT” SOLID PRINCIPLES
  • 26.
    SOLID PRINCIPLES OPEN/CLOSED PRINCIPLEVIOLATIONS ▸ Conditions to determine a strategy ▸ Conditions using the same variables or constants are recurring inside the class or related classes ▸ Contains hard-coded references to other classes or class names ▸ Objects are being created using the new operator
  • 27.
  • 28.
    <?php interface EncoderInterface { public functionencode($data); } class JsonEncoder implements EncoderInterface { public function encode($data) { // TODO: Implement encode() method. } } class XmlEncoder implements EncoderInterface { public function encode($data) { // TODO: Implement encode() method. } } class YamlEncoder implements EncoderInterface { public function encode($data) { // TODO: Implement encode() method. } }
  • 29.
    SOLID PRINCIPLES INTRODUCING ENCODERINTERFACE GenericEncoder.php XmlEncoder.php uses JsonEncoder.phpYamlEncoder.php implements EncoderInterface.php implements implements
  • 30.
    <?php class EncoderFactory { public functioncreateForFormat($format) { if ($format === 'json') { return new JsonEncoder(); } elseif ($format === 'xml') { return new XmlEncoder(); } elseif ($format === 'yaml') { return new YamlEncoder(); } throw new InvalidArgumentException('Unknown format'); } }
  • 31.
    <?php class GenericEncoder { private $encoderFactory; publicfunction __construct(EncoderFactory $encoderFactory) { $this->encoderFactory = $encoderFactory; } public function encodeToFormat($data, $format) { $encoder = $this->encoderFactory->createForFormat($format); $data = $this->prepareData($data, $format); return $encoder->encode($data); } }
  • 32.
  • 34.
    <?php interface EncoderFactoryInterface { /** * Createan encoder for the given format * * @param string $format * @return EncoderInterface */ public function createForFormat($format) } class EncoderFactory implements EncoderFactoryInterface { ... } class GenericEncoder { public function __construct( EncoderFactoryInterface $encoderFactory ) { ... } ... }
  • 35.
    INTRODUCING DEPENDENCY INVERSIONPRINCIPLE SOLID PRINCIPLES GenericEncoder.php XmlEncoder.php uses JsonEncoder.phpYamlEncoder.php implements EncoderInterface.php implements implements EncoderFactoryInterface.php uses creates
  • 36.
    <?php class EncoderFactory implementsEncoderFactoryInterface { private $factories = []; /** * Register a callable that returns an instance of * EncoderInterface for the given format. * * @param string $format * @param callable $factory */ public function addEncoderFactory($format, callable $factory) { $this->factories[$format] = $factory; } public function createForFormat($format) { $factory = $this->factories[$format]; $encoder = $factory; return $encoder; } }
  • 37.
    <?php $encoderFactory = newEncoderFactory(); $encoderFactory->addEncoderFactory( 'xml', function () { return new XmlEncoder(); } ); $encoderFactory->addEncoderFactory( 'json', function () { return new JsonEncoder(); } ); $genericEncoder = new GenericEncoder($encoderFactory); $data = ... $jsonEncodedData = $genericEncoder->encode($data, 'json');
  • 38.
    <?php class GenericEncoder { private functionprepareData($data, $format) { switch ($format) { case 'json': $data = $this->forceArray($data); $data = $this->fixKeys($data); break; case 'xml': $data = $this->fixAttributes($data); break default: # code... break; } return $data; } }
  • 39.
  • 40.
    <?php class GenericEncoder { private $encoderFactory; publicfunction __construct( EncoderFactoryInterface $encoderFactory ) { $this->encoderFactory; } public function encodeToFormat($data, $format) { $encoder = $this->encoderFactory->createForFormat($format); $data = $encoder->prepareData($data); return $encoder->encode($data); } }
  • 41.
    <?php class JsonEncoder implementsEncoderInterface { public function encode($data) { ... } public function prepareData($data) { $data = $this->forceArray($data); $data = $this->fixKeys($data); return $data; } }
  • 42.
    <?php class GenericEncoder { private $encoderFactory; publicfunction __construct( EncoderFactoryInterface $encoderFactory ) { $this->encoderFactory; } public function encodeToFormat($data, $format) { $encoder = $this->encoderFactory->createForFormat($format); $data = $encoder->prepareData($data); return $encoder->encode($data); } }
  • 43.
    <?php class JsonEncoder implementsEncoderInterface { public function encode($data) { $data = $this->prepareData($data); return json_encode($data); } private function prepareData($data) { $data = $this->forceArray($data); $data = $this->fixKeys($data); return $data; } }
  • 44.
  • 45.
    <?php class GenericEncoder { public functionencodeToFormat($data, $format) { if ($format === 'json') { $encoder = new JsonEncoder(); } elseif ($format === 'xml') { $encoder = new XmlEncoder(); } else { throw new InvalidArgumentException('Unknown format'); } $data = $this->prepareData($data, $format); return $encoder->encode($data); } private function prepareData($data, $format) { switch ($format) { case 'json': $data = $this->forceArrayKeys($data); $data = $this->fixKeys($data); break; case 'xml': $data = $this->fixAttributes($data); break; default: throw new InvalidArgumentException( 'Format not supported' ); } return $data; } }
  • 46.
    <?php class GenericEncoder { private $encoderFactory; publicfunction __construct( EncoderFactoryInterface $encoderFactory ) { $this->encoderFactory; } public function encodeToFormat($data, $format) { $encoder = $this->encoderFactory->createForFormat($format); return $encoder->encode($data); } }
  • 48.
    LISKOV SUBSTITUTION PRINCIPLE “DERIVEDCLASSES MUST BE SUBSTITUTABLE FOR THEIR BASE CLASSES” SOLID PRINCIPLES
  • 49.
    SOLID PRINCIPLES WE NEEDTO UNDERSTAND ▸ Derived classes ▸ Being substitutable
  • 50.
  • 51.
    <?php /** * A concreteclass, all methods are implemented, but can be * overridden by derived classes */ class ConcreteClass { public function implementedMethod() { } } /** * An abstract class, some methods need to be implemented by * by derived classes */ abstract class AbstractClass { abstract public function abstractMethod(); public function implementedMethod() { } } /** * An interface, all methods need to be implemented by derived * classes */ interface AnInterface { public function abstractMethod(); }
  • 52.
  • 53.
  • 54.
    LISKOV SUBSTITUTION PRINCIPLE “ADERIVED CLASS DOES NOT HAVE AN IMPLEMENTATION FOR ALL METHODS” SOLID PRINCIPLES
  • 55.
    <?php interface FileInterface { public functionrename($name); public function changeOwner($user, $group); }
  • 56.
    <?php class DropboxFile implementsFileInterface { public function rename($name) { ... } public function changeOwner($user, $group) { throw new BadMethodCallException( "Not implemented for Dropbox files" ); } }
  • 57.
    <?php class DropboxFile implementsFileInterface { public function rename($name) { ... } public function changeOwner($user, $group) { throw new BadMethodCallException( "Not implemented for Dropbox files" ); } } //ask the user to check if $file is an instance of DropboxFile if (!($file instanceof DropboxFile)) { $file->changeOwner(...); }
  • 58.
    <?php class DropboxFile implementsFileInterface { ... public function changeOwner($user, $group) { //let's do nothing :) } }
  • 60.
    INTERFACE SEGREGATION PRINCIPLE “MAKEFINE-GRAINED INTERFACES THAT ARE CLIENT SPECIFIC” SOLID PRINCIPLES
  • 61.
    <?php interface FileInterface { public functionrename($name); } interface FileWithOwnerInterface extends FileInterface { public function changeOwner($user, $group); }
  • 62.
    <?php class DropboxFile implementsFileInterface { public function rename($name) { ... } } class LocalFile implements FileWithOwnerInterface { public function rename($name) { ... } public function changeOwner($user, $group) { ... } }
  • 63.
    SOLID PRINCIPLES NEW HIERARCHYOF FILE CLASSES DropboxFile.php extends LocalFile.php implements FileInterface.php implements FileWithOwnerInterface.php
  • 64.
    LISKOV SUBSTITUTION PRINCIPLE “DIFFERENTSUBSTITUTES RETURN THINGS OF DIFFERENT TYPES” SOLID PRINCIPLES
  • 66.
    <?php interface RouterInterface { public functiongetRoutes() : RouterCollection; } class SimpleRouter implements RouterInterface { public function getRoutes() { $routes = []; //add Route objects to $routes $routes[] = ...; return $routes } } class AdvancedRouter implements RouterInterface { public function getRoutes() { $routeCollection = new RouteCollection(); ... return $routeCollection; } }
  • 67.
    <?php interface RouterInterface { public functiongetRoutes() : RouterCollection; } class SimpleRouter implements RouterInterface { public function getRoutes() : RouteCollection { $routes = []; //add Route objects to $routes2 $routes[] = ...; return $routes } } class AdvancedRouter implements RouterInterface { public function getRoutes() : RouteCollection { $routeCollection = new RouteCollection(); ... return $routeCollection; } }
  • 68.
    LISKOV SUBSTITUTION PRINCIPLE “ADERIVED CLASS IS LESS PERMISSIVE WITH REGARD TO METHOD ARGUMENTS” SOLID PRINCIPLES
  • 69.
    <?php interface MassMailerInterface { public functionsendMail( TransportInterface $transport, MessageInterface $message, RecipientsInterface $recipients ); }
  • 70.
    <?php class SmtpMassMailer implementsMassMailerInterface { public function sendMail( TransportInterface $transport, MessageInterface $message, RecipientsInterface $recipients ) { if (!($transport instanceof SmtpTransport)) { throw new InvalidArgumentException( 'SmtpMassMailer only works with SMTP' ); } } }
  • 71.
    LISKOV SUBSTITUTION PRINCIPLE “NOTEVERY KIND OF MAIL TRANSPORT IS SUITABLE FOR MASS MAILING” SOLID PRINCIPLES
  • 72.
    SOLID PRINCIPLES NEW CLASSDIAGRAM SmtpTransport.php TransportInterface.php extends TransportWithMassMailSupportInterface.php implements
  • 73.
    <?php interface MassMailerInterface { public functionsendMail( TransportWithMassMailSupportInterface $transport, MessageInterface $message, RecipientsInterface $recipients ); }
  • 74.
    <?php class SmtpMassMailer implementsMassMailerInterface { public function sendMail( TransportWithMassMailSupportInterface $transport, MessageInterface $message, RecipientsInterface $recipients ) { … } }
  • 75.
    SOLID PRINCIPLES A GOODSUBSTITUTE ▸ Provides an implementation for all the methods of the base class. ▸ Returns the type of things the base class prescribes. ▸ Doesn’t put extra constraints on arguments for methods.
  • 76.
    INTERFACE SEGREGATION PRINCIPLE “MAKEFINE-GRAINED INTERFACES THAT ARE CLIENT SPECIFIC” SOLID PRINCIPLES
  • 77.
    DEPENDENCY INVERSION PRINCIPLE “DEPENDON ABSTRACTIONS, NOT CONCRETIONS” SOLID PRINCIPLES
  • 78.
    SOLID PRINCIPLES FIZZ BUZZASSIGNMENT ▸ Generate a list of integers, from 1 to n. ▸ Numbers that are divisible by 3 should be replace with “Fizz” ▸ Numbers that are divisible by 5 should be replaced with “Buzz” ▸ Numbers that are both divisible by 3 and by 5 should be replaced with “FizzBuzz”
  • 79.
    <?php class FizzBuzz { public staticfunction generateList($limit) { $list = []; for ($number = 1; $number <= $limit; $number++) { $list[] = self::generateElement($number); } return $list; } private static function generateElement($number) { if ($number % 3 === 0 && $number % 5 === 0) { return 'FizzBuzz'; } if ($number % 3 === 0) { return 'Fizz'; } if ($number % 5 === 0) { return 'Buzz'; } return $number; } }
  • 80.
    SOLID PRINCIPLES THEN… ▸ Itshould be possible to add another rule, without modifying the FizzBuzz class.
  • 81.
  • 82.
    <?php class FizzBuzz { public staticfunction generateList($limit) { ... } private static function generateElement($number) { $fizzBuzzRule = new FizzBuzzRule(); if ($fizzBuzzRule->matches($number)) { return $fizzBuzzRule->getReplacement(); } $fizzRule = new FizzRule(); if ($fizzRule->matches($number)) { return $fizzRule->getReplacement(); } $buzzRule = new BuzzRule(); if ($buzzRule->matches($number)) { return $buzzRule->getReplacement(); } return $number; } }
  • 83.
    <?php interface RuleInterface { public functionmatches($number); public function getReplacement(); }
  • 84.
    <?php class FizzBuzz { private $rules= []; public function addRule(RuleInterface $rule) { $this->rules[] = $rule; } public static function generateList($limit) { ... } private static function generateElement($number) { foreach ($this->rules as $rule) { if ($rule->matches($number)) { return $rule->getReplacement(); } } return $number; } }
  • 85.
    <?php class FizzBuzzRule implementsRuleInterface { ... } class FizzRule implements RuleInterface { ... } class BuzzRule implements RuleInterface { ... }
  • 86.
    <?php $fizzBuzz = newFizzBuzz(); $fizzBuzz->addRule(new FizzBuzzRule()); $fizzBuzz->addRule(new FizzRule()); $fizzBuzz->addRule(new BuzzRule()); ... $list = $fizzBuzz->generateList(100);
  • 87.
    SOLID PRINCIPLES HAVING CONCRETEDEPENDENCIES FizzBuzz.php FizzRule.php BuzzRule.php FizzBuzzRule.php
  • 88.
    SOLID PRINCIPLES HAVING ABSTRACTDEPENDENCIES FizzBuzz.php RuleInterface.php BuzzRule.phpFizzBuzzRule.php FizzRule.php
  • 89.
    DEPENDENCY INVERSION PRINCIPLEVIOLATION “MIXING DIFFERENT LEVELS OF ABSTRACTION” SOLID PRINCIPLES
  • 90.
    <?php use DoctrineDBALConnection; class Authentication { private$connection; public function __construct(Connection $connection) { $this->connection = $connection; } public function checkCredentials($username, $password) { $user = $this->connection->fetchAssoc( 'SELECT * FROM users WHERE username = ?', [$username] ); if ($user === null) { throw new InvalidCredentialsException( "User not found." ); } //validate password ... } }
  • 91.
    SOLID PRINCIPLES AUTHENTICATION CLASSDEPENDS ON CONNECTION Authentication.php Connection.php
  • 92.
    <?php class Authentication { private $userRepository; publicfunction __construct(UserRepository $userRepository) { $this->userRepository = $userRepository; } public function checkCredentials($username, $password) { $user = $this->userRepository->ofUsername($username); if ($user === null) { throw new InvalidCredentialsException( "User not found." ); } //validate password ... } }
  • 93.
    <?php class UserRepository { private $connection; publicfunction __construct(Connection $connection) { $this->connection = $connection; } public function ofUsername($username) { return $this->connection->fetchAssoc( 'SELECT * FROM users WHERE username = ?', [$username] ); } }
  • 94.
    SOLID PRINCIPLES AUTHENTICATION CLASSDEPENDS ON USER REPOSITORY Authentication.php UserRepository.php Connection.php
  • 95.
  • 96.
    <?php class DoctrineDbalUserRepository implementsUserRepositoryInterface { ... } class TextFileUserRepository implements UserRepositoryInterface { ... } class InMemoryUserRepository implements UserRepositoryInterface { ... } class MongoDbUserRepository implements UserRepositoryInterface { ... }
  • 97.
    <?php class Authentication { private $userRepository; publicfunction __construct(UserRepositoryInterface $userRepository) { $this->userRepository = $userRepository; } }
  • 98.
    SOLID PRINCIPLES AUTHENTICATION CLASSDEPENDS ON USER REPOSITORY INTERFACE Authentication.php UserRepositoryInterface.php TextFileUserRepository.php DoctrineDbalUserRepository.php Connection.php