DEVELOPING SOLID
APPLICATIONS WITH PHP
Created by Juan Manuel Torres / @onema
FOLLOW ALLONG:
Quick Survey
- Who is Familiar with OOP?
- Who uses OOP at work?
- Who uses PHPUnit?
- Who is Familiar with Design Patterns?
- Who is familiar with SOLID principles?
- Who makes an effort to use SOLID on a daily basis?
What is S.O.L.I.D?
is a mnemonic acronym that stands for five basic principles of object-oriented programming and design. The principles, when applied together, intend to make it more likely that a programmer will create a system that is easy to maintain and extend over time. [3]
According to Wikipedia SOLID:
What is S.O.L.I.D?
S
O
L
I
D
Single
Responsibility
High level Architecture
Open Closed
Design and extension
Liskov
Substitution
Correctness
Interface
Segregation
Thin interfaces
Dependency
Inversion
Level of Abstraction
S
Single Responsibility Principle
O
R
P
Just because you can, doesn't mean you should!
L
D
High Level Architecture
I
S
O
R
P
A class or module should have one, and only one, reason to change... Classes should have one responsibility—one reason to change. [4]
L
D
Single Responsibility Principle
High Level Architecture
I
While SPR is one of the simplest OO design principles, it is one of the most abused.
Once the program works, I'm done!
Use SRP to neatly organize your application and code
________________________________________________________
S
O
R
P
L
D
High Level Architecture
I
WHY?
Why do we want to use SRP?
Easier to understand and update. Multipurpose classes prevents us from clearly understanding the original intention of the code.
S
O
R
P
L
D
High Level Architecture
I
Why do we want to use SRP?
Better system design. Collaboration with other small classes to achieve system behavior is better than doing it all in one or few classes.
S
O
R
P
L
D
High Level Architecture
I
Example 1
interface Modem
{
public function dial(Phone $phone);
public function hangup();
public function send(Message $msg);
public function receive();
}
Single or multiple responsibilities?
S
O
R
P
L
D
High Level Architecture
Connection
Communication
I
class Version
{
public function getMajorVersionNumber() {...}
public function getMinorVersionNumber() {...}
public function getPatchVersionNumber() {...}
public function getBuildNumber() {...}
}
Single or multiple responsibilities?
S
O
R
P
L
D
High Level Architecture
I
Example 2
O
Open Close Principle
L
C
P
D
S
Open heart surgery is not required when putting on a coat!
Design & Extension
I
O
Open Close Principle
L
Classes, modules, functions should be open for extension and close for modification.
Open for Extension:
This means that the behavior of the module can be extended. [5]
Closed for Modification:
The source code of such a module is inviolate. No one is allowed to make source code changes to it. [5]
C
P
D
S
Design & Extension
I
class Pilot {
public function operate($vehicle) {
if ($vehicle->getType() == 'car') {
// drive a car
} elseif ($vehicle->getType() == 'plane') {
// fly a plane
}
}
}
class Car {
public function getType() {
return 'car';
}
}
Does not conform to the open-closed principle because we would have to modify Pilot when new vehicles are introduced, e.g. a Boat or a Rocket.
O
L
C
P
D
S
Design & Extension
class Plane {
public function getType() {
return 'plane';
}
}
Violation Example #1
I
interface Vehicle {
// ...
public function getType();
public function operateVehicle();
}
class Plane implements Vehicle {
// ...
public function operateVehicle() {
// implement fly plane
}
}
class Car implements Vehicle {
// ...
public function operateVehicle() {
// implement drive car
}
}
Use the Strategy Design Pattern and other design patterns to extend code without internal modifications!
O
C
P
S
Design & Extension
L
I
D
$pilot = new Pilot();
$car = new Car();
$pilot->operate($car);
$plane = new Plane();
$pilot->operate($plane);
Pilot can conduct any type of vehicle without having to modify the pilot code.
O
C
P
S
Design & Extension
class Pilot {
// ...
public function operate(Vehicle $vehicle) {
$vehicle->operateVehicle();
}
}
L
I
D
L
S
D
S
P
O
If it looks like a duck
Quacks like a duck
But needs batteries
You probably have the wrong abstraction
Liskov Substitution Principle
Correctness
I
L
Liskov Substitution Principle
S
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T. [9]
S
P
O
D
Correctness
I
L
S
D
S
P
O
o2 (T)
o1 (S)
T
S
P
If for each object o1
there is an object o2
such that for all programs P defined in terms of T,
the behavior of P is unchanged when o1 is substituted for o2,
then S is a subtype of T.
of type S
of type T
Subtypes must be substitutable for their base types.
Child classes should never break the parent class' type definitions [8]
Correctness
I
L
S
S
P
O
D
Violation Example #1
interface Bird {
public function birdSound();
public function fly();
}
class Duck implements Bird {
public function birdSound() {
echo 'quack!';
}
public function fly() {
echo 'duck is flying!';
}
}
class Ostrich implements Bird {
public function birdSound() {
echo 'Ostrich noises!';
}
public function fly() {
echo "D'oh!";
}
}
Introduces new behavior,
NON-FLYING BIRD!!!
Correctness
I
L
S
S
P
O
D
Violation Example #2
class Ostrich implements Bird {
// ...
public function fly($jetPack) {
echo "Attach jetpack to Ostrich and FLY!";
}
// ...
}
class BirdSimulator {
public function __construct(Bird $bird) {...}
public function main() {
echo 'This bird says '.$this->bird->birdSound();
echo 'This bird can Fly '.$this->bird->fly();
}
}
Declaration of Ostrich::fly() must be compatible with bird::fly()
What error are we going to get here?
Correctness
I
$birdSim1 = new BirdSimulator(new Duck());
$birdSim1->main();
$birdSim2 = new BirdSimulator(new Ostrich());
$birdSim2->main();
L
S
S
P
O
D
So what is the point?
The Liskov Substitution Principle (A.K.A Design by Contract) is an important feature of all programs that conform to the Open-Closed principle.
It is only when derived types are completely substitutable for their base types that functions which use those base types can be reused with impunity, and the derived types can be changed with impunity. [16]
Correctness
I
I
S
L
S
P
O
D
Interface Segregation Principle
Thin interfaces
You want me to plug this in where?
I
S
L
S
P
O
D
The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use. [11]
Interface Segregation Principle
Classes that implement interfaces should not be forced to implement methods they do not use. [12]
Thin interfaces
I
S
L
S
P
O
D
This principle deals with disadvantages of fat interfaces
interface Vehicle {
public function accelerate();
public function brake();
public function changeGear($gear);
public function ejectCD();
public function getType()
public function lightsOn();
public function operateVehicle();
public function signalLeft();
public function signalRight();
public function startEngine();
public function stopRadio();
}
Thin interfaces
If you put too many things in your vehicle you are going to leave your donkey up in the air!
I
S
L
S
P
O
D
Each interface should be grouped into methods that serve a different set of clients
interface CDRadio {
public function stopRadio();
public function ejectCD();
}
interface SpeedControl {
public function accelerate();
public function brake();
public function changeGear($gear);
public function startEngine();
}
class Car implements Vehicle {
public function operateVehicle() {
$speedControl = new CarSpeedControl();
$speedControl->startEngine();
$speedControl->accelerate();
// ...
}
}
Thin interfaces
D
S
L
I
P
O
I
Dependency Inversion Principle
Level of Abstraction
Would you solder a lamp directly to the electrical wiring in a wall?
D
S
L
I
P
O
I
Dependency Inversion Principle
A. High level modules should not depend upon low level modules. Both should depend upon abstractions. [18]
B. Abstractions should not depend upon details. Details should depend upon abstractions. [18]
Level of Abstraction
D
S
L
I
P
O
I
Level of Abstraction
Dependency Inversion Principle VS Dependency Injection
Dependency Inversion: is about programming to interfaces NOT implementations.
Dependency Injection: Is the means by which an object acquires a dependency. [13]
Dependency Inversion can be achieved WITH or WITHOUT Dependency Injection.
D
S
L
I
P
O
I
Level of Abstraction
class Car implements Vehicle {
public function operateVehicle() {
$speedControl = new CarSpeedControl();
$speedControl->startEngine();
$speedControl->accelerate();
// ...
}
}
SRP
ISP
OCP
Broke Vehicle interface into single purpose interfaces
The class depends on a specific implementation of SpeedControl
D
S
L
I
P
O
I
Level of Abstraction
CarSpeedControl
Depends on
Car
SpeedControl Interface
Depends on an interface
Car
CarSpeedControl
TurboSpeedControl
Classes "communicate" through an abstraction. Classes don't need to know about each other!
TestSpeedControl
LSP
D
S
L
I
P
O
I
Level of Abstraction
class Car implements Vehicle {
public function __construct(SpeedControl $speedControl) {
$this->speedControl = $speedControl;
}
public function operateVehicle() {
$this->speedControl->startEngine();
$this->speedControl->accelerate();
// ...
}
}
$myCar = new Car(new CarSpeedControl());
$turboCar = new Car(new TurboSpeedControl());
S
L
O
I
EXAMPLE
D
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Local;
$filesystem = new Filesystem(new Local(__DIR__.'/path/to/root'));
// Write Files
$filesystem->write('path/to/file.txt', 'contents');
// Update Files
$filesystem->update('path/to/file.txt', 'new contents');
// Write or Update Files
$filesystem->put('path/to/file.txt', 'contents');
// Read Files
$contents = $filesystem->read('path/to/file.txt');
// Check if a file exists
$exists = $filesystem->has('path/to/file.txt');
League\Flysystem
S
L
O
I
EXAMPLES
D
Is the
S.O.L.I.D.?
- Single Responsibility: Arguable, classes implement read and write methods...
- Open Closed: Yes! it follows a strategy pattern where each adapter is neatly encapsulated in a class.
- Liskov substitution: Classes implement an interface, (even the main file system class).
- Interface Segregation: Arguable, see SRP
- Dependency Inversion: Yes! File system depends on the Adapter Interface, hence we can use different adapters.
League\Flysystem
Conclusion
- Use SRP to keep your code clean and organized
- Use OCP to enable you and others to extend your code
- Use LSP to ensure your classes can be reused without side-effects
- Use ISP to prevent forcing subtypes to implement methods they don't use
- Use the DIP to create more modular code
The Road to S.O.L.I.D.
- S.O.L.I.D. Requires repetition
- Be disciplined about repetition
- Use design patterns to solve problems
- Use libraries, components and frameworks to help you out
- Do not be discouraged
- Do not over do it
- You can always find ways to over-engineer your code
- Do enough to have a good design
- Do enough to cover your use cases
- Refactoring is a part of life APPLY S.O.L.I.D. WHILE REFACTORING
Questions?
THE END
BY Juan Manuel Torres / @onema
REFERENCES
- Object-oriented programming
- Object Oriented PHP for Beginners
- SOLID
- Clean Code
- The Open-Closed Principle
- Object Oriented Programming Principles (Abstraction)
- Object Oriented Programming Principles (Encapsulation)
- Liskov Substitution & Interface Segregation Principles
- Data abstraction and Hierarchy
- Understanding and Applying Polymorphism in PHP
- Interface Segregation Principle
- The I in SOLID
- From STUPID to SOLID code
- The Single Responsibility Principle
- The Open-Closed Principle
- Liskov Substitution Principle
- The Interface Segregation Principle
- The Dependency Inversion Principle
Developing SOLID applications with PHP
By Juan Manuel Torres
Developing SOLID applications with PHP
- 995