PHP Interfaces
Lots of developers struggle to grasp the concept of the "interface" in PHP. But once they get the idea and understand how it works, they then struggle with a new problem: writing interfaces that are too complicated.
This might seem like a contradiction in terms: after all, we want to create comprehensive interfaces that describe the behaviour of a particular object type. And since we don't generally want to extend the interface, shouldn't the interface we write be complete?
Yes, and no. Let me explain.
<?php
interface MyDBObject {
public function connect($host, $db, $user, $pass);
public function query($query, array $params);
public function prepare($query);
public function beginTransaction();
public function commit();
public function rollback();
}
This interface looks good: it handles the connection and the connection-level functionality without adding in query-specific behaviours. That's what we want.
But then let's pose the question: what happens when you encounter a database that doesn't support transactions?
Most modern databases are ACID compliant. But some still aren't. And what happens when you have to implement one of those?
Their answer is often to simply leave the transaction-aware methods blank. But this is a problem, because the Interface Segregation Principle says that no object should be required to implement a method it doesn't use.
Oops.
So, to solve this problem we can break this interface into *two separate interfaces*:
<?php
interface MyDBObject {
public function connect($host, $db, $user, $pass);
public function query($query, array $params);
public function prepare($query);
}
interface TransactionAware extends MyDBObject {
public function beginTransaction();
public function commit();
public function rollback();
}
There, now we have two interfaces we can use. Great! This lets us implement only those methods we need for older, non-ACID compliant databases, while giving us the methods we need for modern database applications.
Let me explain.
The Imagick library in PHP has a single object that has literally hundreds of public methods. The object interface is huge.
This is a classic "God" object. The object is enormous, and probably does far more than it should.
My general rule of thumb is that an object should have no more than six to eight public methods or it is probably overreaching. Of course, this is not a hard and fast rule: there are many objects with a single role that have ten or twelve methods. But it is a good rule for the start of the evaluation process, and for taking a hard look at your object.
This might seem like a contradiction in terms: after all, we want to create comprehensive interfaces that describe the behaviour of a particular object type. And since we don't generally want to extend the interface, shouldn't the interface we write be complete?
Yes, and no. Let me explain.
The overbearing interface
Let's take a look at a common database interface that many of developers come up with when they're tasked with writing a database layer:<?php
interface MyDBObject {
public function connect($host, $db, $user, $pass);
public function query($query, array $params);
public function prepare($query);
public function beginTransaction();
public function commit();
public function rollback();
}
This interface looks good: it handles the connection and the connection-level functionality without adding in query-specific behaviours. That's what we want.
But then let's pose the question: what happens when you encounter a database that doesn't support transactions?
Most modern databases are ACID compliant. But some still aren't. And what happens when you have to implement one of those?
Their answer is often to simply leave the transaction-aware methods blank. But this is a problem, because the Interface Segregation Principle says that no object should be required to implement a method it doesn't use.
Oops.
Fixing the interface
PHP doesn't require that we use a single interface for each object we create. In fact, the beauty of PHP interfaces is that we can implement lots of them, even though we don't have the ability to do multiple inheritance.So, to solve this problem we can break this interface into *two separate interfaces*:
<?php
interface MyDBObject {
public function connect($host, $db, $user, $pass);
public function query($query, array $params);
public function prepare($query);
}
interface TransactionAware extends MyDBObject {
public function beginTransaction();
public function commit();
public function rollback();
}
There, now we have two interfaces we can use. Great! This lets us implement only those methods we need for older, non-ACID compliant databases, while giving us the methods we need for modern database applications.
Avoiding "God" objects
Another common error is the creation of an interface that does *everything*. Even if a behaviour is in scope for a particular object, that doesn't mean we should necessarily implement it in this object.Let me explain.
The Imagick library in PHP has a single object that has literally hundreds of public methods. The object interface is huge.
This is a classic "God" object. The object is enormous, and probably does far more than it should.
My general rule of thumb is that an object should have no more than six to eight public methods or it is probably overreaching. Of course, this is not a hard and fast rule: there are many objects with a single role that have ten or twelve methods. But it is a good rule for the start of the evaluation process, and for taking a hard look at your object.
Comments
Post a Comment