2016-02-29

composer.lock: Why you need it and why it should be kept in version control

Composer is a dependency manager for PHP, used by many projects, most notably the Symfony PHP framework. It allows developers to specify which dependencies a project has by creating a composer.json file. When composer is run with "composer install", it uses the composer.json file to find out which versions of the dependencies to get. I do not want to get into too much detail regarding how version constraints can be specified in the composer.json file. If you are interested, there is a detailed manual on versions for Composer. Suffice to say that you usually do not want to specify a fixed version, but instead want to specify a minimum version so that your software can be updated to use newer versions of your dependencies.

The problem with this approach should already be apparent: When you create the project or checkout the project from source control and run "composer install", you will get the newest compatible versions of the dependencies which are available at the time you run the command. However, when another developer checks out the code, the same will happen to him, only that by then there could be a newer version than what you have. It is possible, that this newer version is incompatible with your project, which you did not anticipate when defining the version constraints in the composer.json file. So now the other developer can not get your project working because the dependency fails and, being new on the project, has no idea how to fix it. You, on the other hand, are happily coding on your checked out copy of the code which uses the old version of the dependencies and will never be aware of the problem.

The gist of this example is that it is bad practice to use different versions of dependencies among developers. You want all developers to use the same version of the dependencies to get reproducible results for all checked out copies of your code. The Composer folks were aware of this problem and created composer.lock to ensure this.

When you run "composer install" for the first time, Composer will find the newest compatible version of your dependencies and install them. It will then generate the composer.lock file which contains information about the exact version which was installed so that it is possible to restore this state on your machine and on other machines. So unlike the composer.json file, the composer.lock file contains specific versions numbers for all your dependencies. The next time you or someone else runs "composer install", Composer will try to find the composer.lock file and install the version of the dependencies specified in there.

Now it should also be obvious why you should add the composer.lock file to version control: By sharing this file, you let all other developers know which versions of the dependencies you have installed and tested your code with. They will now no longer need to fix possible bugs which might happen with newer versions of your dependencies.

So why should you not specify exact version constraints directly in your composer.json file? Well, this file is used to specify which versions your software should be compatible with, so which versions it should be safe to upgrade to. If you specify version constraints correctly and all your dependencies obey semantic versioning rules, you can run "composer update" to get the newest compatible version for each dependency as specified in the composer.json file. Note that this does not read the composer.lock file, but instead updates it with the new exact version numbers you are now using. After you have tested and verified everything still works, you can commit your updated composer.lock file to the repository. All other developers can then run "composer install" again to use the same versions as you.

2015-11-03

How to prevent choice field from rendering attr on each option element in Symfony 2.7

In my Symfony 2.6 application, I use some forms with the choice field type. As I wanted those choice fields to match the rest of my UI which uses jQuery UI, I added the relevant classes to my fields when rendering them.
<?php echo $view['form']->widget($form['myField'], ['attr' => ['class' => "text ui-widget-content ui-corner-all"]]); ?>
This worked perfectly and generated the correct attribute on my <select> tag, but not on my <option> tags. Just as I wanted it.
<select class="text ui-widget-content ui-corner-all" name="myField">
    <option value="1">Option 1</option>
    <option value="2">Option 2</option>
</select>
At a later point in time, I upgraded my application from Symfony 2.6 to 2.7 and ever since then the class attribute was rendered on the <select> tag as well as on the <option> tags.
<select class="text ui-widget-content ui-corner-all" name="myField">
    <option value="1" class="text ui-widget-content ui-corner-all">Option 1</option>
    <option value="2" class="text ui-widget-content ui-corner-all">Option 2</option>
</select>
Unfortunately, this made borders appear around the single elements in my select lists which looks quite ugly.

After a bit of searching the internet, I found a blog post about this new behavior on the Symfony blog. In New in Symfony 2.7: Choice form type refactorization, the new choice_attr options is explained. When rendering the <option> tags, Symfony now merges the entries in choice_attr with the ones in attr.
So the easiest solution seems to be to overwrite our class entry in the choice_attr option and we should be done. I did this directly in my form class.
namespace MyBundle\Form;

public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder
        ->add('roles',
            'entity',
            [
                'class' => 'MyBundle:Role',
                'choice_label' => 'name',
                'multiple' => true,
                'choice_attr' => function () { return ["class" => ""]; }
            ]);
}
While this solution works, you will need to do it for each choice field that you use in your entire application. As I was using quite a few of those, I decided I wanted something more flexible which would allow me to change the behavior of the choice field without touching every single form. So I reverted my previous change and searched for a better solution.

The first step was to create a custom field type which is based on the choice field type but exhibits the same behavior I was used to from Symfony 2.6. So I created a new field type that would always override the class attribute in the choice_attr options.
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ChoiceNoOptAttrType extends ChoiceType {
    public function configureOptions(OptionsResolver $resolver) {
        parent::configureOptions($resolver);

        $resolver->setDefault("choice_attr", function () { return ["class" => ""]; });
    }
}
This at least means that I don't need to touch the choice_attr option for all my form types. However, I would still need to change each of my existing form types to use my newly created choice form type. Also not a nice solution.
I checked the Symfony documentation to find out how Symfony actually creates a choice field. Symfony uses dependency injetion to locate its built-in field types. So what we need to do is change the dependency injection container to return our newly created custom choice field type replacement.
Symfony provides what they call “Compiler Passes” to make changes to the DI container after it has been initialized. The basic idea is that you create a custom class that is given the fully instantiated DI container on which you can then perform changes, e.g. replace an existing service with a different one. (Further reading in the Symfony docs: Creating a Compiler Pass)
This seems like the perfect entry point for what we want to do: replace the built-in choice field type. Thanks to the verbose Symfony documentation, it is not hard to figure out what code we need to accomplish this task.
namespace MyBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class MyCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        $definition = $container->getDefinition("form.type.choice");
        $definition->setClass('MyBundle\Form\ChoiceNoOptAttrType');
    }
}
Now that we have created a compiler pass, we still need to make sure that it is actually executed. Compiler passes need to be registered for Symfony to find and execute them when building the container. As my compiler pass was in my bundle, I added the required code to my bundle class. (Further reading in the Symfony docs: How to Work with Compiler Passes in Bundles)
namespace MyBundle;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use MyBundle\DependencyInjection\Compiler\MyCompilerPass;

class MyBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

        $container->addCompilerPass(new MyCompilerPass());
    }
}
And this is enough to let Symfony work its magic. Now all choice fields in my application use my custom created form type. Now I don't have to make any changes to my existing form types and they still exhibit the same behavior as in version 2.6.