One day, you join a Laravel project.
You clone the repository, run:
composer install
Everything works perfectly.
A few days later, another developer joins the same project.
He runs the same command.
But suddenly…
“Why am I getting a different package version?”
You check the project and notice two important files:
composer.json
composer.lock
At first glance, they look similar.
Both are related to Composer dependencies.
So what is the actual difference?
Let’s understand it through a simple story.
Meet composer.json
Think of composer.json as your project's shopping list.
You tell Composer:
“These are the packages my project needs.”
For example:
{
"require": {
"php": "^8.2",
"laravel/framework": "^12.0",
"laravel/sanctum": "^4.0"
}
}This does not necessarily tell Composer the exact version it must install.
For example:
laravel/framework: ^12.0
means:
“Give me a compatible Laravel 12.x version according to this constraint.”
So Composer has some flexibility when resolving dependencies.
Then Who Decides the Exact Version?
Enter our second file:
composer.lock
Think of composer.lock as the final purchase receipt.
It records the exact package versions Composer resolved.
Something like:
laravel/framework → 12.35.1
laravel/sanctum → 4.2.0
symfony/console → 7.3.2
So the relationship is:
composer.json
↓
"What packages do I need?"
↓
Composer dependency resolution
↓
composer.lock
↓
"These exact versions were selected"
A Real-Life Example
Imagine you’re building a Laravel application today.
Your composer.json says:
"laravel/framework": "^12.0"
Today, Composer might resolve:
Laravel 12.30.0
A month later, Laravel releases:
Laravel 12.35.1
Now another developer clones the project.
Without composer.lock
Composer may resolve a newer compatible version:
12.35.1
Your machine:
12.30.0
Another developer:
12.35.1
Now you have:
“But it works on my machine!”
And the debugging begins.
What Happens With composer.lock?
Suppose the project already has:
laravel/framework → 12.30.0
recorded in composer.lock.
Another developer clones the project and runs:
composer install
Composer uses the lock file and installs the versions recorded there.
So:
Developer 1 → 12.30.0
Developer 2 → 12.30.0
Developer 3 → 12.30.0
Production → 12.30.0
Much more predictable.
The Most Important Difference
Here is the easiest way to remember it:
FilePurposecomposer.jsonDefines dependency requirementscomposer.lockStores exact resolved versions
Or even simpler:
composer.json = What I want
composer.lock = What I got
What Happens When You Run composer install?
This is where many developers get confused.
When composer.lock exists:
composer install
Composer installs the versions recorded in the lock file.
So it is generally the command you use when setting up an existing project.
For example:
git clone project
cd project
composer install
Then your environment gets the dependency versions expected by the project.
What Happens When You Run composer update?
Now things become different.
When you run:
composer update
Composer looks at:
composer.json
and resolves the dependencies again.
It may discover newer versions that satisfy your version constraints.
Then Composer updates:
composer.lock
For example:
Before
Laravel → 12.30.0
composer update
After
Laravel → 12.35.1
The lock file changes because the dependency resolution changed.
A Common Laravel Scenario
Imagine your project contains:
"require": {
"laravel/framework": "^12.0",
"spatie/laravel-permission": "^6.0"
}Your lock file might contain:
laravel/framework → 12.30.0
spatie/laravel-permission → 6.20.0
Now Spatie releases:
6.21.0
Your composer.json doesn't necessarily need to change because:
^6.0
already allows compatible 6.x versions.
Running:
composer update
could update the package in composer.lock.
But running:
composer install
will normally stick to the versions already recorded in the lock file.
Should composer.lock Be Committed to Git?
For most applications, yes.
For example, a Laravel application repository commonly contains:
app/
bootstrap/
config/
database/
resources/
routes/
composer.json
composer.lock
Commit both:
git add composer.json composer.lock
git commit -m "Update dependencies"
Why?
Because your team wants predictable dependency versions across:
Local
↓
Development
↓
Staging
↓
Production
Everyone should ideally be using the same dependency set.
What About PHP Packages?
There is an important distinction between a project/application and a reusable package/library.
For a Laravel application:
composer.lock
is normally committed.
For a reusable PHP package, the lock file is generally not committed as part of the package’s published dependency contract, because consumers need Composer to resolve dependencies in the context of their own projects.
This distinction is important when working with packages published to platforms such as Packagist.
Does composer.lock Exist Automatically?
It is usually generated when Composer resolves and installs dependencies for a project.
For example:
composer install
or:
composer update
can create/update the lock file depending on the project state and command.
A typical project might start with:
composer.json
Then after dependency resolution:
composer.json
composer.lock
vendor/
What Is Inside composer.lock?
The lock file contains much more detail than just package names.
It records information such as:
Package name
Version
Source
Distribution
Dependencies
PHP requirements
Hashes / metadata
A simplified example:
{
"name": "laravel/framework",
"version": "v12.30.0",
"require": {
"php": "^8.2"
}
}The real lock file is much larger because Laravel itself depends on many other packages.
Laravel Has More Dependencies Than You Think
You may write:
"laravel/framework": "^12.0"
But Laravel depends on many other Symfony and PHP ecosystem packages.
So Composer creates a dependency tree:
Your Laravel App
|
└── Laravel Framework
|
├── Symfony Console
├── Symfony HTTP Foundation
├── Symfony Routing
├── Symfony Process
└── ...
composer.lock records the resolved dependency graph.
That’s one reason the file can become quite large.
A Mistake Developers Often Make
Imagine you pull the latest code:
git pull
and someone changed:
composer.json
composer.lock
You then run:
composer update
just because you want to install the new package.
That may update many other dependencies too.
You could unexpectedly upgrade packages that were already working correctly.
For an existing project, this is often safer:
composer install
when the lock file already represents the intended dependency set.
When Should You Use composer update?
Use it when you intentionally want to re-resolve or upgrade dependencies.
For example:
composer update
or a targeted update such as:
composer update laravel/framework
After reviewing and testing the changes, commit the updated lock file.
The Golden Rule
Remember these two commands:
composer install
↓
Use the locked dependency versionscomposer update
↓
Resolve dependencies again
↓
Update composer.lock
And these two files:
composer.json
↓
Dependency rules / requirements
composer.lock
↓
Exact resolved dependency versions
Final Thought
The easiest analogy is this:
composer.json is your recipe.
It says:
“I need Laravel, Sanctum, Spatie Permission, and PHP 8.2+.”
composer.lock is the exact ingredient list that was selected.
It says:
“This is the precise version of every ingredient that made the application work together.”
That’s why both files are important, but they serve completely different purposes.
When you’re working on a Laravel team project, understanding this difference can save you from one of the most common dependency problems:
“It works on my machine, but not on yours.”
So the next time you see:
composer.json
composer.lock
remember:
JSON defines the rules.
LOCK records the result.
Happy Coding! 🚀
— Mr. Chauhan
Комментарии (0)
Пока нет комментариев — будьте первым.