Backend Development

The Covariance and Contra-variance Between Objects in PHP

The Covariance and Contra-variance Between Objects in PHP

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:

 

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.

0 0 votes
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
0
Confused
0

You may also like

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments