We are privileged to use all the benefits of open source - plethora of useful frameworks and libraries that solve various kind of problems. Of course, we should be picky and use only test-covered and high-quality code libraries, hopefully with encouraging number of GitHub stars and Packagist number of downloads.

But even though some 3rd-party library you are depending on is clean and tidy, how you integrate it can impact cleanliness of your code.

Show me what you mean

For the purposes of some application, I decide to use some 3rd-party DBAL implementation, and I choose Doctrine DBAL. But instead of directly coupling my code with the Doctrine namespace and its Connection interface, I'll adapt it based on how I want to use database handler in my project.

First, I define the interface:

interface DbHandlerInterface
{
    public function fetchAll(string $sql, array $params = []) : array;

    public function fetchOne(string $sql, array $params = []) : array;

    public function insert(string $table, array $data) : int;

    public function update(string $table, array $data, array $identifier) : int;

    public function delete(string $table, array $identifier) : int;
}

And here is how concrete implementation based on Doctrine DBAL might look like:

use Doctrine\DBAL\Connection;

final class DoctrineDbHandler implements DbHandlerInterface
{
    /**
     * @var Connection
     */
    private $connection;

    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }

    public function fetchAll(string $sql, array $params = []) : array
    {
        return $this->connection->fetchAll($sql, $params);
    }

    public function fetchOne(string $sql, array $params = []) : array
    {
        return $this->connection->fetchAssoc($statement, $params);
    }

    public function insert(string $table, array $data) : int
    {
        return $this->connection->insert($table, $data);
    }

    public function update(string $table, array $data, array $identifier) : int
    {
        return $this->connection->update($table, $data, $identifier);
    }

    public function delete(string $table, array $identifier) : int
    {
        return $this->connection->delete($table, $identifier);
    }
}

This is a rather simple example in which adapting interface has only small subset of methods and also certain method parameters have been omitted. Adaptation could be much more evident and more drastic in some other example, depending on the purpose of new interface and its use cases.

So basically, when we build our domain/business logic, we want to make sure that it interacts with our own interfaces, that represent a boundary between our code and external dependencies.

Interfaces Diagram

What's the point?

There are many motives for such a move:

  • loose coupling - by having a boundary in form of well designed interfaces, you decoupled your business logic from external dependencies.
  • abstracting external dependencies - by adapting interface of a 3rd-party library according to your needs, you end up with a custom-tailored, minimalistic interface that reduces complexity introduced by the 3rd-party library.
  • less painful BC break encounters - if the author of a 3rd-party library accidentally introduces BC break in a new release, there is only one place where you need to do the patching.
  • switching to alternative solution - if you ever decide to use some different library of the same purpose, refactoring would be a breeze, because scope of changes is reduced to a single place in code.
  • easy learning curve for fellow contributors - it is much easier for a new developer joining your team to comprehend some simplified interface, designed for the purpose of a project he will be working on, rather than having to study some complex interface, many of which methods are not used at all.

So those are all benefits, and I really cannot point out any meaningful shortcomings in relation with this approach. If you have some, I encourage you to drop some comment.

Exceptions

Still, there are situations when I turn away from this principle. Many libraries have adopted PSR interfaces proposed by FIG. PSR stands for PHP Standard Recommendation, and those standards are proposed/accepted by some of the most prominent members and projects of the PHP community. I treat those standards as part of the PHP language itself and I omit my principle regarding 3rd-party code in their case.

Another example is when interface of some dependency is simple and abstract enough by itself. For example, if I strictly kept with my principle in case of Zend\Filter\FilterInterface, I would end up writing a FilterInterface in my namespace that extends it, and moreover, in order to be able to use concrete filters from the Zend\Filter namespace in my code, I would have to create a proxy that wraps some filter instance through my interface. That would be an overhead resulting in unnecessary level of complexity.

Conclusion

Using 3rd-party libraries comes with a cost of coupling, but that cost can be reduced to a bare minimum by using techniques and practices described in this article.