Lately, I’ve written extensively about object-oriented PHP, from class inheritance and visibility, to design patterns and magic methods. In all of these discussions, I’ve talked about classes without taking into account object interfaces.
Interfaces, often referred to as contracts or agreements, set up rules for classes in which a class that implements an interface has to follow those specific rules. Interfaces put constraints on your classes that improve your code. In this article, I’m going to discuss what interfaces are and what they can and can’t do. By the end, you will be able to see if you should use interfaces to improve your code.
What Exactly Are Interfaces?
Interfaces describe the method or methods possessed by the specific class that implements them. It does not in any way describe what those methods will do as all methods defined in an interface must be public.
When I started working with interfaces, I was often annoyed that I couldn’t declare a method as protected or private in an interface. This was a sign that I was missing the point of the interface.
If I write a class to act as an object of a class that implements an interface, I can identify its public methods and signatures.
Here is a quick example from Caldera Forms, an interface that our payment processors should implement.
interface Caldera_Forms_Processor_Interface_Payment { /** * Do Payment * * @since 1.3.5.3 * * @param array $config Processor config * @param array $form Form config * @param string $proccesid Unique ID for this instance of the processor * @param Caldera_Forms_Processor_Get_Data $data_object Processor data * * @return Caldera_Forms_Processor_Get_Data */ public function do_payment( array $config, array $form, $proccesid, Caldera_Forms_Processor_Get_Data $data_object ); }
I can implement this interface for Stripe, PayPal, or any other payment processor, and know that to trigger the payment, this method will be used. While payment will be processed differently, it is completely fine as the interface does not describe how a method works, it describes which method does what.
Arguments And Return Types
When creating a method in an interface, you do not add a body to the function. Instead, you define its name and arguments. The class that implements this interface must use the exact same arguments, including the defaults defined by the interface.
Methods in an interface can use type hinting. If they do, the class that implements them must use the same type hints. For example, we have an interface in Caldera Forms for form processors to implement. Look at the first method in this interface:
public function pre_processor( array $config, array $form, $proccesid );
A class implementing this interface has to work with an array passed to its first two arguments. If a class can’t be written that way then it shouldn’t use this interface.
If you look at the source for the interface, you will notice there is a method called fields. It returns an array, but because PHP5 does not have return type declaration, that is a convention, not a rule. If I was writing that interface knowing it would only be used in PHP7, I would add a return type like this:
Public function fields() : array;
Combining Interfaces
Interfaces, like classes, can be combined with other interfaces and inherit their methods. This is great because it allows you to create simple interfaces and put them together for larger projects.
Earlier I showed you an interface in Caldera Forms for form processors. We have another interface specifically for processors that add subscribers to newsletters. This interface inherits the main processor interface but adds one additional method. Newsletter processors need the basic processor methods and a subscribe method.
Interface inheritance allows multiple interfaces to be combined without duplicating code. In some cases. It is also possible, in PHP 5.3.9 or later, for a class to implement multiple interfaces. The syntax uses a comma between interfaces names. Here is an example of the declaration of a controller class for use with the Symfony router I used recently:
class User implements ServiceProviderInterface, BootableProviderInterface {
My class User must follow the rules of both of these interfaces. To make one interface that extended both of these would be messy and provide no real benefit, which is why I just used the comma syntax to implement both interfaces.
Interface Constants
So far we’ve only discussed methods in an interface. Interfaces can also define class constants. Keep in mind that when implementing or inheriting an interface, the value of its constants cannot be changed.
Interface constants are not a feature I see used often. One use for interface constants is for defining a plugin’s slug. WordPress developers, myself included, often use class inheritance to define a plugin slug property once and use it as JavaScript or CSS handles and the menu page slug.
Reducing repetitive code and having only one place where a change has to be made is great. But, alone, this is not a great reason to extend a base class. An interface can solve the same problem without creating the need to follow a class inheritance hierarchy that is otherwise unnecessary. For example:
interface admin { const SLUG = 'my-plugin' }
Should You Use Object Interfaces?
One of the major benefits of interfaces is that they add rules to your program that might otherwise be enforced by convention. In my mind, interfaces are like database abstractions. Neither are necessary, but the sooner you add one the more manageable your project is and the easier it will be to change how it works.
WordPress plugins are often written with the idea that add-on plugins will be needed. Too often we have conventions for what those add-ons should do and what hooks they should use — but those are just conventions. The core plugin does not really know how the add-ons work. If we require add-ons to use certain interfaces — using class_impliments() — then the core plugin will know how the add-on operates.
For some types of plugins, calling add-on classes instead of using hooks might be a better solution, but without knowing how they work, it’s impossible. Interfaces make this possible.
Another reason to use interfaces is to describe a workflow that everyone on your project must follow. As opposed to writing a convention, which must be enforced through careful code reviews, a set of rules defined in an interface must be followed or a fatal error will occur.
So, should you use object interfaces? The answer is yes, however, don’t use them just because it’s “better OOP” or to make you feel more sophisticated. Instead, you should use interfaces because they make your code more manageable, easier to read, and because they solve a real problem.
No Comments