Profile    Mohammed Shiroz Status   Loading  
Logo
Share This
Back to blog
Filter by:
Tags
//Article title

What Actually Happens When You Run composer install

About Post

You clone a project, type composer install, watch a wall of green text scroll past, and get on with your day. It's probably the PHP command you run most and read least.

Then one day it fails with an error about a PHP version you're not even using, or prints a warning about the lock file being out of date, or works on your laptop and installs something slightly different on the server. And suddenly it matters what that command actually does.

So let's slow it down and follow it, step by step, from the moment you press Enter.

Two files, two jobs

Everything starts with the difference between these two files, and most Composer confusion comes from mixing them up.

composer.jsoncomposer.lock
SaysWhat versions you're willing to acceptThe exact versions you actually got
Example"laravel/framework": "^12.0"laravel/framework at one exact release, with its source reference
Written byYouComposer
CoversYour direct dependenciesEvery package, including dependencies of dependencies

Think of composer.json as a shopping list ("milk, any brand, not expired") and composer.lock as the receipt ("this exact carton, from this shelf, at this time"). If you want someone else to end up with exactly what you have, you give them the receipt.

Step 1: is there a lock file?

This is the fork in the road.

  • If composer.lock exists, Composer installs exactly what it lists. No thinking, no choosing. The same versions on every machine.
  • If it doesn't, Composer has to work out a set of versions that satisfies every constraint in your project and in every package's own composer.json. That's dependency resolution, and it's effectively what composer update does. Then it writes a new lock file.

If the lock file exists but doesn't match composer.json (someone edited the JSON and didn't update), Composer warns you that the lock file is not up to date. Don't ignore that one. It means the lock no longer describes the project.

Step 2: checking your platform

Packages declare what they need from your environment: a PHP version, and extensions like ext-mbstring or ext-intl. Composer checks these against the PHP running the command.

This is where the confusing errors come from. If your terminal's PHP differs from the one serving your app (very common on machines with several PHP versions, including XAMPP setups), Composer checks the wrong one. Run php -v in the same terminal before blaming the package.

You'll find advice online to use --ignore-platform-reqs. It makes the error go away by installing packages your PHP may not be able to run. Treat it as a last resort, not a fix.

Step 3: downloading, with a cache

For each package in the lock file, Composer downloads the release (usually a zip archive) and extracts it into vendor/. Downloads are cached in Composer's home directory, which is why the second install of the same versions is so much faster than the first.

Note that Packagist, the main package repository, only hosts metadata: which versions exist and where to get them. The code itself usually comes from wherever the package lives, such as GitHub.

Step 4: building the autoloader

This is the step that makes new SomeClass() work without a single require. Composer generates vendor/autoload.php from the autoload rules of every package and of your own project:

"autoload": {
    "psr-4": {
        "App\\": "app/",
        "Database\\Factories\\": "database/factories/"
    }
}

PSR-4 is a simple rule: namespace App\Services\PdfService maps to app/Services/PdfService.php. The case has to match exactly on Linux, which is why a class that loads on Windows can fail on the server.

When you add a new namespace mapping, run composer dump-autoload to regenerate the autoloader. In production, --optimize-autoloader builds a full class map so PHP doesn't have to check the file system for every class.

Step 5: running scripts

Finally, Composer runs any scripts defined in composer.json. Laravel uses this: after the autoloader is dumped, it runs php artisan package:discover to register service providers from your packages.

That has a surprising consequence. If composer install fails at this point, the problem often isn't Composer at all. It's your application failing to boot, because Artisan is running your app. A broken config file or a missing environment value can look like a Composer error.

Semantic versioning in two minutes

Versions follow MAJOR.MINOR.PATCH: breaking changes bump the major, new features the minor, fixes the patch. Constraints in composer.json use that convention:

  • ^12.0 means at least 12.0, below 13.0. Any non-breaking update. This is the sensible default.
  • ~1.2 means at least 1.2, below 2.0. ~1.2.3 is tighter: at least 1.2.3, below 1.3.0.
  • ^0.3 means at least 0.3.0, below 0.4.0, because before 1.0 every minor version is allowed to break things.
  • * means anything. Please don't.

Semver is a promise, not a guarantee. Package authors do occasionally break things in a minor release, which is exactly why the lock file exists.

The rule: commit composer.lock for every application, and deploy with composer install, never composer update. Updating is a decision you make on purpose, on your machine, in a pull request, with the tests running.

Why committing the lock file matters

  • Reproducible installs. Your laptop, CI and production get identical code.
  • Reviewable updates. A dependency update shows up as a lock file diff in the pull request, so you can see exactly which packages moved.
  • Security audits. composer audit checks the exact versions in your lock file against known vulnerabilities.

For libraries the story is different: when someone installs your package, your lock file is ignored. Their project's constraints decide.

And when two branches both changed the lock file and Git reports a conflict, don't hand-edit it. Resolve composer.json, take one branch's lock file, then re-run the composer require or composer update vendor/package commands from the other branch so Composer rebuilds it properly.

The production one-liner

composer install --no-dev --optimize-autoloader --no-interaction

No dev packages, an optimized class map, and no prompts waiting for a human who isn't there. Put it in your deploy script and never think about it again.

What's the strangest composer install failure you've had to debug? Mine usually turn out to be the wrong PHP binary in the terminal.

Comments (0)
Leave your review

Thanks for your valuable comments. Your comments has been updated and appreciate your getting in touch...

01. About Shiroz

Mohammed Shiroz

Hi, I'm Mohammed Shiroz, a software engineer and AI enthusiast from Sri Lanka who turns ideas into intelligent, real-world solutions. With over 9 years of hands-on experience, I currently lead real estate ERP development at Kate Group, a...

03.My Projects

04. Categories

Ready To order Your Project ?

Get in Touch
Close