Legacy Code and Teams Series: Composer

After talking about Technical Committee and Training Sessions in the previous post of the series, today, I’ll carry on with adding Composer to legacy projects without pain.

The day I left Emagister, I was sat down with Eber and Christian having beers and talking about all the cool things we did there. We were reviewing each of the changes, it cost, benefits, feelings in the team, etc. When I said, “what about composer? that was cool!”, Eber said, “Composer is not just cool, it’s a must.”. As always, he was too right. It has been also the first thing done at Atrápalo.

Starting with a brand-new project using create-project command it’s easy, but, what about those developers dealing with a big legacy project, with custom autoloader, PEAR dependencies, hardcoded fixes in a external library, non PSR-0 code, etc. Don’t worry, you can also take benefit of Composer. However, there are some tricks you should take care of in order to face those issues.

My code does not follow PSR-0

The autoloading options for Composer are really well defined in its documentation. People just don’t read them. If your code follows PSR-0 or PSR-4, you have to define in your composer.json the “psr-0” or “psr-4” directive and specified what prefixes point to what directories. Easy.

But, if your code does not follow any of those standards, you can always use the “classmap” option.

{
    "autoload": {
        "classmap": ["src/", "lib/", "Something.php"]
    }
}

When running composer install or composer update, it will go through all the folders and files identifying any class definition. It will generate a file ./vendor/composer/autoload_classmap.php with an array with all the class names and their corresponding path files. So, when you require a file, composer goes to this dictionary, find the class by name and requires the file associated with it.

Cons: What it’s annoying is that if any developer in your team adds a new class in those folders, you will need to run composer dump-autoload in order to regenerate the classmap. If you want to update the classmap automatically, you can add a hook in your Composer, but it’s an approach I just don’t like.

I already have a custom autoloader

You may have an existing autoloader that you want to preserve. However, if the previous autoloader trick does not fix your situation, you really have something strange such as classes defined with the same name in different files you load dynamically. My suggestion is to use just one autoloader. So, while you move to composer ;) you may need to live with both autoloaders in your application. Is there an option to do it? Of course! You can achieve this mixing your current  “spl_auto_register” and composer’s autoload require famous code line:

<?php
// Somewhere in your bootstrap file
spl_auto_register(['MyCustomAutoloader', 'load']);
require __DIR__ . '/../../vendor/autoload.php'; 
//...

If you need to live with both autoloaders, you may need to run your autoloader before Composer’s. In order to do it, you will have to take a look to “prepend-autoloader” Composer option. By default, Composer will always prepend its autoloader to any others registered.

“prepend-autoloader: Defaults to true. If false, the composer autoloader will not be prepended to existing autoloaders. This is sometimes required to fix interoperability issues with other autoloaders.”

The reason, take a look to /vendor/composer/ClassLoader.php:

/**
 * Registers this instance as an autoloader.
 *
 * @param bool $prepend Whether to prepend the autoloader or not
 */
public function register($prepend = false)
{
    spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}

So probably, you will need to play with this configuration if you need to tune what are the order you need to make your autoloader and Composer’s live together. Tricks apart, it’s possible to mix them, so don’t worry. Do it while you move to composer.

I have dependencies directly in my code

For each library you have in your project, find it in Packagist. If it’s there, great! just define the library in your require configuration section. Everything is explain in Composer doc. If the library is in other place rather than Packagist, you can define your own repository in Composer and require your library normally. Let’s see an example.

If you want to import the Google Analytics API library, you’ll need to define your own repository.

"repositories": [
    {
        "type": "package",
        "package": {
            "name": "gapi/gapi",
            "version": "1.3.0",
            "dist": {
                "url": "https://gapi-google-analytics-php-interface.googlecode.com/files/gapi-1.3.zip",
                "type": "zip"
            },
            "source": {
                "url": "http://gapi-google-analytics-php-interface.googlecode.com/svn",
                "type": "svn",
                "reference": "trunk/"
            },
            "autoload": {
                "files": [
                    "gapi.class.php"
                ]
            }
        }
    },
    ...
],
"require": {
    "gapi/gapi": "1.3.0",
    ...

For more information, just take a look to how to create a custom repository Composer documentation.

I have some dependencies that are not in Packagist

If some libraries are not there, you can just create your own library in Packagist. Register your account, put your code in GitHub and link your project. I’m sure you are not the only one that needs that library in Packagist. Community power!

I have some dependencies I cannot update due to own modifications

Bad, bad, bad. This is something you should never do. Try always to extend a library but not to modify it directly. You may get stuck in that version and updates may be imposible to apply. So, the options you have are: move your version to Packagist as the previous tip, try to use the official Packagist version extending the library you are using or take your modifications and make a pull request to library’s owner.

I still use some PEAR libraries

Some of the most typical PEAR libraries have been ported to Packagist. Just search for “pear”.

Sum up

Just use Composer, seriously. More tips are welcome.