Using Composer on legacy projects

Composer LogoIt’s been a long time since my last post. But now, I’m back with something probably useful for you. Composer is around for a while but I was not able to find some time to work with it. We needed something like this in the company so I spent some time with it and moved our project to Composer enabled dependencies. You can (and should) definitely do the same.

If you are not aware what Composer is, check the http://getcomposer.org. It will give you an idea. Are you back? Cool, now we can continue. Composer is a Maven/Aptitude/Yum like package/dependency manager for PHP. You can define your own packages and dependencies, without including them in your code base or in your include_path. When you run a composer install command, they are loaded automatically and installed with their own autoloader etc.

The best part is that it’s still the good old PHP with all your dependencies and libraries inside a “vendor” folder. You can find many next generation PHP frameworks like Symfony 2 using it extensively. So everything is easy if you upload your packages and dependencies to the Packagist, the Composer package repository, everything else is handled smoothly. But what if you already have a legacy code base that is somewhat “modular” but does not have anything similar to the Composer? Things get a little bit complicated then.

What I did was to figure out how to use Composer and it’s packages without requiring to change a lot of things in my code base. We have ZF 1.x project that has many modules inside the app/modules folder. I suppose it’s a similar setup for most ZF 1.x users, at least for those who read the documentation and my blog :). When we move those modules to the vendor folder, things might not go as expected. So one of the first problems was the ability to link modules to the app/modules folder. But even before that, your modules should be valid composer packages.

So what should we do to convert our modules into composer packages? It’s pretty straightforward, just add a composer.json file and fill in the blanks. Something like this will suffice:

{
    "name": "kartaca/codebase-custom",
    "description": "My Custom module",
    "authors": [
        {
            "name": "Roy Simkes",
            "email": "myemail@email.com"
        }
    ],
    "minimum-stability": "stable",
    "require": {}
}

You can also use the composer create-project command and it will create you the skeleton.

However this won’t be enough in using them with the composer. As our code base is private we need to be able to show them to our main package by another way. So once we create composer.json files, we need to move those to their own locations in the repository (if you haven’t done such a thing previously of course). Just create a modules directory in your code repository and it will probably be enough. Then you will need to move those modules to their, from the actual code base. Once this is done, now you need to define them in your code base as a dependency You need such a composer.json:

{
    "name": "kartaca/codebase",
    "description": "My custom Code Project or whatever you would like to call",
    "authors": [
        {
            "name": "Roy Simkes",
            "email": "myemail@email.com"
        }
    ],
    "minimum-stability": "stable",
    "require": {
        /**
         * As we are the only maintainers of those modules, it doesn't matter if we get the current trunk
         * If you have strict versioning requirements, you can set them too. If you use svn don't forget 
         *  to define the tags and branches paths if you define versions.
         */
        "kartaca/codebase-installer": "dev-master",
        "kartaca/codebase-custom": "dev-master",
    },
    "repositories": [
        {
            /*If you are using SVN don't forget to define the paths. */
            "type": "vcs",
            "url": "https://svn.codebase.com/codebase/modules/custom",
            "trunk-path": "trunk",
            "branches-path": false,
            "tags-path": false
        },
        {
            /*You don't need anything else for git, as you define the branch on require part. */
            "type": "vcs",
            "url": "https://git.codebase.com/codebase/modules/installer",
        }
    ]
}

As you see, for our custom modules, we have defined the repositories where the composer will look out for the packages. Wihtout these, it won’t be able to find them. Instead of defining each repository here, you can also create a repository server too with Satis. But this is a simple setup which will work like a charm. As this is for a singe project it will be enough.

So once you run the composer install command, everything will be fine and the packages will be installed in the vendor folder. In the composer.json file you should have noticed that there was a installer module and from the previous sentence you should notice that getting modules in the vendor directory is not enough as we need them in the app/modules. Now we are back at the first problem. So what we need is a custom installer module which will install our custom modules to the appropriate directory.

So we need a new module named kartaca/codebase-installer which will have a composer file like:

{
    "name": "kartaca/codebase-installer",
    "type": "composer-installer",
    "description": "Codebase Module Installer Package",
    "authors": [
        {
            "name": "Roy Simkes",
            "email": "myemail@email.com"
        }
    ],
    /* Define you autoloading method as composer already handles such things. Put your class inside a src folder in the root of this module. */
    "autoload": {
        "psr-0": {"Kartaca": "src/"}
    },
    "extra": {
        /*Say the class name of the installer which will be run*/
        "class": "Kartaca\\Composer\\Module\\Installer"
    }
}

So what we did was to define a package with type composer-installer. The type parameter is a must and tells the composer to attach the class defined in this package to run after during installation commands. Our Installer class is something like:

getName());
        //Now create the links under the modules folder
        echo "Creating symlinks for: " . $package->getName() . "\n";
        @symlink("../../vendor/" . $package->getName() . "/", "app/modules/" . $linkName);
        //Check if it has a public directory or not. If the module has one, then create a symlink on the public dir as well.
        if (file_exists("vendor/" . $package->getName() . "/public")) {
            if (!file_exists("public/modules")) {
                mkdir("public/modules");
            }
            @symlink("../../vendor/" . $package->getName() . "/public", "public/modules/" . $linkName);
        }
    }

    public function supports($packageType)
    {
        //Only our own modules should be used by this Installer. Modules like monolog should be ignored
        //If this method returns true, the installer will be used for all the life cycle events of that package
        return "kartaca-codebase-module" === $packageType;
    }
}

In order to this installer module to run on our custom modules, we need to change the previous composer.json to this:

{
    "name": "kartaca/codebase-custom",
    /* This is where the magic happens. */
    "type": "kartaca-codebase-module",
    "description": "Custom module for Codebase with custom type which will trigger the Installer module",
    "authors": [
        {
            "name": "Roy Simkes",
            "email": "myemail@email.com"
        }
    ],
    "require": {
        /**
         * Don't forget to define this. Otherwise, composer might install this module before it installs
         *  the Installer module, and the link we need might not be created.
         */
        "kartaca/codebase-installer": "dev-master"
    }
}

Notice we didn’t define repositories this time. As we already defined them before in the main composer file, we don’t need them again. While it’s a minor thing, don’t forget to define the installer module as the first module, if it’s not installed first, your other modules won’t be linked (installer will not work). Well it’s minor because you already define the installer module as a dependency to all your submodules, so Composer will install it first hand anyway.

While it’s specific to the ZF 1.x you need to enable the composer’s autoloader manually in your codebase as Zend’s Autoloader overrides the composer’s. You could also try http://composer.github.com/installers/ if you use Zend directly, however I have not used it.

So this is it. You have successfully move your codebase to Composer. Of course there might be some improvements that can be done, like starting to use Composer packages like Assetic or Monolog or whatever however this is pretty much covers the whole process of moving your code base to the Composer. It’s not hard and considering the current usage rate of Composer, it might be a boon for you if you are bored of PEAR. With this setup you can even create “lite” or “full” packages of your code base easily just by defining different composer.json files for each “setup” that has different dependencies.