Nils Adermann
@naderman


You thought Composer couldn't do that?

About Me

Passionate Free Software Developer
  https://github.com/naderman

phpBB Development Lead
 https://www.phpbb.com

Co-Founder of
 https://www.forumatic.com
 Professional Managed phpBB Hosting

Composer

The Story of an Install/Update

  1. Bootstrap
  2. Creation of the Package Pool
  3. Dependency Resolution
  4. Package Install / Update / Uninstall
  5. Wrap-up (Lock, Autoload, Events)

1. Bootstrap

Loading configuration

You have to define config options for each project

$COMPOSER_HOME/config.json

allows you to set defaults for config options in all your projects

*nix: /home/example/.composer/config.json

OSX: /Users/example/.composer/config.json

Windows: C:\Users\example\AppData\Roaming\Composer\config.json

1. Bootstrap

pre-install-cmd / pre-update-cmd scripts fire

1. Bootstrap

Loading of the root package

On first install or update

Create install requests for the requirements

(on update:) Force update installed dev packages to latest

On install from lock file

Load lock file as a repository

Create install requests for all its packages

2. Creation of the package pool

Platform repository

$ composer show --platform
                

Local (installed) repositories

$ composer show --installed
                

Locked repository (install only)

Custom repositories

Packagist

Custom Repository: VCS

{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/nelmio/NelmioSecretBundle"
        },
        {
            "type": "vcs",
            "url": "https://github.com/nelmio/lib"
        }
    ],
    "require": {
        "nelmio/secret-bundle": "1.*",
        "nelmio/lib": "1.5.3"
    }
}
            

I have to list every private package's vcs repository in my project

Private packages with Satis

satis.json

{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/nelmio/NelmioSecretBundle"
        },
        {
            "type": "vcs",
            "url": "https://github.com/nelmio/lib"
        }
    ]
}
            
composer create-project composer/satis myrepo
cd myrepo
bin/satis build satis.json www/
            

Private packages with Satis

composer.json

{
    "repositories": [
        {
            "type": "composer",
            "url": "https://satis.example.org/"
        }
    ],
    "require": {
        "nelmio/secret-bundle": "1.*",
        "nelmio/lib": "1.5.3"
    }
}
            

I still have to specify my custom satis repository in each package

Why aren't repositories recursive?

Hold on!

Proxying with Satis

Proxying with Satis

satis.json

{
    "repositories": [
        {
            "type": "composer",
            "url": "https://packagist.org"
        }
    ],
    "require": {
        "symfony/symfony": "*"
    }
}
            

Proxying with Satis

satis.json

{
    "repositories": [
        {
            "type": "composer",
            "url": "https://susans-satis.example.com"
        },
        {
            "type": "composer",
            "url": "https://johns-satis.example.com"
        }
    ],
    "config": {
        "require-all": true
    }
}
            

3. Dependency Resolution: Input

Pool

Package name → List of package versions which

Request

List of packages with version constraints to be installed/updated

3. Dependency Resolution: Solver

Generate boolean expression

All involved packages and their relationships in a single expression

Request: Install foo/x
foo/x-v1 requires foo/y-v1
foo/x-v2 requires foo/y
bar/z-v1 replaces foo/y-v2
(foo/x-v1 | foo/x-2) &
(!foo/x-v1 | foo/y-v1) &
(!foo/x-v1 | foo/y-v1 | foo/y-v2 | bar/z-v1)

Find TRUE/FALSE assignment for each package version

TRUE means install if not yet installed.

FALSE means remove if currently installed.

Generate transaction with correct installation order

Now why aren't repositories loaded recursively?

Option 1: Load all repositories recursively on startup

Would take a very long time

Option 2: Iteratively fetch repositories

Would either have to start over and potentially loop for a long time, or produce difficult to understand sub-optimal results

http://getcomposer.org/doc/faqs/why-can't-composer-load-repositories-recursively.md

So I really have to list every used custom repository in every project?

Combine Satis with config.json

*nix: /home/example/.composer/config.json

OSX: /Users/example/.composer/config.json

Windows: C:\Users\example\AppData\Roaming\Composer\config.json

{
    "repositories": [
        {
            "type": "composer",
            "url": "https://satis.example.org/"
        }
    ]
}
            

Combine Satis with config.json

satis.json

{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/nelmio/NelmioSecretBundle"
        },
        {
            "type": "vcs",
            "url": "https://github.com/nelmio/lib"
        }
    ]
}
            

Combine Satis with config.json

composer.json

{
    "require": {
        "nelmio/secret-bundle": "1.*",
        "nelmio/lib": "1.5.3"
    }
}
            

And Now For Something Completely Different

We found a bug in Symfony

If I want to patch an existing package I can only

Using a forked project

{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/naderman/symfony"
        }
    ],
    "require": {
        "symfony/symfony": "dev-master"
    }
}
            

Additional repositories take priority over the default ones

Using a forked project

{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/naderman/symfony"
        }
    ],
    "require": {
        "symfony/symfony": "dev-my-patch"
    }
}
            

Your branches are available as well

Use composer show -v symfony/symfony to check

I have other dependencies that require a specific version of symfony/symfony and now I get a conflict

Aliasing

{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/naderman/symfony"
        }
    ],
    "require": {
        "symfony/symfony": "dev-my-patch as 2.1.0"
        "symfony-cmf/symfony-cmf": "1.0.*"
    }
}
            

They said it's a feature not a bug

I cannot publish my fork permanently

Replace

{
    "name": "naderman/symfony",
    "replace": {
        "symfony/symfony": "2.*"
    }
}
            
{
    "require": {
        "naderman/symfony": "3.0.*"
        "symfony-cmf/symfony-cmf": "1.0.*"
    }
}
            

When I rename a package everything depending on the old name breaks

Renaming packages safely

{
    "name": "new/name",
    "replace": {
        "old/name": "*"
    }
}
            

I required naderman/symfony 3.0.*

dev-master has to be aliased to 3.0.0 in all my projects

Use branch-alias to make branches installable

Making Branches Installable

Symfony master branch

{
    "name": "symfony/symfony",
    "extra": {
        "branch-alias": {
            "dev-master": "2.2.x-dev"
        }
    }
}
            

Your project

{
    "require": {
        "symfony/symfony": "2.2.*"
    },
    "minimum-stability": "dev"
}
            
{
    "require": {
        "symfony/symfony": "2.2.*@dev"
    }
}
            

4. Install / Update / Uninstall

--prefer-source forces source install (git clone)

--prefer-dist forces dist install (zip download)

--dry-run if you are just curious

--verbose / -v to see more details

$ composer update -v (--verbose)
Loading composer repositories with package information
Updating dependencies
  - Updating symfony/symfony dev-master (487b8c => 885d47)
    Checking out 885d4733664b040765f2faab68f3aacef58d1216
    Pulling in changes:
      885d473 - Fabien Potencier: merged branch Tobion/empty-requ....
      13937de - Fabien Potencier: replaced self.version/2.1.* by ...
      a9a0f42 - Fabien Potencier: merged 2.1
      3c32fd9 - Fabien Potencier: replaced self.version by 2.1.*...
      c5edce7 - Fabien Potencier: merged branch eventhor...
      4d6dd46 - Fabien Potencier: merged branch eventhor...
                

5. Wrap-up

Writing lock file (update or first install only)

$ composer install
Installing dependencies from lock file
Your lock file is out of sync with your composer.json, run "composer.phar update" to update dependencies
            

Updating the lock file requires updating everything to the latest version

Update individual packages

$ composer update package1 package2
            

Update only the lock file

$ composer update nothing
            

> 6,800 packages on Packagist

Thank you for publishing your projects!

Makes Composer slow with old lock file format

<Seldaek> Update your lock files people, the end is nigh!

$ composer update nothing
                

5. Wrap-up

Generating an autoloader

Composer's autoloader is slow because it searches for files on disk

Generating an optimized autoloader

$ composer dump-autoload --optimize
                

5. Wrap-up

post-install-cmd / post-update-cmd scripts fire

Scripts are annoying you during deployment?

$ composer install --no-scripts
$ app/console assetic:dump --env=prod --no-debug web/
$ [...]
            

Troubleshooting

Package not auto-updating?

Validate your config

$ composer validate
./composer.json is valid
            

503 from GitHub when downloading zips?

Install from source instead

$ composer install --prefer-source
            

Experiencing a strange behavior?

Update composer

$ composer self-update
                

Check your setup's settings

$ curl -s https://getcomposer.org/installer | php -- --check
                

Update your deps

$ composer update -v
                

Still not working? Reinstall the deps

$ rm -rf vendor/
$ composer update -v
                

Still not working?

Report a bug with full --verbose output

  1. Define config and custom repositories in $COMPOSER_HOME/config.json
  2. Use composer show with options to look at available packages
  3. Setup private repositories with Satis
  4. Define custom repositories in a Satis proxy loaded in config.json
  5. Packages from custom repositories have higher priority
  6. Use replace when publishing a fork or renaming a package
  7. Use branch-alias to make named branches available as versions
  8. Use --dry-run and --verbose to get more info
  9. Always commit your lock file
  10. Update your lock file with composer update nothing
  11. Generate a class map with composer dump-autoload --optimize

Thank you

Find Out More

Questions?

naderman@naderman.de

@naderman


Feedback:

https://joind.in/7807