73

Composer behind the scene

Almost all modern PHP frameworks rely on Composer.

The idea is to have PHP libraries and packages you can reuse through a unique dependency manager.

Composer is ridiculously easy to install, and once you have it on your local machine, you can start using it.

Why should I use it?

The key concept is “reuse”. With Composer, you get access to an extensive range of standardized packages. This way, you don’t reinvent the wheel, and you can easily manage all dependencies.

Autoloading is also one of the killer features of Composer. Instead of doing this:

$root_dir = "/path/to/root_folder";
require $root_dir . "/lib/mylib.php";
require $root_dir . "/lib/mylib2.php";
require $root_dir . "/lib/mylib3.php";

You call only one file, vendor/autoload.php:

require $root_dir . "/vendor/autoload.php";

No more includes/requires at the beginning of each script. Besides, Composer has a smarter way to load classes. Thanks to an internal mapping, it only loads necessary files.

Quick installation

Please follow instructions here: for Mac and Linux.

For Windows, it’s a little bit different: see instruction

Then run the following command on your terminal:

composer -v

it gives you the specific version of Composer you just installed.

First Composer init

Composer gives you a useful command to get started:

composer init

It guides you with some prompts. The typical scenario might look like that:

Welcome to the Composer config generator

This command will guide you through creating your composer.json config.

Package name (/) [julien/test]:

Description []:

Author [Julien Maury xxx@unkown.com, n to skip]:

Minimum Stability []:

Package Type (e.g. library, project, metapackage, composer-plugin) []:

With those details, Composer will initialize your project and download your dependencies. You will get a new vendor directory where Composer downloads all packages and two files called composer.json and composer.lock.

If you open the composer.json file, you may see something like:

{
    "name": "julien/test",
    "require": {
        "stripe/stripe-php": "^7.37"
    },
    "authors": [
        {
            "name": "Julien Maury",
            "email": "xxx@unkown.com"
        }
    ]
}

In my example, Composer puts the Stripe package in the vendor directory. I can now use the Stripe package in my code:

require $root_dir . '/vendor/autoload.php';
$stripe = new \Stripe\StripeClient("MYSECRETKEY");

Simple as that!

Where to find packages?

The most popular place is packagist, but you can add any repository provided it has a composer.json file.

You have to declare source repositories if it’s not from packagist.org. For example:

{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/igorw/monolog"
        }
    ],
    "require": {
        "monolog/monolog": "dev-bugfix"
    }
}

Composer goes way beyond that

Composer is not only a great dependency manager. It solves problems. Thanks to its internal mapping system (a class loader), it only loads necessary files, so if you have several packages, it does not load all packages on every single request.

It’s reverse logic. If you need to use a class in your vendors, use it, and Composer will look into the vendors and load it for you.

You can control the way Composer autoloads your classes thanks to theautoload setting in the composer.json file:

"autoload": {
 "classmap": [
     "src/myclasses/"
     ]
}

With this basic rule, you tell Composer to autoload classes in the folder src/myclasses/, but indeed you can fine-tune it a little bit more…

PSR

PSR stands for PHP Standards Recommendations. Composer allows for easily using those standards when autoloading, so it solves another problem.

Instead of using a genericclassmap you can use a more specific sets of rulles such as “psr-4”:

"autoload": {
    "psr-4": {
      "App\\": "src/"
    }
}

What’s the point? With those PSR-4 rules, you have to follow some standards to name your files and namespaces/sub-namespaces. Here we have an App namespace in the src folder.

Because we are using PSR-4, if we want to create the class App\Repository\MyRepository, the file path must be src/Repository/MyRepository.php. Composer resolves the path from the namespace.

Running commands vs. editing files

Composer comes with a set of useful commands you can use to add/remove dependencies.

To add a package, you can either edit the composer.json file and add your dependency in the require section, or run in your terminal something like:

composer require stripe/stripe-php

To remove this package, run:

composer remove stripe/stripe-php

I recommend using command lines, especially for removing stuff.

Optimizing autoload

Autoloading is a powerful feature, but you can even go beyond by switching to an optimized autoload:

"config": {
    "optimize-autoloader": true
}

It improves performance by 30-40%. But how?

It spares a lot of filesystem checks. Known classes return their path instantly. Besides, if you enable opcache, it’s even faster!

Composer commands are a little bit slower with this configuration, though. It needs more time to optimize autoloading, but it’s palatable.

The .lock file

Every time you run a composer command, the .lock file is updated. When you run a composer init, this file is generated for the first time.

It saves the list of installed packages at a specific version and a hash of the composer.json file.

It’s essential for Composer. It makes sure that people get the same packages as when you developed the project.

The create command

You can use Composer to create projects. It’s excellent to start a new code based on some skeleton.

For example, for my WordPress projects, I appreciate the Bedrock approach. It’s still WordPress but entirely reconfigured to use Composer easily.

You can run the following command to test it:

composer create-project roots/bedrock my-project

Configuration

In this section, I give configuration examples. It is not an exhausting list. Please check this page to see more options.

Vendor dir

It’s possible to change the vendor folder with the vendor-dir config:

"config": {
    "vendor-dir": "the-source"
}

In your code, just load the new path:

require $root_dir . '/the-source/autoload.php';

Sort packages

Make sure, the following goes in your composer.json file in the config section:

"config": {
    "sort-packages": true
}

It sorts packages by name, very handy for maintenance.

The platform configuration

The PHP version is vital. How can you be sure that all your dependencies are compatible with your PHP version in production?

The answer is the platform configuration in composer.json:

"config": {
    "platform": {
        "php": "7.2"
    }
}

This way, all your dependencies will be updated accordingly.

Pro tips and versioning

Always commit the composer.lock file

It’s crucial to commit some files and to ignore some directories. I think you should always commit the composer.lock file.

Don’t try to resolve git conflicts in the composer.lock file

This one is a classic error. In case you need to merge or rebase your work, the composer.lock file may have some conflicts. You mustn’t attempt to resolve conflicts manually. You would get a wrong version of the file that would trigger additional errors.

Instead, run:

composer update --lock

Then, stage the resulting composer.lock file (git add).

Note that you can add a simple line in the .gitattributes file to tell Git to skip this file when merging:

/composer.lock -merge

Don’t put everything in the require section

If you need to add dependencies only for debugging, testing, or something specific to your local machine, it’s good practice to add them as require-dev dependencies not require.

Whenever you require something, you can either use require or require --dev:

composer require --dev <vendor-name>/<package-name>

The difference is essential. You don’t want all the things in production, especially when it’s not necessary. Composer will separate dev dependencies from other dependencies with a specific section in the composer.json file:

"require-dev": {
    "phpunit/phpunit": "9.2.6",
    "mockery/mockery": "1.4.1"
}

If you run a composer install, then Composer will download both dev and core dependencies. You can run a composer install --no-dev to prevent that behavior, it’s useful when preparing a release for production.

update vs. install vs. dump-autoload command

dump-autoload

The most basic one. It specifically generates the autoload. The composer install command already includes it, so it’s unnecessary to run it after a composer installation.

install

It installs all dependencies, including external dependencies, and it generates the autoload.

update

It updates all packages. Alternatively, you can update only a specific package :

composer update stripe/stripe-php

Extending Composer with hooks

For that, use the scripts section of the composer.json file. It gives you entry points to run some additional command (and even PHP scripts) on specific composer events such as installing, updating, or dump-autoload.

Please visit documentation. Their tutorial is extra good, nothing to add.

Note that you can call composer commands along with your custom scripts and commands.

For example, put the following in your composer.json file:

"scripts": {
    "test": [
        "@composer show",
        "ls -alh",
        "curl -I https://dev.to"
    ]
}

then run:

composer test

Wrap up

I hope you know a little more about Composer, why it’s useful, and how it works.