Composer
Composer reads composer.json, resolves your dependencies, and puts them in vendor/ behind one autoload file you require once.
composer require vendor/package [--dev]Composer is PHP's dependency manager. You name the packages you want in a file; Composer works out which versions fit together, downloads them, and generates an autoloader for all of them plus your own code.
composer.json
This file, in your project root, is the whole configuration. name identifies your project as vendor/package. require lists what the running application needs. require-dev lists what only you need while developing — a test runner, a static analyser — and those are skipped when you install with --no-dev on a server. autoload maps your own namespace prefix to a directory.
You rarely edit require by hand. composer require monolog/monolog adds the entry, picks a sensible constraint, resolves, installs, and updates the lock file in one step. composer require --dev phpunit/phpunit does the same under require-dev.
install against update
These are different commands and the difference matters. composer install reads composer.lock and installs those exact versions. composer update ignores the locked versions, re-resolves against the constraints in composer.json, and rewrites the lock file. Deployments run install. You run update on your own machine, deliberately, then commit the new lock file.
vendor/
Everything downloaded lands in vendor/. It is generated, so it is never committed — .gitignore it. Its one entry point is vendor/autoload.php, which you require once at the top of your front controller.
Example
When to use it
- Add a logging or HTTP package to a project instead of writing one.
- Give a new teammate one command, composer install, that reproduces your exact dependency set.
- Keep a test runner available locally and absent from the production server.
- Replace a hand-written autoloader with the generated one and delete the require lines.
More examples
A composer.json for a plain PHP application
A starting point with no framework in it. The php entry is a real constraint and Composer refuses to install if the running PHP does not satisfy it, which catches a server on 8.3 before your code does. The ext-* entries say the same about extensions, so a missing PDO fails at install time rather than on the first query. scripts gives a name to a command, so composer test runs phpunit and newcomers do not have to find the binary in vendor/bin.
{
"name": "acme/shop",
"description": "Order and invoice handling for the Acme shop",
"type": "project",
"license": "proprietary",
"require": {
"php": ">=8.5",
"ext-pdo": "*",
"ext-mbstring": "*"
},
"require-dev": {
"phpunit/phpunit": "^12.0"
},
"autoload": {
"psr-4": {
"Acme\\Shop\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Acme\\Shop\\Tests\\": "tests/"
}
},
"config": {
"sort-packages": true
},
"scripts": {
"test": "phpunit"
}
}The commands, in the order you actually run them
Memorise which of these writes composer.lock: require, update and remove do; install does not. That one fact explains most Composer confusion. The last line is the deployment command: --no-dev leaves out everything in require-dev, and --optimize-autoloader converts the PSR-4 rules into a flat map of class name to file so PHP stops checking the filesystem for each class.
# Start a project. Writes composer.json; nothing is installed yet.
composer init
# Add a runtime dependency. Edits composer.json, resolves, downloads,
# writes composer.lock and vendor/, regenerates the autoloader.
composer require monolog/monolog
# Add a tool you only need while developing.
composer require --dev phpunit/phpunit
# A teammate clones the repo. This installs the EXACT locked versions.
composer install
# You decide to take newer versions. Re-resolves and rewrites the lock file.
# Run this on your machine, never on a server, then commit composer.lock.
composer update
# Move one package and leave the rest alone.
composer update monolog/monolog
# You edited the autoload section by hand, or moved src/ around.
composer dump-autoload
# On the production server.
composer install --no-dev --optimize-autoloaderWiring vendor/autoload.php into the front controller
The file that handles the request requires the autoloader once, as the first statement after declare. Everything below it, both third-party classes and your own, resolves by name. Point your web server's document root at public/ so that vendor/ sits outside it: a dependency that ships a stray .php file should never be reachable over HTTP. If this require fails, nobody has run composer install in that checkout.
<?php
// public/index.php
declare(strict_types=1);
// One require for every dependency AND for your own src/ classes.
require __DIR__ . '/../vendor/autoload.php';
use Acme\Shop\Billing\InvoiceRepository;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('shop');
$log->pushHandler(new StreamHandler(__DIR__ . '/../var/app.log', Logger::WARNING));
// Acme\Shop\... resolves through the same autoloader as Monolog\...
$repository = new InvoiceRepository($pdo);
Discussion