Constructors and Breaking the Liskov Substitution Principle
At the risk of being targeted by the PHP hate-mongers, I must confess that I’m pretty comfortable with PHP’s object model. I’m not that naïve to claim it’s a full-blown model exposing all the bells and whistles of other “fatter” players, such as C++ and Java, but despite of all its quirks, PHP delivers what it promises at face value. I would say however that the model is notoriously permissive considering the relaxed constraints it imposes when it comes to defining userland interfaces.
One issue with the model’s “forgiveness” is that one fine day you may wake up in a whimsical mood and specify a constructor inside an interface just because you can, without a twinge of guilt. If you think this through, the existence of a constructor as part of an interface is pretty pointless. First off, every time you define a contract, you’re actually abstracting away the behavior of the concrete implementations and leaving out of the picture how they must be assembled down the road. Second, how in the world you can get client code consuming a constructor once the implementers have been instantiated? There’s no parallel universe in which you can do such things unless without sloppily polluting consumers with a bunch of new operators, which should live and breathe somewhere else according to the “isolate application logic from object construction” mantra.
Rants aside, the morale of the story can be boiled down to the following: “Object construction is not part of the contract honored by its implementers”.
It’s easier to grasp concepts by example rather than reading dull theory, so in this article I’ll be demonstrating from a practical standpoint how the implementation of different constructors down the same hierarchy isn’t a violation of the Liskov Substitution Principle, which is a reason why you shouldn’t fall into the temptation of tainting your interfaces with constructors.
The Myth of LSP Breakage
I guess I’ve already made a valid point with regard to avoiding constructors in interfaces, a process that certainly helps to generate contracts that describe and prescribe the behavior of certain object types (a.k.a. Subtype Polymorphism) but not how to create the objects in question. It might be incidentally harder, however, to understand why overriding a parent constructor in a subclass, or eventually implementing a brand new one doesn’t violates the LSP.
Let’s say we need to create a PDO adapter just by hanging a lightweight subclass from the native PDO. The adapter’s contract, along with the corresponding implementation, could look as follows:
<?php
namespace LibraryDatabase;
interface DatabaseAdapterInterface
{
public function executeQuery($sql, array $parameters = array());
public function select($table, array $bind, $operator = "AND");
public function insert($table, array $bind);
public function update($table, array $bind, $where = "");
public function delete($table, $where = "");
}
<?php
namespace LibraryDatabase;
class PdoAdapter extends PDO implements DatabaseAdapterInterface
{
private $statement;
public function __construct($dsn, $username = null, $password = null, array $options = array()) {
if (!extension_loaded("pdo")) {
throw new InvalidArgumentException(
"The adapter needs the PDO extension to be loaded.");
}
if (!is_string($dsn) || empty($dsn)) {
throw new InvalidArgumentException("The DSN is invalid.");
}
parent::__construct($dsn, $username, $password, $options);
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
// Prepare and execute an SQL statement
public function executeQuery($sql, array $parameters = array()) {
try {
$this->statement = $this->prepare($sql);
$this->statement->execute($parameters);
$this->statement->setFetchMode(PDO::FETCH_OBJ);
return $this->statement;
}
catch (PDOException $e) {
throw new RunTimeException($e->getMessage());
}
}
// Prepare and execute a SELECT statement
public function select($table, array $bind = array(), $operator = "AND") {
if ($bind) {
$where = array();
foreach ($bind as $col => $value) {
unset($bind[$col]);
$bind[":" . $col] = $value;
$where[] = $col . " = :" . $col;
}
}
$sql = "SELECT * FROM " . $table
. (($bind) ? " WHERE "
. implode(" " . $operator . " ", $where) : " ");
return $this->executeQuery($sql, $bind);
}
// Prepare and execute an INSERT statement
public function insert($table, array $bind) {
$cols = implode(", ", array_keys($bind));
$values = implode(", :", array_keys($bind));
foreach ($bind as $col => $value) {
unset($bind[$col]);
$bind[":" . $col] = $value;
}
$sql = "INSERT INTO " . $table
. " (" . $cols . ") VALUES (:" . $values . ")";
$this->executeQuery($sql, $bind);
return $this->lastInsertId();
}
// Prepare and execute an UPDATE statement
public function update($table, array $bind, $where = "") {
$set = array();
foreach ($bind as $col => $value) {
unset($bind[$col]);
$bind[":" . $col] = $value;
$set[] = $col . " = :" . $col;
}
$sql = "UPDATE " . $table . " SET " . implode(", ", $set)
. (($where) ? " WHERE " . $where : " ");
return $this->executeQuery($sql, $bind)->rowCount();
}
// Prepare and execute a DELETE statement
public function delete($table, $where = "") {
$sql = "DELETE FROM " . $table
. (($where) ? " WHERE " . $where : " ");
return $this->executeQuery($sql)->rowCount();
}
}
The batch of tasks performed by the PdoAdapter
class are indeed straightforward, limited to running a few prepared queries via its executeQuery()
method and shooting some CRUD operations against a given table. The most relevant detail to highlight here is how the adapter overrides its parent’s constructor in order to do some checks and see if the PDO extension has been installed before starting consuming it.
Here’s how we’d get the adapter up and running:
<?php
use LibraryLoaderAutoloader,
LibraryDatabasePdoAdapter;
require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader;
$autoloader->register();
$adapter = new PdoAdapter("mysql:dbname=test", "myusername", "mypassword");
$stmt = $adapter->prepare("SELECT * FROM users");
$stmt->execute();
$users = $stmt->fetchAll(PDO::FETCH_OBJ);
foreach ($users as $user) {
echo $user->name . " " . $user->email . "<br>";
}
I’m intentionally avoiding the adapter’s executeQuery()
method and instead appealing to the native execute()
method to pull in a few users from the database. At first blush this all seems to be pretty boring boilerplate stuff that should be skipped over for more interesting things… but bear with me because indeed there’s a tiny catch here worth stressing. Even considering that the adapter effectively implements a different constructor from the default one provided by PDO, the contract it agrees with client code is neatly maintained from top to bottom. In short, this means that having disparate constructor implementations in a base type and in a subtype by no means infringes the LSP substitutability rules as long as the contract exposed by the two classes is properly honored.
Logically, this can be seen more clearly if we switch the previous script over to a native PDO instance rather than consuming the adapter:
<?php
$adapter = new PDO("mysql:dbname=test", "myusername", "mypassword");
$stmt = $adapter->prepare("SELECT * FROM users");
$stmt->execute();
$users = $stmt->fetchAll(PDO::FETCH_OBJ);
foreach ($users as $user) {
echo $user->name . " " . $user->email . "<br>";
}
The fact of having a hierarchy of classes, even a very elemental one, where the subtypes are constructed differently than the base types has literally nothing to do with breaking the desired substitutability that’s actively promoted by the LSP.
You might argue that the signatures of the adapter and the native PDO look pretty much the same, and that’s why substitutability is in this case preserved so well. But it’s really easy to refute an argument like this. For instance, I could take a more pragmatic approach and refactor the adapter’s constructor so that it can accept an array of named connection arguments:
<?php
namespace LibraryDatabase;
class PdoAdapter extends PDO implements DatabaseAdapterInterface
{
private $statement;
public function __construct(array $config = array()) {
if (!extension_loaded("pdo")) {
throw new InvalidArgumentException(
"The adapter needs the PDO extension to be loaded.");
}
if (!isset($config["dsn"]) || !is_string($config["dsn"])) {
throw new InvalidArgumentException(
"The DSN has not been specified or is invalid.");
}
$username = $password = $options = null;
extract($config);
parent::__construct($dsn, $username, $password, $options);
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
// the same implementation goes here
}
At this point the adapter’s constructor is a pretty different creature whose signature and implementation neatly accommodate an array of connection parameters instead of digesting plain scalars. With such a drastic refactoring in place, one might take for granted that isn’t possible to swap out at runtime an instance of the adapter with a native PDO one anymore. Rush judgments tend to be dangerous, something that the following snippet puts in evidence:
<?php
$adapter = new PdoAdapter(array(
"dsn" => "mysql:dbname=test",
"username" => "myusername",
"password" => "mypassword"
));
$stmt = $adapter->prepare("SELECT * FROM users");
$stmt->execute();
$users = $stmt->fetchAll(PDO::FETCH_OBJ);
foreach ($users as $user) {
echo $user->name . " " . $user->email . "<br>";
}
Even though instantiating an instance of the adapter is now quite a different process, the client code remains entirely agnostic about this change in the contract it consumes remains the same!
Still, if you feel a pinch of skepticism and think the example falls short when it comes to demonstrating the interchangeability between types of a given hierarchy, look at the following which refactors even more drastically the constructor and supplies the connection arguments via a naive DTO (Data Transfer Object):
<?php
namespace LibraryDatabase;
interface ConnectionDefinitionInterface
{
public function getDsn();
public function getUserName();
public function getPassword();
public function getOptions();
}
<?php
namespace LibraryDatabase;
class ConnectionDefinition implements ConnectionDefinitionInterface
{
private $dsn;
private $username;
private $password;
private $options = array();
public function __construct($dsn, $username = null, $password = null, array $options = array()) {
if (!is_string($dsn) || empty($dsn)) {
throw new InvalidArgumentException("The DSN is invalid.");
}
$this->dsn = $dsn;
$this->username = $username;
$this->password = $password;
$this->options = $options;
}
public function getDsn() {
return $this->dsn;
}
public function getUserName() {
return $this->username;
}
public function getPassword() {
return $this->password;
}
public function getOptions() {
return $this->options;
}
}
<?php
namespace LibraryDatabase;
class PdoAdapter extends PDO implements DatabaseAdapterInterface
{
private $statement;
public function __construct(ConnectionDefinitionInterface $connectionDefinition) {
if (!extension_loaded("pdo")) {
throw new InvalidArgumentException(
"The adapter needs the PDO extension to be loaded.");
}
parent::__construct(
$connectionDefinition->getDsn(),
$connectionDefinition->getUserName(),
$connectionDefinition->getPassword(),
$connectionDefinition->getOptions()
);
$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
// the same implementation goes here
}
While it’s fair to admit that DTOs don’t have a prolific existence in the PHP world (though some popular frameworks like Zend Framework 2 and Simfony 2 use them ubiquitously), in this case I appealed to a simple one to pass a set of connection arguments to the adapter’s constructor. The radical change, for obvious reasons, is going to ripple artifacts toward the code responsible for instantiating the adapter, be it a DIC, a low-level factory, or any other independent mechanism along that line that doesn’t interfere explicitly with application logic:
<?php
$adapter = new PdoAdapter(
new ConnectionDefinition("mysql:dbname=test", "myusername", "mypassword")
);
As long as the adapter’s contract remains untouched along the way, it’s possible to interchange an instance of the adapter with a PDO one and client code won’t pitch a single compliant.
Though this example might be somewhat overkill when it comes to pushing PDO functionality inside the boundaries of a slim wrapper, it does an excellent job at showing the flagrant LSP breakages that are easily found in the wild occur because of other reasons, which I dare to claim aren’t even related to the “flaws” of the base type/subtype construction schema.
Closing Remarks
The mantra has been said over and over, but it’s worth stressing it once again: providing a certain programming principle or pattern with a dogmatic connotation which must be blindly fulfilled is something almost as harmful as not using those patterns and principles at all, hence letting the chaos of pragmatism dramatically rot the quality of a system.
Aside from attempting to demystify constructors as one of the root causes of potential LSP infringements, my argument against including constructors in interfaces certainly isn’t meant to be dogma. But being aware of the consequences of this sort of “construction-restrictive” contracts just because PHP’s object model is relaxed in that aspect might help to mitigate the proliferation of this inflexible, pointless practice.
Image via Fotolia