Tooling

Composer and Dependency Management: What's New for PHP

Master Composer 2.7+ for PHP dependency management. Covers audit, platform checks, private packages, monorepo strategies, and security best practices.

Composer PHP package manager dependency tree visualization

Composer is the dependency manager for PHP. Every modern PHP project uses it, and most developers interact with it daily. But many developers use only require, install, and update, missing features that prevent security issues, reduce build times, and simplify complex dependency graphs.

This guide covers practical Composer usage in 2026, from daily workflows to advanced configuration.

Composer 2.7: what is new

Security auditing

1
2
# Check all dependencies for known vulnerabilities
composer audit

composer audit queries the Packagist security advisory database and reports any installed packages with known CVEs. This should run in every CI pipeline.

1
2
3
4
5
6
7
Found 2 security vulnerability advisories affecting 2 packages:
+-------------------+--------------------------+
| Package | CVE |
+-------------------+--------------------------+
| guzzlehttp/psr7 | CVE-2023-29197 |
| symfony/http-kernel| CVE-2024-XXXXX |
+-------------------+--------------------------+

Platform check improvements

1
2
# Verify your PHP version and extensions match composer.json requirements
composer check-platform-reqs

This catches the common “works on my machine” problem where production has a different PHP version or is missing an extension.

Faster installs

Composer 2.7 improved parallel downloading and metadata caching. Fresh installs are ~30% faster than Composer 2.4. The --prefer-dist flag (default) downloads zip files instead of cloning git repositories, which is faster for most packages.

Essential daily commands

Installing dependencies

1
2
3
4
5
# Install from composer.lock (deterministic, for CI and production)
composer install --no-dev --optimize-autoloader

# Install for development
composer install

Always use composer install (not update) in CI and production. install reads composer.lock and installs exact versions. update resolves new versions, which may differ between environments.

Adding packages

1
2
3
4
5
6
7
8
9
# Add a production dependency
composer require laravel/framework:^13.0

# Add a dev dependency
composer require --dev phpunit/phpunit:^10.0

# Add without immediately updating
composer require --no-update package/name
composer update package/name --with-dependencies

Updating safely

1
2
3
4
5
6
7
8
9
10
11
# Preview what would change
composer update --dry-run

# Update one package and its dependencies
composer update laravel/framework --with-dependencies

# Update all packages within constraints
composer update

# Update to latest minor versions only (avoid major bumps)
composer update --prefer-lowest # Find minimum compatible versions

Removing packages

1
2
# Remove a package and update the lock file
composer remove unused/package

Version constraints explained

1
2
3
4
5
6
7
8
9
{
"require": {
"vendor/package": "^2.0",
"vendor/exact": "2.1.3",
"vendor/range": ">=2.0 <3.0",
"vendor/tilde": "~2.1",
"vendor/wildcard": "2.1.*"
}
}
Constraint Meaning Allows
^2.0 Caret (recommended) >=2.0.0, <3.0.0
^2.1.3 Caret with patch >=2.1.3, <3.0.0
~2.1 Tilde >=2.1.0, <3.0.0
~2.1.3 Tilde with patch >=2.1.3, <2.2.0
2.1.* Wildcard >=2.1.0, <2.2.0

Use ^ (caret) by default. It allows minor and patch updates (which should be backward compatible) while preventing major version bumps.

The composer.lock file

composer.lock records the exact version of every installed package, including transitive dependencies. It ensures that every developer and every deployment uses identical versions.

Rules:

  • Always commit composer.lock to version control
  • Never edit composer.lock manually
  • If composer.lock conflicts in a merge, delete it and run composer update
1
2
# Verify lock file is in sync with composer.json
composer validate

Autoloading

PSR-4 autoloading

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"autoload": {
"psr-4": {
"App\\": "src/",
"App\\Tests\\": "tests/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
}
}

Optimized autoloader

1
2
3
4
5
# Generate optimized class map for production
composer dump-autoload --optimize

# Or with install
composer install --optimize-autoloader

The optimized autoloader converts PSR-4 rules into a static class map, eliminating filesystem lookups. This is measurably faster on production servers.

Classmap autoloading

For directories that do not follow PSR-4:

1
2
3
4
5
6
{
"autoload": {
"classmap": ["database/seeds", "database/factories"],
"files": ["src/helpers.php"]
}
}

Scripts and hooks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"scripts": {
"post-install-cmd": [
"@php artisan package:discover"
],
"post-update-cmd": [
"@php artisan package:discover"
],
"test": "vendor/bin/phpunit",
"lint": "vendor/bin/phpstan analyse",
"check": [
"@test",
"@lint",
"composer audit"
]
}
}
1
2
3
4
5
# Run a custom script
composer run-script test

# Short form
composer test

Private packages

Private Packagist

For organizations with private packages, Private Packagist provides a hosted solution:

1
2
3
4
5
6
7
8
{
"repositories": [
{
"type": "composer",
"url": "https://repo.packagist.com/your-org/"
}
]
}

Git repositories

1
2
3
4
5
6
7
8
{
"repositories": [
{
"type": "vcs",
"url": "git@github.com:your-org/private-package.git"
}
]
}

Path repositories (monorepos)

1
2
3
4
5
6
7
8
9
10
11
{
"repositories": [
{
"type": "path",
"url": "../packages/*"
}
],
"require": {
"my-org/shared-models": "*"
}
}

Path repositories create symlinks, allowing you to develop multiple packages in a monorepo while maintaining proper dependency declarations.

Platform configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"config": {
"platform": {
"php": "8.4.0",
"ext-redis": "6.0.0"
},
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
}
}

The platform setting tells Composer to resolve dependencies as if you were running a specific PHP version, even if your local version differs. This ensures CI and production compatibility.

Common mistakes

Not committing composer.lock: Without the lock file, composer install behaves like composer update, resolving potentially different versions on each machine.

Using composer update in production: Always use composer install --no-dev in production. update may pull in different versions than what was tested.

Ignoring composer audit: Known vulnerabilities in dependencies are the easiest attack vector. Run audit in CI and fix flagged packages promptly.

Requiring exact versions: "vendor/package": "2.1.3" prevents receiving security patches. Use ^2.1.3 instead to allow patch and minor updates.

FAQ

What is the difference between require and require-dev?

require packages are installed everywhere (dev, CI, production). require-dev packages are only installed when --no-dev is not passed. Put test frameworks, linters, and debug tools in require-dev.

How do I resolve dependency conflicts?

Run composer why-not vendor/package 3.0 to see which packages prevent an upgrade. Then update those blocking packages or use composer require vendor/package:^3.0 --with-all-dependencies.

Should I use composer.phar or the global install?

Either works. The global install (composer command) is more convenient. Including composer.phar in the project ensures everyone uses the same Composer version.

Next steps

Run composer audit and composer check-platform-reqs on your project right now. Fix any flagged vulnerabilities and platform mismatches. Then add both commands to your CI pipeline.

For PHP testing frameworks installed via Composer, the PHPUnit and Pest guide covers testing best practices. The Mago linter guide covers static analysis tools that complement Composer’s security auditing.