ObqPxwaNcVbkBEcXJUDaAp7ED0XT8WBRpWZFMxcY.webp

Однажды вы присоединяетесь к проекту на Laravel.

Клонируете репозиторий, запускаете:

composer install

Всё работает идеально.

Через несколько дней к тому же проекту присоединяется другой разработчик.

Он запускает ту же команду.

Но внезапно…

 «Почему у меня установилась другая версия пакета?»

Вы проверяете проект и замечаете два важных файла:

composer.json
composer.lock

На первый взгляд они выглядят похоже.

Оба относятся к зависимостям Composer.

Так в чём же реальная разница?

Давайте разберемся на простой истории.

Знакомьтесь, composer.json

Относитесь к composer.json как к  списку покупок для вашего проекта.

Вы говорите Composer:

«Вот эти пакеты нужны моему проекту».

Например:

{
    "require": {
        "php": "^8.2",
        "laravel/framework": "^12.0",
        "laravel/sanctum": "^4.0"
    }
}

Это  не обязательно указывает Composer точную версию, которую он должен установить.

Например:

laravel/framework: ^12.0

означает:

«Дай мне совместимую версию Laravel 12.x в соответствии с этим ограничением».

Таким образом, у Composer есть определенная гибкость при разрешении (resolve) зависимостей.

Кто же тогда определяет точную версию?

Здесь вступает в игру наш второй файл:

composer.lock

Представьте composer.lock как  итоговый кассовый чек.

Он фиксирует точные версии пакетов, которые разрешил Composer.

Что-то вроде:

laravel/framework → 12.35.1
laravel/sanctum   → 4.2.0
symfony/console   → 7.3.2

То есть связь следующая:

composer.json
      ↓
"Какие пакеты мне нужны?"
      ↓
Разрешение зависимостей в Composer
      ↓
composer.lock
      ↓
"Были отобраны именно эти версии."

Пример из реальной жизни

Представьте, что вы создаете приложение на Laravel сегодня.

В вашем composer.json указано:

"laravel/framework": "^12.0"

Сегодня Composer может разрешить:

Laravel 12.30.0

Месяц спустя Laravel выпускает:

Laravel 12.35.1

Теперь другой разработчик клонирует проект.

Без composer.lock

Composer может разрешить более новую совместимую версию:

12.35.1

На вашей машине:

12.30.0

У другого разработчика:

12.35.1

И теперь вы получаете классическое:

«Но на моей машине всё работает!»

И начинается отладка.

Что происходит при наличии composer.lock?

Предположим, в проекте уже есть:

laravel/framework → 12.30.0

зафиксированная в composer.lock.

Другой разработчик клонирует проект и запускает:

composer install

Composer использует lock-файл и устанавливает зафиксированные в нём версии.

Итог:

Developer 1 → 12.30.0
Developer 2 → 12.30.0
Developer 3 → 12.30.0
Production  → 12.30.0

Гораздо более предсказуемо.

Самое главное отличие

Вот самый простой способ это запомнить:

ФайлНазначениеcomposer.jsonОпределяет требования к зависимостямcomposer.lockСохраняет точные разрешенные версии

Или еще проще:

composer.json  = What I want
composer.lock  = What I got

Что происходит при запуске composer install?

Именно здесь многие разработчики путаются.

Когда composer.lock существует:

composer install

Composer устанавливает версии, зафиксированные в lock-файле.

Поэтому эту команду обычно используют при развертывании существующего проекта.

Например:

git clone project
cd project
composer install

После этого ваша среда получает именно те версии зависимостей, которые ожидает проект.

Что происходит при запуске composer update?

Здесь всё иначе.

Когда вы запускаете:

composer update

Composer смотрит в:

composer.json

и заново разрешает зависимости.

Он может найти более новые версии, удовлетворяющие вашим ограничениям версий.

Затем Composer обновляет:

composer.lock

Например:

Before
Laravel → 12.30.0
composer update

After
Laravel → 12.35.1The lock file changes because the dependency resolution changed.

Типичный сценарий в Laravel

Представьте, что ваш проект содержит:

"require": {
    "laravel/framework": "^12.0",
    "spatie/laravel-permission": "^6.0"
}

Ваш lock-файл может содержать:

laravel/framework → 12.30.0
spatie/laravel-permission → 6.20.0

Теперь Spatie выпускает:

6.21.0

Ваш composer.json не обязательно должен меняться, потому что:

^6.0

уже разрешает совместимые версии 6.x.

Запуск:

composer update

может обновить пакет в composer.lock.

Но запуск:

composer install

обычно сохранит версии, уже зафиксированные в lock-файле.

Нужно ли коммитить composer.lock в Git?

Для большинства  приложений — да.

Например, репозиторий приложения на Laravel обычно содержит:

app/
bootstrap/
config/
database/
resources/
routes/
composer.json
composer.lock

Коммитьте оба:

git add composer.json composer.lock
git commit -m "Update dependencies"

Почему?

Потому что вашей команде нужны предсказуемые версии зависимостей во всех окружениях:

Local
↓
Development
↓
Staging
↓
Production

В идеале все должны использовать одинаковый набор зависимостей.

А как насчет PHP-пакетов?

Существует важное различие между  проектом/приложением и  переиспользуемым пакетом/библиотекой.

Для приложения на Laravel:

composer.lock

обычно закоммичен.

Для переиспользуемого PHP-пакета lock-файл обычно  не коммитится как часть публикуемого контракта зависимостей, поскольку потребителям пакета нужно, чтобы Composer разрешал зависимости в контексте их собственных проектов.

Это различие важно при работе с пакетами, публикуемыми на платформах вроде Packagist.

Создается ли composer.lock автоматически?

Обычно он генерируется, когда Composer разрешает и устанавливает зависимости для проекта.

Например:

composer install

или:

composer update

могут создать или обновить lock-файл в зависимости от состояния проекта и команды.

Типичный проект может начинаться с:

composer.json

Затем после разрешения зависимостей появится:

composer.json
composer.lock
vendor/

Что находится внутри composer.lock?

Lock-файл содержит гораздо больше деталей, чем просто названия пакетов.

Он фиксирует такую информацию, как:

Package name
Version
Source
Distribution
Dependencies
PHP requirements
Hashes / metadata

Упрощенный пример:

{
    "name": "laravel/framework",
    "version": "v12.30.0",
    "require": {
        "php": "^8.2"
    }
}

Реальный lock-файл намного больше, так как сам Laravel зависит от множества других пакетов.

У Laravel больше зависимостей, чем вы думаете

Вы можете написать:

"laravel/framework": "^12.0"

Но Laravel зависит от множества других пакетов Symfony и экосистемы PHP.

Поэтому Composer создает дерево зависимостей:

Your Laravel App
      |
      └── Laravel Framework
             |
             ├── Symfony Console
             ├── Symfony HTTP Foundation
             ├── Symfony Routing
             ├── Symfony Process
             └── ...

composer.lock фиксирует итоговый граф зависимостей.

Это одна из причин, почему этот файл может быть достаточно большим.

Ошибка, которую часто допускают разработчики

Представьте, что вы подтягиваете свежий код:

git pull

и кто-то изменил:

composer.json
composer.lock

Затем вы запускаете:

composer update

просто потому что хотите установить новый пакет.

Это может обновить и  множество других зависимостей.

Вы можете неожиданно обновить пакеты, которые до этого работали корректно.

Для существующего проекта обычно безопаснее использовать:

composer install

когда lock-файл уже содержит нужный набор зависимостей.

Когда следует использовать composer update?

Используйте его, когда вы намеренно хотите  заново разрешить или обновить зависимости.

Например:

composer update

или для точечного обновления, например:

composer update laravel/framework

После проверки и тестирования изменений закоммитьте обновленный lock-файл.

Золотое правило

Запомните эти две команды:

composer install
    ↓
Используйте зафиксированные версии зависимостей.
    ↓
composer update
    ↓
Повторно разрешить зависимости
    ↓
Update composer.lock

И эти два файла:

composer.json
    ↓
Dependency rules / requirements
    ↓
composer.lock
    ↓
Exact resolved dependency versions

Итоговая мысль

Самая простая аналогия такова:

composer.json — это ваш рецепт.

Он говорит:

«Мне нужны Laravel, Sanctum, Spatie Permission и PHP 8.2+».

composer.lock — это точный список выбранных ингредиентов.

Он говорит:

«Вот точные версии каждого ингредиента, с которыми приложение заработало вместе».

Вот почему оба файла важны, но они служат совершенно разным целям.

Когда вы работаете в команде над проектом на Laravel, понимание этой разницы может спасти вас от одной из самых частых проблем с зависимостями:

 «На моей машине всё работает, а на твоей — нет».

Так что в следующий раз, когда увидите:

composer.json
composer.lock

помните:

 JSON определяет правила.
LOCK фиксирует результат.

Удачного кодинга! 🚀