It is often questioned as to why interfaces are a part of PHP, but they are a key part of any object oriented (OO) language and PHP is no exception. I just wanted to outline the importance of interfaces for proper OO design, and some problems you can encounter when you decide not to use them.
What is an interface?
In OO programming an interface defines a contract that all implementing classes must adhere by in order for the code to execute/compile. If we take the example of an error logger, we may have multiple error logging classes that are used to log to different location and use varying protocols. If we implement an interface across all of these logger classes we can guarantee that they all adhere to the contract set out by that interface, basically meaning that they all provide specific methods. An example of such an interface might be:
Now each one of our logger classes that implements this interface must provide the methods
info() in order for the code to execute. If PHP doesn't see these methods, then we would see a fatal error.
This guarantees a level of functionality across all of our logger classes, which makes our life much easier...
Type Hinting for Interfaces
When working on any OO codebase, type hinting should be a key part of your design because it:
- Guarantees that a method parameter is of a specific object/internal type
- Self documents the parameters to other developers (although we should all be writing PHP doc right?)
- Helps your IDE provide code completion (this is just a bonus)
Let's say we have a user manager in our application that expects a logger to be injected in its constructor. Now that our loggers all implement an interface, we can type hint for that interface rather than a specific class type:
Now our UserManager instance knows that it is receiving an object that adheres to the LoggerInterface contract, and we can call
$this->logger->info(); anywhere in our UserManager instance and we know that those methods will exist. Hoorah!
Type hinting in this way allows for greater compliance with the Liskov substitution principle (part of the SOLID OO programming principles), which will make our code base more easily maintainable.
Refactoring with Interfaces
Okay, so no one is perfect. We all write bad code from time to time, whether its a tight deadline, lack of sleep or no coffee in a morning. There comes a time when we have to refactor somebody else's code, it's not pleasant and rarely goes as planned.
Let's say we have to swap out that logger class that writes to a file, and instead write a logger that talks to Sentry. Usually, this would be painful because every class that depends on that logger would have to be checked and tested. However, because the developer before us was thoughtful and they used a
LoggerInterface we can breathe easy. Writing our new Sentry logger is as easy as ensuring we implement the
LoggerInterface. If our trusty former developer has type hinted for a
LoggerInterface in all of the dependent classes, we are good to go! Injecting a Sentry logger instance in all of the dependent classes will keep them happy because we are adhering to the interface's contract.
If we compare this with no type hinting or type hinting for a specific class implementation, we would have to:
- Write our new Sentry logger
- Make sure it has all of the same public methods that our old logger had (we aren't even sure if the dependent classes use all of these - but we can't be sure so we have to implement them)
- Refactor all of the dependent classes to ensure that they don't type hint for an instance of our old logger.
What a headache.
Another key principle in SOLID is the idea of interface segregation. This idea basically refers to smaller interfaces with specific semantic meaning rather than huge interfaces. If your interfaces are becoming too big (more than 5 or 6 methods) I would say that this smells like a breach of this principle and could do with a refactor!
Interfaces are a great tool in OO programming, use them well and you can save yourself (and other developers) a massive amount of time maintaining the code base.