Published on Aug 10, 2011 by Pim Elshoff
Programming #SOLID #OOP #PHP #Architecture #Principles
Oh how we love acronyms. We’ve discussed a lot about writing a class, but we haven’t talked about writing classes yet. How do you know if your solution is right? It is not enough to have a working program. SOLID is a set of principles that define severable measurable properties your architecture should have at least, in order to be dubbed right.
I’m no expert in this area and I’m still learning. This article is about my understanding as it is currently. While I grasp the concepts in theory, I still find it very hard to apply them in practice. If you find any errors, misconceptions or generally disagree with me, please let me know.
When designing and programming a system two people rarely produce the exact same solution. What may be a good name or good structure to me, does not have to be to you. This article is not about that. This article is about the principles that you can and should apply when you are setting up the architecture of your system. It’s about objective, measurable metrics that tell you about the quality of your project. They don’t tell you what to do, but they do tell you what not to do.
If you followed my article on setting up PHPUnit and other QA tools you will see for yourself that code written in this manner will have better characteristics.
It should also be mentioned that PHP lacks a very important feature that a language such as Java does implement. Functions in php are uniquely identified by their namespace and name only, not their parameters. This means you can’t overload a method to be called with the most specific class name. The only thing you can do is write a (wrapper) method that checks the type of the object at runtime. This breaks one of the principles we’ll discuss, but I think we can be forgiven for that.
Also, this post is rather tl;dr. Sorry, but there’s just so much to say! For your enjoyment I mixed in some photos of snowy France.
Enough chatter, let’s get to it. SOLID is an acronym. It stands for:
We have touched on the LSP before, but we shall now asses all these principles seriously. SOLID is not where this ends. The complete acronym Uncle Bob explains is SOLID RCC ASS. We’ll get to the rest some other time; they’re about packages and coupling between packages.
Every class should have exactly one goal.
This one is pretty straightforward. If you can’t explain what your class does in a few words, it’s probably responsible for too much functionality. A more valuable description is: can I think of more than one reason for this class to change. If you can, your class solves more than one (part of the) problem and should be split up.
If you encounter this problem in a codebase you’re maintaining you can look into moving functionality out of the class into a new class, or splitting the entire class up into smaller classes and keeping the class only as a façade. Often the latter can be combined with extracting interfaces to ensure that the new façade and its aggregations (the classes it uses) are loosely coupled (not depending on each other, but on the contracts they fulfill).
I’ve made this mistake here and there, but learned a lot from it. I think it occurs when you haven’t completely worked out the functionality of the system at hand. The functionality grows or changes, methods are added and before you know that one goal you set is either too vague/abstract to implement in one class, or it’s not one goal anymore.
class Person
{
public function getName()...
public function setName()...
public function echoName()...
This unfinished class has taken a wrong turn right from the start. It is responsible for managing both retaining data and creating output. A better structure would be:
class Person
{
public function getName()...
public function setName()...
}
class NameView
{
public function doOutput($name)...
}
class Controller
{
public function personAction()
{
// Get a person from some data source
$person = ...;
$view = new NameView();
$view->doOutput($person->getName());
}
}
This may seem contrived, because I just turned a 4 LOC program into a gazillion LOC program, but the responsibilities are clear; the Person class is just holding on to data, the NameView class is just there for output and the Controller class is responding to some request by getting all the needed data and putting it in the right view. This second piece of code is much more resistant to change and much more suited for reuse.
Every class should be open for extension and closed for modification.
The Open-Closed principle ensures that the code you write can be extended by others without needing to change it. It’s about hiding properties and methods and only exposing a public API that you support to be used and that’s generic enough to foresee in the ways your clients want to extend it. Code that is structured like this said to close the problem.
If you encounter code that does not close the problem and thus requires changing in order to extend its behavior, consider rewriting the problem to a more abstract level. By extracting the abstraction, solving the abstract problem and then allowing the clients to call the code on a concrete implementation of the abstraction you have successfully closed the problem further. Do mind though that since there are few clairvoyant programmers, there is little code that closes its problem entirely.
Let’s revisit an alternative solution to our previous issue.
class Person
{
public function getName()...
public function setName()...
}
class Employee extends Person
{
...
}
class NameView
{
public function doOutput(Person $person)
{
if (Person instanceof Employee)
{
echo ‘Hi employee ‘, $person->getName();
}
else
{
Echo ‘Hi person ‘, $person->getName();
}
}
}
class Controller
{
public function personAction()
{
// Get a person from some data source
$person = ...;
$view = new NameView();
$view->doOutput($person);
}
}
We introduced ‘Employee’ and made the view detect which type of Person it got, so it can provide different welcoming messages. Neat! We commit the code and everyone is happy. Then someone comes along and wants to add a new class Student and output a message specifically for students as well. But for her to do that, she would have to modify the view we made!
I can think of two ways we could easily solve this problem. We could write a PersonWelcomeMessage class that generates the welcome message and extend it in EmployeeWelcomeMessage and StudentWelcomeMessage, have that generate a string and put that into the view. Alternatively you could have the class Person define a function ‘getTitle’, which returns person for Person and is overwritten in Employee and Student to return ‘employee’ and ‘slacker’ respectively.
class Person
{
...
public function getTitle()
{
return ‘person’;
}
}
class Employee extends Person
{
...
public function getTitle()
{
return ‘employee’;
}
}
class NameView
{
public function doOutput(Person $person)
{
echo ‘Hi ‘, $person->getTitle(), ‘ ‘, $person->getName();
}
}
There has been discussion in the PHP community whether or not this principle is valid in all cases. Symfony and Zend Framework rely in part on exposing the inner workings exactly because implementers can modify them. Symfony2 is moving away from this successfully, but not without criticism.
A subclass’s preconditions should be at least as weak and its postconditions should be at least as strong as its parent’s.
Every method that a subclass overwrites from its parent should accept at least the parameters of the parent and should return at most the return value of the parent. The LSP makes sure that you can use the child in every situation you can use the parent. This makes sense because inheritance defines an ‘is a’ relationship and this is expected behavior. The OOP syntax allows you to easily break this, though.
If you encounter code that breaks the LSP, consider if your class is really a valid subclass. Alternatively, you can loosen the parent’s conditions or tighten the child’s conditions.
A well-known example of an LSP violation is extending a Square class from a base Rectangle class. While in mathematics a square is a special case of a rectangle, namely one in which all sides are of equal length, for OOP they are quite different. The class of rectangle could require some way of setting both the width and the height, where this use case is completely invalid for the square.
class Rectangle
{
public function setHeight($height)
{
$this->height = $height;
}
public function setWidth($width)
{
$this->width = $width;
}
...
}
class Square extends Rectangle
{
/**
* Square has equal length sides so set both
*/
public function setHeight($height)
{
$this->height = $height;
$this->width = $height; // ??
}
public function setWidth($width)
{
$this->height = $width; // ??
$this->width = $width;
}
...
}
class RectangleUser
{
public function doSomething(Rectangle $rectangle)
{
$rectangle->setHeight(2);
$rectangle->setWidth(1);
$rectangle->render();
// Unexpected behavior follows, because the postconditions
// of these methods is that the height is 1 and width is 2
...
}
}
This code can work, but it is difficult to understand and behaves unpredictably. Instead of relying on the very concrete rectangle implementation, the methods Rectangle and Square share should be extracted into an interface and possibly (partially) implemented in an abstract class. Other classes that used these methods of the Rectangle class should be made to rely on the new interface instead. Example:
interface Shape
{
public function render();
}
class Rectangle implements Shape
{
public function setHeight($height)
{
$this->height = $height;
}
public function setWidth($width)
{
$this->width = $width;
}
...
}
class Square implements Shape
{
/**
* Square has equal length sides so set both
*/
public function setLength($length)
{
$this->length = $length;
}
}
class ShapeUser
{
public function doSomething(Shape $shape)
{
// The dimensions of the shape cannot be set in a
// general fashion, but the render method is generic
// $shape should be configured before calling doSomething
$shape->render();
...
}
}
A class should not depend on a class with a large number of methods of which it uses only few.
We’ve recently worked on a project that called a remote service. The service provided a large set of grouped methods. We created a library to work with the various groups and needed a communication adapter to relay our calls to the service. We didn’t want to create multiple concrete classes that do virtually the same thing. Moreover there is a lot of shared code and functionality, such as opening and closing the connection, authentication, etc. And finally we didn’t want to pass around an object with a huge interface where the clients use only a small portion.
We ended up defining interfaces for all the specific groups and have a concrete adapter implement the interfaces. We then could pass around an object with a large, but only slightly exposed interface.
One way to do it:
class ServiceAdapter
{
public function addUser(...) {...}
public function removeUser(...) {...}
public function getUsers(...) {...}
public function addList(...) {...}
public function removeList(...) {...}
public function getLists(...) {...}
public function addFoo(...) {...}
...
}
class UserController
{
public function __construct(ServiceAdapter $adapter)
{
// Even though we are only interested in getUser,
// code hinting gives us getUser, getLists, getFoos...
$adapter->getUse
A better way to structure this would be:
interface ServiceUserAdapter
{
public function addUser(...);
public function removeUser(...);
public function getUsers(...);
}
...
class ServiceAdapter
implements ServiceUserAdapter, ServiceListAdapter, ...
{
public function addUser(...) {...}
public function removeUser(...) {...}
public function getUsers(...) {...}
public function addList(...) {...}
public function removeList(...) {...}
public function getLists(...) {...}
public function addFoo(...) {...}
...
}
class UserController
{
public function __construct(ServiceUserAdapter $adapter)
{
// Now code hinting only sees getUser(...)
$adapter->getUse
We still have the not-entirely-coherent ServiceAdapter but we are also only exposing the methods as they are needed. The UserController does not ‘know’ about the other methods and so there is no risk of them being called inappropriately.
A class that defines an abstract or high level functionality should never depend on any class or method that defines a concrete or low level functionality.
For example, when you are defining an interface you should not design methods with type hints of concrete class names. A concept should instead only rely on classes or interfaces that are at least equally abstract.
If you find your abstract code relying on concrete code, try and extract abstractions from the concrete code and have the abstract code rely on that.
interface HighlyAbstractConcept
{
public function setDatabaseConnection(MySQLDatabaseAdapter $adapter);
public function awesomeMethod();
...
}
Even though this code is very abstract itself, it relies on a very concrete database adapter. I’m not sure any interface that is not directly in a database abstraction component should reference a database at all, but if you want you could do something like this:
interface HighlyAbstractConcept
{
public function setDatabaseConnection(AbstractDatabaseAdapter $adapter);
public function awesomeMethod();
...
}
public ConcreteImplementation implements HighlyAbstractConcept
{
...
}
$ci = new ConcreteImplementation();
$adapter = ZendDb::factory(...);
$ci->setDatabaseConnection($adapter);
As if it’s not enough already, there is still more to be considered when designing and writing OOP software. If you know of more of these, please let me know!
A method should only call a property or method that is present in the current class, present in an object that is instantiated in the method itself or that is present in an object that is the value of a property of the current object.
In other words:
// good
$this->a;
$this->a();
$this->a->b();
$a;
$a->b();
// bad
$this->a()->b();
$a->b()->...;
// ugly
$value->getField()->getFieldType()->getBaseType();
Yea that last one was mine in a recent project… Perhaps this is a bit of an over dramatization and you should really decide for yourself if you are not introducing too much complexity, but for me changing that line to $value->getType() cleared up a lot.
A compelling reason for the LoD is that not adhering to it makes unit testing very un-trivial, because you will need to mock a lot of behavior into your tests.
Think of it like this: you can draw your entire application as a set of points and arrows, where each point is a class and each arrow indicates that a class uses another class. You want every class to only be worried about its direct neighbors, and as little of them as possible. If you change a class and find that you’re worried some other class at the other end of the spectrum breaks down, you’re in trouble. See also this article for more information on the LoD.
I was surprised to learn a lot of these things only after my university education. I scored high grades on OOP but never learned anything about principles to guide us during a larger scale project. With all the math, the abstract courses and the all but unused languages such as Clean I think the science of oop software architecture could do with a bit more attention.
Some of my inspiration for writing this has come from the articles of Giorgio Sironi, an author on Dzone.com. He has also been my starting source on PHPUnit with his book ‘Practical Php Testing’.
But most of this article is based on the works of Robert C. Martin, known as ‘Uncle Bob’. His writings on SOLID have been available since before I started high school, over 14 years ago.
I also referenced some refactoring methodologies. You can read more on that @Agile guru Martin Fowler’s alpha list of refactorings.
This article is part of the Object Oriented Programming series.
No trackbacks yet
Shay Ben Moshe said:
Very nice article!Thank you! 23:14, 11 August 2011
Shameer said:
Great article.. 05:24, 12 August 2011james fuller said:
all devs should be required to read "clean code" by robert martin 07:29, 12 August 2011Julien said:
Great article, thank you!NB: Your link "my article on setting up PHPUnit and other QA tools" is broken and there is a typo in the interface segregation code:
// Now code hinting only sees getUser(...)
$adapter->getUse
// should be $adapter->getUsers(...)
10:20, 12 August 2011
Berry Langerak said:
Love the pictures :) 14:24, 12 August 2011Pim Elshoff said:
Thanks guys!@james: ehr, yea they should :$ it's on my todo list...
@Julien: thanks, links are fixed! Stupid tinymce :( As for the typo, code hinting shouldn't fire on complete code ;-) But thanks for your attention to detail!
@Berry: thanks :) 15:26, 12 August 2011
sigdipsydaY said:
If someone want to get accurate information your site is the place to find them. Keep it up mate. 15:30, 12 August 2011Ashesh said:
That was a wonderful read. Thanks a lot 12:17, 20 August 2011