In this post we will discuss
PHP packages and
used alcohol dependencies. Rather, the so-called optional or proposed dependencies (optional dependencies, suggest / dev-dependencies), which are defined in composer.json.
What is addiction?
To begin with, let’s 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 filesystem library , which I have changed to some extent. To determine all dependencies of this piece of code, we must ask ourselves the following questions:
')
- What do you need to make this code work?
But you need to think about this:
- What version of PHP will you need to run to get no errors?
- Perhaps you will need to install some specific version?
- What PHP extensions will be needed?
- What PEAR libraries do I need to install?
- What packages are missing?
Returning to the GridFS class, the PHP version must be at least 5.3, because neimspaces are used. Also, the class
\MongoGridFS
, which is a mongo extension to PHP and is available only from version 0.9.0 of this extension. Everything seems to create composer.json:
{ ..., "require": { "php": ">=5.3", "ext-mongo": ">=0.9.0" } .. }
This list is enough and now it seems like nothing stops us from using this class in our applications ... Alas, this is not so.
Valid list of dependencies knplabs / gaufrette
As I said before, 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 the 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 turned out about dependencies earlier is no good, because the library says that it just needs PHP version not lower than 5.3.2, and everything else - to your taste, optionally or only for dev purposes - call it what you want .
Of course, people who have been using Composer or Packagist for a long time have become accustomed to such things. But this is just the wrong approach. As we found out earlier, ext-mongo is a
true dependency of the GridFS class, but composer.json tells us the opposite.
All this means is that if we want to use this class in our project, it is not enough just to use the
knplabs/gaufrette
. I also made ext-mongo necessary in my project, which is a mistake: it is not my project that requires the mongo extension, but the
knplabs/gaufrette
. Moreover, how can I find out which version of ext-mongo to choose? Those dependencies that are listed in the suggest block do not talk about it, forcing me to choose.
It's just another package.
knplabs/gaufrette
not the only one who acts in this way, presenting real dependencies as suggested. It is 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 does not work without them.
Decision
What should package developers do in this case? Separate them. In the case of
knplabs/gaufrette
this means that there must be a
knplabs/gaufrette
package containing all the common code needed to abstract from the file system. And then each individual adapter, such as the GridFS, should live in its 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 everything, nowhere there are hidden dependencies, they are all necessary.
The
knplabs/gaufrette
in turn, no longer has dependencies at all, and the packages with adapters are just proposed:
{ "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 becomes more stable. There is no reason to change anything, since all the variable parts are inside adapters.
- Different packages may have different developers. For example,
knplabs/gaufrette-mongo-gridfs
can be modified by someone who knows MongoDB very well. - Users do not need to keep track of 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 it is a valid dependency in this package? Then split the package and specify this dependency in it as necessary. If all the code in the main package works without this proposed dependency, then it can be specified as suggested.