Optional dependencies not needed

Original author: Matthias Noback
  • Transfer
This post will be about PHP packages and alcohol addictions. Rather, about the so-called optional or proposed dependencies (optional dependencies, suggest / dev-dependencies), which are defined in composer.json.

What is addiction?


To begin with, we will deal with what dependence is and what it is all about. There is the following code:

namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use \MongoGridFS;
class GridFS implements Adapter
{
    private $gridFS;
    public function __construct(MongoGridFS $gridFS)
    {
        $this->gridFS = $gridFS;
    }
    public function read($key)
    {
        $file = $this->find($key);
        return ($file) ? $file->getBytes() : false;
    }
}

The GridFS class is part of the Gaufrette abstract file system library , which I have modified to some extent. To determine all the dependencies of this piece of code, we must ask ourselves the following questions:

  • What does this code need to work?


But one more thing to think about:

  1. What version of PHP will you need to run in order not to get errors?
  2. You may also need to put some specific version?
  3. What PHP extensions are needed?
  4. What PEAR libraries do you need to install?
  5. What packages are missing?

Returning to the GridFS class, the PHP version should be at least 5.3, because namespaces are used. Also, a class is required \MongoGridFS, which is a mongo extension to PHP and is available only from version 0.9.0 of this extension. Everything seems to be creating composer.json:

{
    ...,
    "require": {
        "php": ">=5.3",
        "ext-mongo": ">=0.9.0"
    }
    ..
}

This list is enough and now it seems like nothing is stopping us from using this class in our applications ... Alas, this is not so.

Valid knplabs / gaufrette dependency list


As I said, GridFS is part of the Gaufrette library, which provides an abstract file system layer for storing files on various types of file systems without worrying about the details of the file system used. Take a look at composer.json of this library:

{
    "name": "knplabs/gaufrette",
    "require": {
        "php": ">=5.3.2"
    },
    "require-dev": {
        ...
    },
    "suggest": {
        ...
        "amazonwebservices/aws-sdk-for-php": "to use the legacy Amazon S3 adapters",
        "phpseclib/phpseclib": "to use the SFTP",
        "doctrine/dbal": "to use the Doctrine DBAL adapter",
        "microsoft/windowsazure": "to use Microsoft Azure Blob Storage adapter",
        "ext-zip": "to use the Zip adapter",
        "ext-apc": "to use the APC adapter",
        "ext-curl": "*",
        "ext-mbstring": "*",
        "ext-mongo": "*",
        "ext-fileinfo": "*"
    },
    ...
}

And here he is, the first surprise! Almost everything that was found out about dependencies earlier is worthless, because the library says that it just needs PHP version no lower than 5.3.2, and everything else - to taste, optional or only for dev purposes - call it what you want .

Of course, people who have been using Composer or Packagist for a long time are already used to such things. But this is just the wrong approach. As we explained earlier, ext-mongo is a real relationship ( to true the dependency) class GridFS, however composer.json tells us otherwise.

All this means only that if we want to use this class in our project, it’s not enough just to use the packageknplabs/gaufrette. I also made ext-mongo necessary in my project, which is a mistake: this is not my project that requires the mongo extension, but a package knplabs/gaufrette. Moreover, how do I know which version of ext-mongo should I choose? Those dependencies that are indicated in the suggest block do not talk about this, forcing me to choose it.

It's just a different package.


knplabs/gaufrettenot the only one who acts in this way, passing off real dependencies as proposed. It’s convenient for package owners to add different classes that users may need. Or not needed. Therefore, if the use of these classes is optional, then their dependencies are also optional. However, package owners forget that dependencies are never optional. They are always required, because the code simply won’t work without them.

Decision


What should package developers do in this case? Separate them. In the case of knplabs/gaufrettethis, it means that there should be a package knplabs/gaufrettecontaining all the common code needed to abstract from the file system. And then every single adapter, such as GridFS, must live in a package, for example knplabs/gaufrette-mongo-gridfs. And he will already have his dependencies:

{
    ...,
    "require": {
        "php": ">=5.3",
        "knplabs/gaufrette": "~0.1"
        "ext-mongo": ">=0.9.0"
    }
}

And that's all, there are no hidden dependencies anywhere, all of them are necessary.

Himself, knplabs/gaufrettein turn, no longer has any dependencies at all, and packages with adapters are just the ones offered:

{
    "require": {
        "php": ">=5.3.2"
    },
    "suggested": {
        "knplabs/gaufrette-mongo-gridfs": "For storing files using Mongo GridFS",
        ...
    }
}

This approach has a number of advantages:

  • The main package is becoming more stable. There is no reason to change anything, since all the changeable parts are inside the adapters.
  • Different packages may have different developers. For example, knplabs/gaufrette-mongo-gridfssomeone who knows MongoDB very well can modify it.
  • Users do not need to monitor updates to parts of the library that they do not use.
  • Users will not have to manually add additional dependencies to their projects.

Next time, adding the proposed dependencies to your package, think about whether this dependency is valid in this package? Then separate the package and specify this dependency in it as necessary. If all the code in the main package works without this proposed dependency, then in this case it can be indicated as proposed.

Also popular now: