Install Composer on Windows, macOS and Linux, signature checked

Install Composer on Windows, macOS and Linux, signature checked
Quick answer

Download the installer, compare its SHA-384 hash with the one published at composer.github.io/installer.sig, then run it into /usr/local/bin/composer. Commit composer.lock, ignore vendor/. In production, composer install --no-dev --optimize-autoloader, never composer update and never sudo.

Composer is the dependency manager for PHP. It installs the libraries a project needs, resolves their own dependencies, locks versions and generates class autoloading. This tutorial covers installation on all three systems, the day-to-day commands and the mistakes that cost time. Verified with Composer 2.10.3 on PHP 8.5.10.

Installing, with the signature checked

The recipe you see everywhere is curl -sS https://getcomposer.org/installer | php. It runs a downloaded file on the spot, with nothing checked. The official procedure compares the SHA-384 hash of the script with the one published by the project before running it.

installer-composer.sh
#!/usr/bin/env bash
set -euo pipefail

ATTENDU=$(curl -sS https://composer.github.io/installer.sig)
curl -sS https://getcomposer.org/installer -o composer-setup.php
CALCULE=$(php -r "echo hash_file('sha384', 'composer-setup.php');")

if [ "$ATTENDU" != "$CALCULE" ]; then
    >&2 echo 'ERREUR : signature invalide, ne pas exécuter'
    rm composer-setup.php
    exit 1
fi

php composer-setup.php --quiet --install-dir=/usr/local/bin --filename=composer
rm composer-setup.php
composer --version
code
Composer version 2.10.3 2026-08-27 13:34:23

The two hashes match, so the installer is genuine. Thirty seconds of extra work, against running an arbitrary script with the rights of your account.

Windows

Download and run Composer-Setup.exe. The installer finds PHP, sets up the PATH and updates itself.

macOS

The script above works as it is. With Homebrew:

bash
brew install composer

One thing about older tutorials: they tell you to add an alias to ~/.bash_profile. Since macOS Catalina (2019) the default shell is zsh, and that file is no longer read. The file to edit is ~/.zshrc. And the alias is not needed at all if composer.phar is installed in /usr/local/bin/composer with the execute bit set.

Linux

bash
sudo apt-get update
sudo apt-get install -y php-cli unzip curl
# then the verification script above

The composer package in the Debian and Ubuntu repositories is often several versions behind. The official installer is the better option.

Starting a project

bash
mkdir mon-projet && cd mon-projet
composer init          # interactive questionnaire
composer require monolog/monolog

This command creates or updates three things:

  • composer.json, what you ask for. Goes into version control.
  • composer.lock, the exact versions installed, down to the commit. Goes into version control too: this is what guarantees that the whole team and production run the same code.
  • vendor/, the downloaded files. Not versioned, put it in .gitignore.
.gitignore
/vendor/

install or update: the distinction that matters

Command What it does Where to use it
composer install Installs exactly what composer.lock says Production, continuous integration, joining a team
composer update Looks for newer versions and rewrites composer.lock Development machine, never in production

Running composer update on a production server installs versions nobody has tested. It is behind a remarkable number of failed releases.

bash
# Update a single package
composer update monolog/monolog

# Update a package and its dependencies
composer update monolog/monolog --with-dependencies

# See what would change, without changing anything
composer update --dry-run

Version constraints

composer.json
{
    "require": {
        "php": ">=8.2",
        "monolog/monolog": "^3.5",
        "symfony/console": "~6.4.0",
        "vendor/paquet": "2.1.3"
    }
}
Notation Allows Rejects
^3.5 3.5, 3.6, 3.99 4.0
~6.4.0 6.4.0, 6.4.9 6.5.0
6.4.* 6.4.0 to 6.4.99 6.5.0
2.1.3 2.1.3 only everything else

The ^ is the right default: it allows fixes and additions, and rejects breaking changes. Pinning an exact version looks careful, but it also blocks security patches.

Declaring the PHP version in require stops you installing a package that will not run on the server. And when the development machine does not have the same version as production, config.platform resolves dependencies for the target version:

json
{
    "config": {
        "platform": {
            "php": "8.4.0"
        }
    }
}

Autoloading

This is the most useful thing Composer does, and the last one people discover. The PSR-4 standard maps a namespace prefix to a directory.

composer.json
{
    "autoload": {
        "psr-4": {
            "App\\": "src/",
            "App\\Tests\\": "tests/"
        },
        "files": [
            "src/fonctions.php"
        ]
    }
}

Backslashes are doubled in a JSON file. It is the most common mistake in this file, and it stays silent right up to the moment Composer refuses to read it:

code
"App\": "src/"     -> JSON INVALIDE (l'antislash échappe le guillemet)
"App\\": "src/"    -> correct

With this configuration, the App\Personnages\Guerrier class is looked for in src/Personnages/Guerrier.php. The path follows from the name, with backslashes in the code and forward slashes in the directories.

src/Personnages/Guerrier.php
<?php

declare(strict_types=1);

namespace App\Personnages;   // backslashes, never forward slashes

final class Guerrier
{
    // …
}
index.php
<?php

require __DIR__ . '/vendor/autoload.php';

use App\Personnages\Guerrier;

$conan = new Guerrier();

After any change to the autoload section:

bash
composer dump-autoload

No comments in composer.json

JSON has no comments. A // slipped into the file makes it unreadable:

code
In JsonFile.php line 398:
  "./composer.json" does not contain valid JSON
  Lexical error on line 5. Comments are not allowed.

The composer validate command checks the file before the problem turns up somewhere else.

Checking for vulnerabilities

composer audit compares the installed packages against the security advisory database of the PHP ecosystem.

bash
composer audit
code
No security vulnerability advisories found.

It is a command to put in continuous integration, after composer install: without a vendor/ folder it only answers “No installed packages found”. It returns a non-zero exit code when an advisory concerns the project.

Spotting abandoned packages

Composer reports packages whose author has declared them abandoned, at install time. The warning often goes unnoticed in the flow of output:

code
Package facebook/php-sdk is abandoned, you should avoid using it.
Use facebook/graph-sdk instead.

An abandoned package gets no more fixes, security ones included. That message deserves a pause rather than being left to scroll past.

Deploying to production

bash
composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist
Option Effect
--no-dev Does not install development tools (tests, static analysis)
--optimize-autoloader Generates a full class map, with no disk lookup at runtime
--no-interaction Asks no questions
--prefer-dist Downloads archives instead of cloning the repositories

And one rule worth repeating: never run Composer with sudo. The files in vendor/ would belong to root, the web server could no longer read them, and the Composer cache would end up in /root/.composer. If the global install asks for sudo, that is only to write into /usr/local/bin, once.

Day-to-day commands

bash
# What is installed
composer show
composer show monolog/monolog        # details of one package
composer show --tree                 # dependency tree

# What could be updated
composer outdated
composer outdated --direct           # only your direct dependencies

# Why is this package here?
composer why psr/log
composer why-not symfony/console 7.0 # what is blocking an upgrade

# Remove a package
composer remove monolog/monolog

# Repair a broken install
rm -rf vendor composer.lock && composer install

Two Composer 1 flags are still doing the rounds in tutorials. composer show -i now returns a warning, since installed packages are what you get by default:

code
You are using the deprecated option "installed". Only installed packages are
shown by default now. The --all option can be used to show all packages.

And composer --V does not exist, it is -V or --version:

code
The "--V" option does not exist.

Package sources off the beaten track

json
{
    "require": {
        "moi/mon-paquet": "dev-main"
    },
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/moi/mon-paquet"
        }
    ]
}

To work on a package alongside the project that uses it, a repository of type path creates a symbolic link: your changes show up straight away, with no reinstall.

json
{
    "repositories": [
        {
            "type": "path",
            "url": "../mes-paquets/mon-paquet",
            "options": {
                "symlink": true
            }
        }
    ]
}

Scripts

json
{
    "scripts": {
        "test": "phpunit",
        "analyse": "phpstan analyse src --level=8",
        "verifier": [
            "@analyse",
            "@test"
        ]
    }
}
bash
composer verifier

That saves everyone remembering the paths into vendor/bin, and gives the whole team the same commands.

Key points

  • Check the hash of the installer before running it.
  • Commit composer.lock, ignore vendor/.
  • install in production, update only in development.
  • Double the backslashes in composer.json, and no comments.
  • composer audit in continuous integration, never sudo.

Composer is the way into most libraries: PHPMailer for sending email and Ratchet for a WebSocket server both install with composer require.

What comes next: Composer best practices, the basics of OOP in PHP to make the most of autoloading, and the Web development hub.

Common errors

Installer run without checking curl … | php runs a downloaded file with nothing checked. The official procedure compares its SHA-384 hash before running it.
Single backslash in composer.json "App": "src/" is invalid JSON: the backslash escapes the quote. You need two.
A comment in composer.json Lexical error on line 5. Comments are not allowed. JSON does not accept comments.
composer update in production It installs versions nobody has tested. In production it is composer install, which follows composer.lock.
Composer run with sudo The files in vendor/ then belong to root and the web server can no longer read them.
composer --V and composer show -i The "--V" option does not exist. and You are using the deprecated option "installed". Both flags come from Composer 1.
~/.bash_profile on macOS The default shell has been zsh since macOS Catalina: that file is no longer read, it is ~/.zshrc.

ComposerDépendancesPHP

Damien Flandrin Web developer since 2010, creator of Gekkode and Email Impact. Every article is tested on a real project before publication. Contact
Newsletter

New tests, tutorials and projects, by e-mail.

Reproducible tests, versioned code, dated results. Never any spam.