Vespolina: The Sandbox Story 4 – composer.json To The Rescue!

This post is about how to set up vespolina project using composer. It is intended mainly for people wanting to setup vespolina sandbox in a snap. When loading dependencies for a project, and in particular with vespolina sandbox which loads 15 vespolina bundles we want to have an easy and standard way of loading not just the vespolina bundles but also all the symfony2 dependencies. For that welcome to composer.

Before we proceed let’s install composer on your system. We don’t want to install it the regular trivial way but we want to set it up so that we can do PRs to the composer repo and help back. So we chose the development setup. The following sets development installation of composer on ubuntu 11.10 latest:

mkdir $HOME/composer
cd $HOME/composer
git clone https://github.com/composer/composer.git .
wget http://getcomposer.org/composer.phar
php composer.phar install
ln -s $HOME/composer/bin/composer /usr/bin/composer

With this we basically want to create a composer folder at the user home directory level, clone composer there, bring composer to build composer source code dependencies, then symlink the script under composer/bin to a default system wide /usr/bin/composer executable. This is a flexible setup indeed as it will let us run it simply in this way `composer [options]`.
If you are not of the same mind, you can find instructions for other types of installation setups here.

Now that we have our setup ready. We move into our project directory. And also create some helper scripts which we would want to PR composer with. These scripts basically remove the files/folders created by a composer run. The remove script is needed because sometimes we just mess with the dependencies writing the composer.json file and we need to start afresh. The remove script looks like this:

// remove.sh
rm -rf vendor composer.lock

Now we are ready to write our composer.json file. There are some gotchas and this is why I wrote this blog post.
The way to add requires to composer.json is to go check the availability on packagist.org and copy the vendor/package_name and do the inserts under require like:

{
    "autoload": {
        "psr-0": { "": "src/" }
    },
    "require": {
        "php": ">=5.3.2",
        "symfony/symfony": "2.1.0-dev",
        "vendor/package_name": "master-dev",

One can use the command `composer show package_vendor\package_name` in the same way. Be mindful that each package has requirement constraints assigned to packages that the package at hand depends on. If by chance it happens that the package that is depended upon does not match the constraint criteria then the package will not be installed and composer could choose to tell your or not to tell you depending on how bad is the conflict. You may have a successful build of the explicit dependencies but that means little. You will have cryptic errors until:

1. you manually impose the requires as above for the packages depended upon, or
2. you choose the version of package at hand that relaxes the requirements so that its dependency can get installed as well.

There can be a case where #2 above is impossible to meet for a current version or range, and that is more the developer community’s fault rather than yours :D. Worst case scenario is that you do things manually like I have indicated above.

Two things to keep in mind when writing your composer.json are: First, only bundles need a target-dir key to reproduce the path for their namespace. Second, libraries need PSR-0 key set indicating its top namespace and the root directory (lib, src, or whatever is the case) to ensure they can get loaded but libraries do not use a target-dir keyword.

After you have completed your composer.json it is time to make sure composer not only parses correctly but imports what it says it will import. The command that allows this test is:

composer install --dry-run --dev
composer install   // <--- this only when done!

The first command is a dry run to test what will happen. The second command is going to run it all the way.
Determining which dependencies did not load from the errors is simple, however it could also be challenging to the newbie just inferring the missing component. In order to troubleshoot further one should take a look at `installed.json` and `autoload_namespaces.php` under vendors directory (notice vendors directory can be configured by the top composer.json and you can read further on packagist.org). Looking at these two files can help you see what is wrong and back port it to your composer.json to make things work.

Once you have at least one composer.json working, even if you have to explicitly tell all dependencies into your composer.json file, you can take advantage of another more powerful tool. The tool is under the command `depends` and it will tell you what packages are affected by the package at hand and which is the required version of this package at hand:

~ composer depends symfony/symfony
friendsofsymfony/rest-bundle master-dev requires >=2.1.0-dev
doctrine/doctrine-fixtures-bundle master-dev requires >=2.0
snc/redis-bundle 1.0.1 requires >=2.0.4
secotrust/route-statistics-bundle master-dev requires >=2.0.4
doctrine/couchdb-odm-bundle master-dev requires 2.1-dev
loso/di-annotations-bundle 0.0.5-dev requires >=2.0.4
cedriclombardot/admingenerator-generator-bundle 1.0.0-BETA-dev requires >=2.0.0

This command is used to simplify our composer entries. If we go one by one, line by line on our requires and run the depends command on every package in each line and make sure to remove the line if we find that there is a package that is listed already on our composer.json that will import the package in the current line. If we do this operation we will simplify our composer.json to a few lines.

And as you can see it will give you which packages depends upon this package named. Of course that is somewhat helpeful (like the show command) so you don’t have to go online to packagist.org and check every package.
There is also a tool to check locally what packages and its versions are installed:

~ composer debug:packages --local
local: sonata/admin-bundle master-dev (9999999-dev)
local: vespolina/monetary-bundle master-dev (9999999-dev)
local: vespolina/ecommerce-flows-bundle master-dev (9999999-dev)
local: vespolina/workflow-bundle master-dev (9999999-dev)

Also this can help you further understand the dependencies and ensure we have installed all packages and its dependencies. If some package is missing from this list we know it was not imported by composer due to some internal error.

To avoid unnecessary repetitive downloads, we use the command `composer install –dry-run –dev`.
The process to debug an entry in a composer.json file is the following:

1. add one entry
2. run `composer install --dry-run --dev` and check the list of imported packages is correct
3. if yes then go to 1, else check dependencies, move down (remove to debug), or call it for the day.

In order to aid for huge composer files. We have come with the idea of clusters. Clusters of dependencies that can be grouped and pulled altogether with just one require line. In order to accomplish this we create a composer.json inside an empty project folder, push it to a github repository and then submit it to packagist and registered under vendor_name/cluster-other_name. The next step is just add the require line into our top and project level composer.json. An example of this approach follows:

{
    "name": "vespolina/vespolina-sandbox-composer",
    "type": "group-of-deps",
    "description": "Vespolina Bundles",
    "keywords": ["vespolina"],
    "homepage": "http://vespolina-project.org",
    "license": "MIT",
    "authors": [
        {
            "name": "Vespolina Team",
            "email": "cordoval@gmail.com"
        }
    ],
    "require": {
        "vespolina/store-bundle": "*",
        "vespolina/checkout-bundle": "*",
        "vespolina/product-bundle": "*",
        "vespolina/cart-bundle": "*",
        "vespolina/inventory-bundle": "*",
        "vespolina/taxonomy-bundle": "*",
        "vespolina/order-bundle": "*",
        "vespolina/customer-bundle": "*",
        "vespolina/pricing-bundle": "*",
        "vespolina/fulfillment-bundle": "*",
        "vespolina/merchandise-bundle": "*",
        "vespolina/taxation-bundle": "*",
        "vespolina/workflow-bundle": "*",
        "vespolina/ecommerce-flows-bundle": "*",
        "vespolina/monetary-bundle": "*"
    }
}

And inside our project’s composer.json we have the following require entry:

"vespolina/vespolina-sandbox-composer": "*",

There is a word of caution when creating packagist.org repositories for some old projects or projects that are not yet available on packagist.org. That word is to notify and PR the package maintainer to make its package available at packagsit.org. Then you will be able not just include it as a custom repository at the top composer.json level of your project, but you would just include a short require line which will import the package for you and will make your composer.json look much simpler. Right now–and probably always–composer does not resolve repositories recursively, it only does for dependencies available in packagist.org. So it is impossible for composer to resole ALL repositories in ALL packages before starting, and this is the intention of its creators. Therefore the best practice is to inform your package maintainers and to be patient. Do not create temporary repository or namespaces, not even in your namespace, this is not encouraged and will not be tolerated as it will break things for someone who may start depending on your package. Things can go bad for whoever depends on your package and you happen to delete it. So packagist.org is therefore for permanent evolving stuff.

This ends the first part of our series on vespolina and composer. Come back for more.

I thank the Lord Jesus Christ. And the opportunity to have received much help through helpful top guys like Stof, Seldaek, igorw, and asm89 and the developers of composer.

One thought on “Vespolina: The Sandbox Story 4 – composer.json To The Rescue!

  1. Pingback: A week of symfony #263 (9->15 January 2012) « We are php

Leave a Reply

Your email address will not be published. Required fields are marked *