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 

  1. Use SRP to keep your code clean and organized
  2. Use OCP to enable you and others to extend your code
  3. Use LSP to ensure your classes can be reused without side-effects
  4. Use ISP to prevent forcing subtypes to implement methods they don't use
  5. Use the DIP to create more modular code

The Road to S.O.L.I.D.

  1. S.O.L.I.D. Requires repetition
  2. Be disciplined about repetition
  3. Use design patterns to solve problems
  4. Use libraries, components and frameworks to help you out
  5. Do not be discouraged
  6. Do not over do it
    1. You can always find ways to over-engineer your code
    2. Do enough to have a good design
    3. Do enough to cover your use cases
  7. Refactoring is a part of life APPLY S.O.L.I.D. WHILE REFACTORING

Questions?

THE END

BY Juan Manuel Torres / @onema

REFERENCES

Developing SOLID applications with PHP

By Juan Manuel Torres

Developing SOLID applications with PHP

  • 1,015