1. Make Your Project SOLID
PHP Conference Argentina 2013
Benjamin Eberlei, @beberlei
4th October 2013
2. About me
Helping people to create high quality web applications.
http://qafoo.com
Doctrine Developer
Symfony Contributor
Twitter @beberlei and @qafoo
3. About me
Helping people to create high quality web applications.
http://qafoo.com
Doctrine Developer
Symfony Contributor
Twitter @beberlei and @qafoo
4. About me
Helping people to create high quality web applications.
http://qafoo.com
Doctrine Developer
Symfony Contributor
Twitter @beberlei and @qafoo
5. About me
Helping people to create high quality web applications.
http://qafoo.com
Doctrine Developer
Symfony Contributor
Twitter @beberlei and @qafoo
6. About me
Helping people to create high quality web applications.
http://qafoo.com
Doctrine Developer
Symfony Contributor
Twitter @beberlei and @qafoo
7. Why Object Orientation?
So, why do you want object oriented code?
Manage complexity
Reusable code
Maintainable code
8. Why Object Orientation?
So, why do you want object oriented code?
Manage complexity
Reusable code
Maintainable code
9. Why Object Orientation?
So, why do you want object oriented code?
Manage complexity
Reusable code
Maintainable code
10. Why Object Orientation?
So, why do you want object oriented code?
Manage complexity
Reusable code
Maintainable code
11. The SOLID principles
5 essential principles of object oriented design
Introduced by Robert C. Martin (Uncle Bob)
not the inventor of the principles
Have proven to lead to better code
Scientific background (partly)
12. By Example
A weather loader component
Fetch weather for a city
Relevant data:
Condition
Temperature
Wind
Be service-agnostic
Weather service come and go
Data licenses may change
Log service failures
Make it possible to add service fallbacks later
14. The Issue
1
<?php
2
3
class GoogleWeatherService
4
{
5
public function getWeatherForLocation ( L o c a t i o n $ l o c a t i o n )
6
{
$xml
= $ t h i s −> getData ( $ l o c a t i o n ) ;
$weather = $ t h i s −> e x t r a c t W e a t h e r ( $xml ) ;
r e t u r n $weather ;
7
8
9
}
10
11
12
protected function e x t r a c t W e a t h e r ( $xml )
13
{
$weather = new Weather ( ) ;
$weather −> c o n d i t i o n s = $ t h i s −> p a r s e C o n d i t i o n s ( $xml ) ;
// ...
$weather −>windSpeed = $ t h i s −> c o n v e r t M i l e s T o K i l o m e t e r (
$ t h i s −>parseWindSpeed ( $xml )
);
r e t u r n $weather ;
14
15
16
17
18
19
20
}
21
22
/∗ . . . ∗/
23
24
}
15. The Fix
1
<?php
2
3
class GoogleWeatherService
4
{
public function
construct (
H t t p C l i e n t $ c l i e n t , GoogleDataParser $ p a r s e r )
{ /∗ . . . ∗/ }
5
6
7
8
public function getWeatherForLocation ( L o c a t i o n $ l o c a t i o n )
9
{
10
$xml = $ t h i s −> c l i e n t −> g e t ( s p r i n t f (
’ h t t p : / / . . . / ? c i t y=%s ’ ,
$ l o c a t i o n −> c i t y
) );
r e t u r n $ t h i s −> parser −> parseWeather ( $xml ) ;
11
12
13
14
15
}
16
17
}
19. The Wrong Way
3
class WeatherLoader
4
{
public function
{ /∗ . . . ∗/ }
5
6
construct ( $service )
7
8
public function getWeatherForLocation ( S t r u c t L o c a t i o n $ l o c a t i o n )
9
{
11
// ...
switch ( g e t c l a s s ( $ t h i s −> s e r v i c e ) )
12
{
10
case ’ GoogleWeatherService ’ :
r e t u r n $ t h i s −> s e r v i c e −> getWeather ( $ l o c a t i o n ) ;
13
14
15
case ’ WetterComWeatherService ’ :
r e t u r n $ t h i s −> s e r v i c e −> r e t r i e v e W e a t h e r (
$ l o c a t i o n −> c i t y , $ l o c a t i o n −> c o u n t r y
);
// ...
16
17
18
19
20
}
21
}
22
23
}
20. The Right Way
3
class WeatherLoader
4
{
public function
{ /∗ . . . ∗/ }
5
6
c o n s t r u c t ( WeatherService $ s e r v i c e )
7
8
public function getWeatherForLocation ( S t r u c t L o c a t i o n $ l o c a t i o n )
9
{
// ...
r e t u r n $ t h i s −> s e r v i c e −> getWeatherForLocation ( $ l o c a t i o n ) ;
10
11
}
12
13
}
21. Open/Close Principle
Changes introduce errors
Especially cascading changes
Ideally: Write once, change never!
Extend software only by new code
New interface implementations
Inheritance
Aggregation
23. A Simple Class
1
<?php
2
3
class D i s t a n c e C o n v e r t e r
4
{
const FACTOR = 0 . 6 2 1 4 ;
5
6
7
public function m i l e s T o K i l o m e t e r s ( $ m i l e s )
8
{
r e t u r n $ m i l e s / s e l f : : FACTOR;
9
}
10
11
}
24. Getting into Trouble
1
<?php
2
3
class F o r m a t t i n g D i s t a n c e C o n v e r t e r extends DistanceConverer
4
{
5
public function m i l e s T o K i l o m e t e r s ( $ m i l e s )
6
{
7
i f ( $miles < 0 )
8
{
throw new I n v a l i d A r g u m e n t E x c e p t i o n ( ) ;
9
10
}
11
return s p r i n t f (
’ %01.2 f km ’ , p a r e n t : : m i l e s T o K i l o m e t e r s ( $ m i l e s )
);
12
13
}
14
15
}
31. Liskov Substitution Principle
Do not change contracts by inheritance
Methods must work as expected in derived classes
Users must not distinguish between super- and subclass
Subtype polymorphism
32. Liskov Substitution Principle
Do not change contracts by inheritance
Methods must work as expected in derived classes
Users must not distinguish between super- and subclass
Subtype polymorphism
33. Dependency Inversion Principle
“A. High-level modules should not depend
on low level modules. Both should depend
on abstractions.”
“B. Abstractions should not depend upon
details. Details should depend upon
abstractions.”
34. Dependency Inversion Principle
“A. High-level modules should not depend
on low level modules. Both should depend
on abstractions.”
“B. Abstractions should not depend upon
details. Details should depend upon
abstractions.”
35. The Issue
1
<?php
2
3
class WeatherLoader
4
{
6
public function
construct (
GoogleWeatherService $weatherService , F i l e L o g g e r $ l o g g e r )
7
{
5
$ t h i s −> w ea th e rS er v ic e = $weatherService ;
$ t h i s −> l o g g e r
= $logger ;
8
9
10
}
11
public function getWeatherForLocation ( L o c a t i o n $ l o c a t i o n )
12
{
// ...
$ t h i s −> l o g g e r −> l o g ( ’Some l o g message . ’ ) ;
// ...
$ t h i s −> l o g g e r −> w r i t e T o F i l e ( ) ;
13
14
15
16
}
17
18
}
36. Doing it Right
1
<?php
2
3
class WeatherLoader
4
{
6
public function
construct (
WeatherService $weatherService , Logger $ l o g g e r )
7
{
5
$ t h i s −> w ea th e rS er v ic e = $weatherService ;
$ t h i s −> l o g g e r
= $logger ;
8
9
10
}
11
public function getWeatherForLocation ( L o c a t i o n $ l o c a t i o n )
12
{
// ...
$ t h i s −> l o g g e r −> l o g ( ’Some l o g message . ’ ) ;
// ...
13
14
15
}
16
17
}
37. Dependency Inversion Principle
Use abstraction to encapsulate low level modules
Abstractions are the APIs
Abstractions hide implementation details
Depend on interfaces, not realizations
Define interfaces from a usage point of view
Finding abstractions is not easy
Dependency Injection, anyone?
38. Dependency Inversion Principle
Use abstraction to encapsulate low level modules
Abstractions are the APIs
Abstractions hide implementation details
Depend on interfaces, not realizations
Define interfaces from a usage point of view
Finding abstractions is not easy
Dependency Injection, anyone?
39. Dependency Inversion Principle
Use abstraction to encapsulate low level modules
Abstractions are the APIs
Abstractions hide implementation details
Depend on interfaces, not realizations
Define interfaces from a usage point of view
Finding abstractions is not easy
Dependency Injection, anyone?
40. Dependency Inversion Principle
Use abstraction to encapsulate low level modules
Abstractions are the APIs
Abstractions hide implementation details
Depend on interfaces, not realizations
Define interfaces from a usage point of view
Finding abstractions is not easy
Dependency Injection, anyone?
42. Suboptimal
3
class Loader
4
{
public function
{ /∗ . . . ∗/ }
5
6
c o n s t r u c t ( WeatherService $weatherService , Logger $ l o g g e r )
7
public function getWeatherForLocation ( L o c a t i o n $ l o c a t i o n )
{ /∗ . . . ∗/ }
8
9
10
}
3
a b s t r a c t class WeatherService
4
{
a b s t r a c t public function getWeatherForLocation ( L o c a t i o n $ l o c a t i o n ) ;
5
6
a b s t r a c t public function g e t F o r e c a s t F o r L o c a t i o n ( L o c a t i o n $ l o c a t i o n , $weekDay ) ;
7
8
}
43. The Fix
1
interface LocationWeatherProvider
2
{
function getWeatherForLocation ( L o c a t i o n $ l o c a t i o n ) ;
3
4
}
1
a b s t r a c t class WeatherService implements L o c a t i o n W e a t h e r P r o v i d e r
2
{
a b s t r a c t public function getWeatherForLocation ( L o c a t i o n $ l o c a t i o n ) ;
3
4
a b s t r a c t public function g e t F o r e c a s t F o r L o c a t i o n ( L o c a t i o n $ l o c a t i o n , $weekDay ) ;
5
6
}
1
class Loader
2
{
public function
{ /∗ . . . ∗/ }
3
4
c o n s t r u c t ( L o c a t i o n W e a t h e r P r o v i d e r $ p r o v i d e r , Logger $ l o g g e r )
5
public function getWeatherForLocation ( L o c a t i o n $ l o c a t i o n )
{ /∗ . . . ∗/ }
6
7
8
}
44. The Fix
1
interface LocationWeatherProvider
2
{
function getWeatherForLocation ( L o c a t i o n $ l o c a t i o n ) ;
3
4
}
1
a b s t r a c t class WeatherService implements L o c a t i o n W e a t h e r P r o v i d e r
2
{
a b s t r a c t public function getWeatherForLocation ( L o c a t i o n $ l o c a t i o n ) ;
3
4
a b s t r a c t public function g e t F o r e c a s t F o r L o c a t i o n ( L o c a t i o n $ l o c a t i o n , $weekDay ) ;
5
6
}
1
class Loader
2
{
public function
{ /∗ . . . ∗/ }
3
4
c o n s t r u c t ( L o c a t i o n W e a t h e r P r o v i d e r $ p r o v i d e r , Logger $ l o g g e r )
5
public function getWeatherForLocation ( L o c a t i o n $ l o c a t i o n )
{ /∗ . . . ∗/ }
6
7
8
}
45. Interface Segregation Principle
Avoid not needed dependencies
Design interfaces from a usage point of view
Do not let unnecessary functionality float in
46. The SOLID principles
Single Responsibility Principle
Open/Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle