The relation between parent and child class methods in OOP is often referred to by terms Covariance and Contra-variance, let’s talk about this topic in this article.
When speaking about Covariance and Contra-variance in PHP we speak in the context of classes inheritance as both terms represent the relation between parent and child class methods returns types and parameter types.
These terms comes with PHP 7.2 with partial contra-variance support and by the release of PHP 7.4 the full support of Covariance and Contra-variance full support is added
Covariance
In simple words the Covariance is about class method return types which stats that the child class method return type can be more specific than the parent class method return type (return type of the methods can be narrowed).
Simple example for description:
<?php abstract class DB { abstract public function query(); } class Mysql extends DB { public function query() { echo "Processing mysql query"; } } class Oracle extends DB { public function query() { echo "Processing oracle query"; } }
In this code i have an abstract class DB which contains one abstract method query(). The DB::query() method should contain implementation details for executing query depending on db type but for simplicity we echo an statement in each class that extends from DB.
The Mysql and Oracle classes extend from the abstract class DB and override the query() method.
Let’s add another interface called DBEngine:
interface DBEngine { public function process(): DB; } class MysqlEngine implements DBEngine { public function process() : Mysql // instead of returning type DB it return type Mysql { return new Mysql(); } } class OracleEngine implements DBEngine { public function process() : Oracle { return new Oracle(); } }
The DBEngine interface makes use of the DB class and contains a method process() which return an instance of type DB. In other means the DBEngine delegates the calls to the DB class.
Next i created two implementations for the DBEngine interface which is MysqlEngine and OracleEngine.
The point here if you notice that the process() method in the DBEngine interface it return a type of class DB. However in the implementations class it return Mysql for MysqlEngine and Oracle for OracleEngine respectively:
interface DBEngine { public function process(): DB; // ----- > parent class }
class MysqlEngine implements DBEngine { public function process() : Mysql // --------> return specific type Mysql from DB { return new Mysql(); } } class OracleEngine implements DBEngine { public function process() : Oracle // return specific type Oracle from DB { return new Oracle(); } }
And this covariance, we returned specific type in child class methods than the parent class method.
According to this example here are some cases that the type can be more specific:
- Iterable is changed to array or traversable
- A type is removed from union type
- A class type is changed to a child class type
- A type is added to an intersection type.
If you know about the SOLID Principal (Liskov Substitution Principal) LSP you may guess that the covariance is compatible with LSP. LSP means any implementation of an abstraction or an interface should be substitutable anywhere that the abstraction is accepted.
Contra-variance
The second part of this article is the contra-variance. Contra-variance is about method parameters and it allows that a parameter type in a child method to be less specific than that of the parent.
You can think of contra-variance as the inverse of covariance but for parameter types but the covariance for the method return type.
Consider this dummy example:
class Processor { } class PDFProcessor extends Processor { }
In this code i have two dummy classes, Processor class and another class that extends from it PDFProcessor, the classes have no properties or methods just for clarification.
Next i will add two other classes that make use of the above classes via parameter injections:
class Exporter { public function print(PDFProcessor $processor) { echo "this paper exported as " . get_class($processor) . "\n"; } } class XLSExporter extends Exporter { public function print(Processor $processor) { echo "this paper exported as " . get_class($processor) . "\n"; } }
The classes Exporter and XLSExporter uses the Processor classes in the print() method. The point i want to show in the print() method of the base class Exporter accepts a parameter of type PDFProcessor while the print() method of the XLSExporter accepts a Processor type.
This is the contra-variant relationship, here the parent class method accepts the more specific parameters and the child class method accepts the less specific parameters.
You may be confused about the contra-variance as the normal case we usually learn it that the child class methods should accept the more specific parameters and you are right. The contra-variant is complicated at first glance and may be used in rare situations than covariance.