In object-oriented PHP, inheritance is the ability to create a class that extends another class and takes on some or all of its features. This is one of the most useful functions of object orient programming in PHP (OOP PHP). I’ve skimmed over it in several of my recent articles on OOP in PHP, but in this article, I want to take a deeper look at class inheritance, how to use it, and why it’s important.
Class Inheritance
The first time I tried to write a really big WordPress plugin, I had four main classes that dealt with four different post types. These four classes had some differences but had several similarities created by writing it in one class and then copying and pasting it into the other two.
Although it seemed to be working at the time, I then realized that I made some mistakes that were literally copied and pasted into four different places. To address the issues, I had to make the change four times. And, if any future change was necessary it would require a change in four places. That was quantifiably bad.
Luckily for me, I had just read Carl Alexander‘s articles on inheritance and on abstract classes, which showed how to do this right. This article provides a basic introduction to class inheritance in object-oriented PHP, which will equip you to write better, more reusable code and be able to customize other people’s code more efficiently.
When a plugin or library gives you a class that is almost perfect, instead of copying it and changing one method, you can extend that class and override the method. When you have two classes that are almost identical, you can write a base class and then extend it twice.
Extending Classes And Overriding
A class extends another by using the “extends” keyword in its declaration. If we wanted to extend WP_Query, we would start our class with “product_query extends WP_Query.” Any class can be extended unless it is declared with the final keyword.
When a class is extended, we consider it to be the parent class and the class that is extending to be the subclass. Using class inheritance properly requires a strong understanding of property and method visibility, which I covered in depth here.
A subclass starts the same as a parent class. For example, if we just wrote this for our product_query class, it would be the same as WP_Query:
product_query extends WP_Query{ }
Of course, that doesn’t accomplish anything. On the other hand, if we were working on a project that needed a lot of queries for posts in the product post type, we could simplify things with this class:
class product_query extends WP_Query { public function __construct( $query ){ $query[ 'post_type' ] = 'product'; parent::__construct( $query ); } }
This class follows all of the same rules of WP_Query, but we don’t have to keep telling it that we just want product posts. Also, it is clear that the objects created with it are for product posts. We could add more customization in the construct.
Note that in my extended class, I made a function called __construct() and that WP_Query also declares a constructor. Any method — magic or not — can be overridden in a subclass, but you can still access the method of the parent class using the “parent” keyword.
Keep in mind that in PHP5 overriding a method and changing its signature — changing its parameters or their types — will trigger a strict standards notice and in PHP7 will trigger a warning. You should not do this as it makes the code more difficult to read.
If you are using return type declarations in PHP7, don’t change the return type of a method when overriding it in a subclass or a fatal error will occur. PHP5 does not support return type declaration.
A good example of class inheritance is the classes WP_HTTP_Response, which is in the wp-includes/rest-api directory, but could be used for any type of HTTP response. It is extended by the WP_REST_Response class, which is specifically used when responding to REST API requests.
Abstract Classes
Unless a class is declared as final it can still be extended. PHP provides an abstract convention for classes that can not be instantiated directly and acts only to provide base classes for other classes that extend them.
The other special rule of an abstract class is that they can have abstract methods, which must be overridden by the class that extends them or an error will occur. This is a really useful system because it lets you define how the subclasses will function.
Abstract methods cannot have a body, and the methods that override them must not alter their signature. If you try to change the parameters or types of parameters in PHP5 or in PHP7, a fatal error will occur.
This is slightly different than overriding non-abstract methods, though in practice it’s the same since you should never change the signature of a method even if you technically can in PHP5 with strict standards disabled.
Interfaces provide a similar role, but the methods of an interface must be public whereas abstract methods can be public, protected, or private. Methods overriding abstract methods must also use the same signature.
The WordPress REST API’s WP_REST_Controller is a great example of an abstract class. It is extended by all of the endpoints to provide a standard system of how endpoint classes should work.
Note that it does not use abstract methods because abstract methods are not supported in older versions of PHP. Instead, the base class has methods that call __doing_it_wrong(). This is a workaround to force those methods to be extended.
For another example, let’s consider a plugin that connects to social networks via oAuth and then stores public and secret keys for later use. Since the actual fetching of keys will be different we need different code for that, but that code can be reused to store and get the keys. So we could write an abstract method called “connect” that handles everything but getting the keys, which would happen in an abstract method.
abstract class social { protected $public; protected $secret; public function __construct( $public, $secret ){ $this->public = $public; $this->secret = $secret; } abstract public function connect(); public function get_public(){ return $this->public; } public function get_secret(){ return $this->secret; } }
This reduces code redundancy and enforces a particular pattern in the code, where all of the classes that extend this class have the same method for getting the keys, and could safely call that method of every subclass.
For example, we could make a class called “twitter,” and one called “facebook” that extended these classes. Both would just need a method called connect.
With the connect method in place, we can assume its existence and use two classes or more to do the same thing, like this:
class facebook extends social{ public function connect(){ //do some things to get public secret keys and set in the right properties } } class twitter extends social{ public function connect(){ //do some things to get public secret keys and set in the right properties } } foreach( ['twitter', 'facebook' ] as $social_network ){ /** @var social $obj */ $obj = new $social_network; $obj->connect(); update_option( 'public_' . $social_network, $obj->get_public() ); update_option( 'secret_' . $social_network, $obj->get_secret() ); }
Less Code, Better Code
This brief introduction to class inheritance in PHP should help you write smaller classes with more reusable code. Reusable code isn’t just more efficient, it is easier to read, easier to maintain, and requires fewer unit tests.
There are several uses for class inheritance, which you should really try to explore. Instead of reusing WP_Query over and over again, you could write a base class that calls WP_Query and then extends it a few times to fit your needs.
If you are building a plugin that needs a custom REST API endpoint, for example, you can extend the WP_REST_Controller class, which will save you a lot of work.
If you are working on a site that interacts with multiple transactional email services, write a base class that handles the common functionality that all interactions with the APIs of those transactional email services need and extend it once per service.
I could go on with examples, but you should look at your existing code and at plans for projects and think about how class inheritance could help you skip the copy/paste method and improve your code.
3 Comments