Solid OOP Design Part V

Whew, this is the final part of this lesson series. Congrats for making it this far. In the previous lesson, we looked at the Integregation Segregation Principle. We showed how it's better to have multiple smaller interfaces that are very specific than to have one interface to rule them all. In this lesson, we we will cover the Dependency Inversion Principle.

What is the Dependency Inversion Principle?

The principle states:

A. High-level modules should not depend on low-level modules. Both should depend on abstractions.

B. Abstractions should not depend on details. Details should depend on abstractions.

Clear as mud?

Let's look at the following example.

As you can see in our makeMenu method, we have tightly coupled our Validator to our menu method. Even though our core validator logic has been abstracted away to the validator class, we still have the problem where we are depending on a concrete implementation of a Validator.

From the MenuMaker's point of view, it shouldn't care about the details of the validator. No more than you care about the details of your vehicle engine when you press the gas petal. The implementation details of pistons, clutch, gears, etc. should not be required to press the gas petal. The low level functionality of the vehicle, is abstracted away from you, the client.

What if we were change the type of validator we were to use?

The way this code is written now, our Menu Maker has very intimate knowledge about the type of validator that is being used. If for some reason, we were to change the validator to a different type of validator, we would also have to update the client (MenuMaker). This would violate the Open/Closed principle. So let's create an interface for our validator and place that code behind an interface.

MenuMaker client with injected validator

The validator will have to be passed in from the outside, so let's go back to our bootstrapper.php file and pass a new Validator instance into the constructor of the MenuMaker class.

bootstrap.php passing validator instance

But Wait... we aren't finished yet:

That instance of validator is a concrete implementation. If we really want to adhere to the Dependency Inversion Principle, we must pass in an abstract implemenation of the validator. Then allow a more concrete implementation to extend this abstraction. Let's begin this process.

We will begin by renaming Validator.php to MenuValidator.php. Then we will add an abstract class called Validator.php to the contracts folder.

The new directory structure

new directory structure

The abstact Validator class:

renaming Validator.php

The MenuValidator class

MenuValidator

Don't forget, we have to pass in the MenuValidator implementation upon bootup:

bootstrap.php

A Little Review

Now, let's take a step back and think about what we've just performed. Before the refactor, the validator class was tightly coupled to the MenuMaker class (the client). We established that this was bad design. To avoid this set of problems, we type hinted the MenuMaker class constructor parameter and passed in a concrete validator (MenuValidator) from the outside. Passing objects from the outside allows for greater flexibility. If we want to swap out the MenuValidator for a CustomValidator instead, it's super easy. We just have to make sure the CustomValidator extends the abstract Validator and conforms to it's contract.

What about $_POST

If you have been following along, you may have noticed that I access $_POST everywhere in the code base.

For example:

Example of $_POST

By accessing the Post array directly, none of this code can work outside of the web. Riddle me this: How would you be able to test the ExporterFactory class? You can't because as soon as you try to run the createExporters method from the command line, you will get errors because the $_POST array is not available from cli.

Passing from the outside

The key take away is decouple as much as possible. Let's look at passing the input from the user from the outside instead of tightly coupling the $_POST array to all our classes. Let's start with the MenuMaker client and then move on to the rest of our classes.

bootstrapper.php

bootstrapper with $_POST injected

Now we can pass the user input array through as a parameter. Let's proceed with removing $_POST everywhere in our code (except for bootstrapper.php):

MenuMaker.php:

MenuMaker client

Let's move on to the validator.

Contracts/Validator.php
abstract Validator

MenuValidator.php:

MenuValidator

Exporters/ExporterFactory:
ExporterFactory

Contracts/Exportable.php:
Exportable

Contracts/Emailable.php:
Emailable

For both the CsvExporter and HtmlExporter, we have a hardcoded string in both of those files. Let's refactor that while we are in there.

Updated CsvExporter:

CsvExporter with config

The Config

You might be wondering what that config variable is. Well, I'm completely opposed to hardcoded strings. They are bad, very bad. Imagine if a team of developers hard coded one string a month in a code base that is 10,000+ lines of code. Eventually, the code would be incomprehensible. I like to create a config file for things like this. That way it's in a centralized place.

config.php

Lastly, let's take a look at the HtmlExporter refactor.

HtmlExporter

HtmlExporter continued...

HtmlExporter

Conclusion

Now we are finished refactoring using SOLID principles. You can find the repository for the finished product here: https://github.com/cegrif01/menu_maker/tree/dependency_inversion

What you should have noticed is how all five of these principles work together. Violating one, most likely violates others. I hope you are able to benefit from this series.

Charles Griffin