Поговорим про обновление зависимостей. Для обновления всех зависимостей нужно выполнить команду composer update. Чтобы выполнить обновление конкретной зависимости — composer update vendor-name/project-name. А вот то, как будет происходить обновление, зависит от содержимого composer.json.

Рассмотрим возможные варианты:

require {
  'package1': "*",
  'package2': "1.3.5",
  'package3': ">2.3.4",
  'package4': "~3.9",
  'package5': "^1.2"
}

* означает, что можно ставить любую версию библиотеки. После выполнения команды обновления, в папке vendor окажется последняя доступная версия package1.

1.3.5 — конкретный номер. Если версия библиотеки жёстко зафиксирована, никакая команда не сможет обновить её.

Кроме этих вариантов существует масса других способов указать зависимость, часть из них вы видите в списке выше. Все они подробно описаны в официальном руководстве, поэтому не будем повторяться. На текущем этапе для вас и так слишком много новой информации, которую нужно усвоить, а тонкости указания зависимостей не такая важная тема сейчас.

Lock-файл

На предыдущем шаге каждая новая установка зависимостей приводила сначала к созданию, а потом и обновлению lock-файла composer.lock.

Попытаемся разобраться в смысле этого файла. Как мы помним, в файле composer.json указываются зависимости, и мы рассмотрели то, как их устанавливать и обновлять. Но также мы помним, что у каждой зависимости могут быть свои собственные зависимости, которые также обновляются и так до бесконечности. Зависимости зависимостей называются транзитивными, и с ними не всё так просто. Настолько не просто, что существует понятие "ад зависимостей" (dependency hell).

Проблема заключается в том, что мы никак не фиксируем версии транзитивных зависимостей. Предположим, что в нашем пакете есть зависимость A с зафиксированной версией 1.3.2, у которой в зависимостях стоит пакет B, причём с версией *. В такой ситуации (и без lock-файла), composer install поставил бы версию зависимости A указанной версии, но того же самого нельзя сказать про пакет B. Composer поставит последнюю доступную версию из репозитория. Такое поведение не детерминировано. Если создатель обновит B так, что нарушится обратная совместимость, наш проект просто сломается, так как перестанет работать A. Фактически, если бы мы полгода не заходили в проект, а затем зашли и поставили зависимости заново (удалив папку vendor или выполнив новое клонирование), то с почти вероятностью 100% ничего не заработает. Как правило, пакеты обновляются часто, и какой-нибудь из них обязательно изменит мажорную версию за столь большой срок.

Очевидный, но не рабочий выход из данной ситуации — вручную отслеживать зависимости всех зависимостей и явно прописывать их версии в composer.json. Такой способ сработает, но даже в проекте, который содержит пять зависимостей, транзитивных зависимостей будет сотни! Вдумайтесь в эту цифру. Я уже не говорю про то, что пакеты обновляются и меняются. Такую ситуацию невозможно контролировать, она выльется в то, что зависимости просто перестанут обновляться.

Другой выход — требовать того, чтобы создатели всех библиотек всегда жёстко указывали версии. По понятным (человеческим) причинам так не сработает, а автоматизация этого процесса привела бы к полному параличу.

И тут на сцену выходит lock-файл. По сути он представляет собой автоматизированное решение первого способа. Его содержимое выглядит примерно так:

{
    "_readme": [
        "This file locks the dependencies of your project to a known state",
        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
        "This file is @generated automatically"
    ],
    "content-hash": "ab2dac1e4b8d91d81b2295ca726e9499",
    "packages": [
        {
            "name": "tightenco/collect",
            "version": "v5.5.27",
            "source": {
                "type": "git",
                "url": "https://github.com/tightenco/collect.git",
                "reference": "07d58f7f663d5033a08541f9c481d33ad3f514a5"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/tightenco/collect/zipball/07d58f7f663d5033a08541f9c481d33ad3f514a5",
                "reference": "07d58f7f663d5033a08541f9c481d33ad3f514a5",
                "shasum": ""
            }
        }
    ]
}

Первый запуск установки зависимостей сформирует этот файл. Туда записываются все установленные зависимости, в том числе транзитивные с их версиями. При дальнейших запусках composer install всегда ставится то, что указано в lock-файле, даже если удалить папку vendor или в Packagist добавятся новые версии пакетов. Повторный запуск через любой промежуток времени приведёт к тому же результату. Теперь всегда можно быть уверенным — если заработало сейчас, то заработает и потом (и не только у нас).

Наличие lock-файла никак не влияет на поведение команды update для прямых зависимостей. Если пакет, указанный в composer.json, обновился и может быть обновлён в соответствии с тем, как указана его версия, то загрузится новая версия, а lock-файл обновится автоматически. После этого нужно только не забыть зафиксировать его изменение в репозитории.

composer logic

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

Самостоятельная работа

  • Склонируйте репозиторий php-package, а затем выполните внутри него команду composer update. Изучите вывод команды git diff.
  • Попробуйте изменить версию любого пакета в composer.json и выполнить его установку. Доступные версии можно найти на сайте Packagist.

В результате можно увидеть, что обновился файл composer.lock

Для продолжения нужно перейти в курс и вступить в него.