Разберёмся с Composer

В этой статье я постараюсь раскрыть некоторые моменты, которые часто бывают непонятны начинающим осваивать Composer пользователям. Я не буду рассказывать что такое Composer и как установить. Такой информации уже предостаточно. А вот что такое composer.lock файл или почему команда install не устанавливает указанный пакет смогут ответить не все. Поэтому давайте пробежимся по этим вопросам.

При рассмотрении возможностей Composer я буду ссылаться на фреймворк Laravel, так как пишу для пользователей MODX, которые как и я хотели бы познакомится с этим фреймворком поближе.

Минимальные требования — понимать, что такое менеджеры зависимостей и в частности Composer, и разобраться с его установкой.

Практически все современные фреймворки используют менеджеры зависимостей. И Laravel не исключение. Работа этого фреймворка построена на использовании Composer от установки до подключения пакетов. Данные для управления зависимостями Composer берёт из файла composer.json, который можно найти в корне проекта, в нашем случае сайта Laravel. В том случае, если вы клонировали исходники Laravel с GibHub, то необходимо провести её установку (будем считать Laravel женского рода ? ). Для этого в командной строке нужно выполнить одну единственную команду:

// Если Composer установлен глобально 
composer install
// Если нет
php composer.phar install

Composer install

Давайте теперь попробуем понять, что происходит при выполнении этой команды. Мы уже говорили, что все данные Composer берёт из файла composer.json. Но в случае с командой install Composer сначала ищет файл composer.lock. Это очень важный момент. Для чего это нужно мы рассмотрим дальше. А так как в репозитории Laravel этого файла нет, то Composer зачитывает информацию из composer.json. Вот этот файл:

{
    "name": "laravel/laravel",
    "description": "The Laravel Framework.",
    "keywords": ["framework", "laravel"],
    "license": "MIT",
    "type": "project",
    "require": {
        "php": ">=5.6.4",
        "laravel/framework": "5.3.*"
    },
    "require-dev": {
        "fzaninotto/faker": "~1.4",
        "mockery/mockery": "0.9.*",
        "phpunit/phpunit": "~5.0",
        "symfony/css-selector": "3.1.*",
        "symfony/dom-crawler": "3.1.*"
    },
    "autoload": {
        "classmap": [
            "database"
        ],
        "psr-4": {
            "App\\": "app/"
        }
    },
    "autoload-dev": {
        "classmap": [
            "tests/TestCase.php"
        ]
    },
    "scripts": {
        "post-root-package-install": [
            "php -r \"file_exists('.env') || copy('.env.example', '.env');\""
        ],
        "post-create-project-cmd": [
            "php artisan key:generate"
        ],
        "post-install-cmd": [
            "Illuminate\\Foundation\\ComposerScripts::postInstall",
            "php artisan optimize"
        ],
        "post-update-cmd": [
            "Illuminate\\Foundation\\ComposerScripts::postUpdate",
            "php artisan optimize"
        ]
    },
    "config": {
        "preferred-install": "dist"
    }
}

Пакеты, которые Composer должен установить перечислены в секциях «require» и «require-dev». Это так называемые зависимости. Для каждого пакета указана версия. У многих, кстати, часто возникает вопрос по назначению этих секций. Мы это рассмотрим дальше. А пока вернёмся к установке.

Composer последовательно устанавливает указанные пакеты и разрешает зависимости уже самих пакетов. Поясню. Как правило, у пакетов есть свои зависимости и эти зависимости указываются также в composer.jsonсамого пакета. Например, посмотрим на исходник пакета fzaninotto/faker, указанного в секции «require-dev». У него в корне можно найти файл composer.json, в котором указаны зависимости для этого пакета.

После того, как все пакеты будут установлены, Composer сформирует файл composer.lock, в котором зафиксирует установленные версии пакетов. Для чего это нужно? Если вы ведёте коллективную разработку, то ваш коллега скачав (pull) проект с гит-репозитория должен получить тоже окружение и версии всех пакетов, что и у вас. Т.е. если у вас в composer.json версия для пакета жёстко не зафиксирована, а указана через маску

{
    "require": {
        "vendor/somepackage": "1.*"
    }
}

и, например, на момент разработки у вас установлена версия 1.1, то в composer.lock будет указана версия 1.1. Тогда ваш коллега при установке получит ту же версию пакета, которая установлена у вас, даже если вышла новая версия 1.2. Именно поэтому практически всегда проекты имеют файл composer.lock в репозитории. Ещё один плюс — в этом файле указаны все зависимости всех пакетов, поэтому при разворачивании проекта не нужно заново определять все эти зависимости, что существенно экономит время.

Ещё раз. Выполняя команду install Composer проверяет наличие файла composer.lock. Если находит, то устанавливает пакеты согласно указанным версиям из него. Если не находит, то обращается к composer.json, устанавливает указанные пакеты и их зависимости и создаёт файл composer.lock.

Composer update

Команда update используется для обновления зависимостей. Она использует только composer.json. Например, если для пакета указана версия с маской «1.*» или «~2.3.0» или «dev-master», то Composer проверит есть ли более новая версия в репозитории для установленного пакета, и если есть, то установит её. И так для каждого пакета. После этого Composer обновит файл composer.lock.

Мы видим, что при установке проекта в случае, если файл composer.lock отсутствует, команды install и update действуют одинаково. Тогда какую же команду использовать? Запомните, при первой установке, а также когда вы загрузили из репозитория новую версию вашего проекта, всегда используйте комманду install, которая проверит изменения в файле composer.lock. А вот когда вам в рабочем проекте вам нужно установить новые пакеты, обновить установленные или удалить ненужные, то вам понадобится команда update (можно использовать альтернативные команды require и remove, которые будут рассмотрены ниже).

Команда update имеет следующий синтаксис:

// Обновление всех пакетов. Если вы удалили пакет из composer.json, то он будет деинсталирован.
composer update
// Обновление или установка указанных пакетов. Если вы укажите пакет, который удалили из composer.json, то он будет деинсталирован.
composer update vendor/package vendor/package2
// Обновление всех пакетов указанного вендора.
composer update vendor/*

Надеюсь вам всё понятно. Если да, то кивните.

Вышеописанные команды используются при работе с файлом composer.json. Но у Composer есть и другой способ работы — без использования этого файла.

Composer require

Эту команда используется, когда нужно установить один или пару пакетов. В консоли достаточно набрать

composer require vendor/package:version

и Composer сделает всю работу — скачает указанный пакет, добавит запись в composer.json в соответствующую секцию и обновит файл composer.lock.

// Добавить пакет monolog версии 1.11 и выше до версии 2.0 в секцию "require"
composer require monolog/monolog:~1.11
// Добавить пакет phpunit версии 5.0 и выше до версии 6.0 в секцию "require-dev"
composer require phpunit/phpunit:^5.0 --dev

Можно указывать несколько пакетов подряд.

Composer remove

Команда для удаления пакетов. Она также удаляет запись из соответствующей секции зависимостей в composer.json и обновляет composer.lock. Т.е. нам ничего делать не нужно.

composer remove vendor/package vendor/package2

Никогда не удаляйте пакеты вручную из директории vendor! Это сломает зависимости Composer.

Для начала этого вполне хватит, чтобы понимать как работает Composer. В дальнейшем, когда неуверенность пройдёт, освоить другие команды будет уже проще. А теперь как и обещал расскажу чем отличаются секции «require» и «require-dev».

Зависимости «require» и «require-dev»

Давайте разберёмся для чего они нужны. В секции «require» указываются пакеты, которые необходимы для работы проекта (сайта или пакета). Это могут быть комментарии, админка, функции для работы с датами и т.п. В секции «require-dev» указывают пакеты, необходимые только для разработки и не влияющие на работу самого проекта.

Но внимательные заметят, что при установке или обновлении Composer устанавливает пакеты из обоих секций. Тогда возникает вопрос — а зачем их делить? Всё просто. Это разделение условное и им управляете вы сами. Composer сам не может определить, где он запускается — на сервере разработки или продакшене. Поэтому, при разворачивании проекта на production сервере, нужно указать Composer, чтобы он не устанавливал пакеты для разработки из секции «require-dev».

composer install --no-dev

И при обновлении также указывать ключ --no-dev

composer update --no-dev

Ещё очень важное замечание. Пакеты из секции «require-dev» устанавливаются только для корневого пакета. Т.е. у зависимых пакетов Composer эту секцию не учитывает. Логика простая — если вы хотите доработать какой-то пакет и вам нужны пакеты для разработки, то установите его напрямую.

Версии

При указании зависимостей в файле composer.json необходимо указать версию пакета. Давайте рассмотрим варианты определения этих версий.

Точная версия

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

"vendor/package": "1.0.2"
Диапазон

С помощью операторов сравнения вы можете указать диапазоны допустимых версий. Для этого можно использовать следующие операторы: >>=<<= и !=.

Вы можете определить несколько диапазонов. Диапазоны, разделенные пробелом или запятой, будут рассматриваться как логические «И». Две вертикальные черты (||) будут рассматриваться как логическое «ИЛИ». «И» имеет более высокий приоритет чем «ИЛИ».

"vendor/package1": ">1.0"
"vendor/package2": ">=1.0 <2.0"
"vendor/package3": ">=1.0 <1.1 || >=1.2"
Диапазон через дефис

Определяет диапазон с минимальной и максимальной границей версий. Если в правой части указать неполную версию, то она будет дополнена подстановочным знаком «*» (wildcard).

"vendor/package": "1.0.0 - 2.0.0" // эквивалентно ">=1.0.0 <=2.0.0"
# Неполная версия
"vendor/package": "1.0.0 - 2.0"  //эквивалентно ">=1.0.0 <2.1"  так как 2.0 превращается в 2.0.*
Подстановка (*)

Для определения любых значений можно использовать шаблон со знаком *.

"vendor/package": "1.0.*" // Эквивалентно ">=1.0.0 <1.1"
"vendor/package": "*" // Устанавливает последнюю версию. Данный вариант использовать не рекомендуется!
Тильда (~)

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

"vendor/package": "~1.2.3" // Эквивалентно ">=1.2.3 <1.3.0"
"vendor/package": "~1.2" // Эквивалентно ">=1.2 <2.0.0"
Каретка (^)

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

"vendor/package": "^1.2.3" // Эквивалентно ">=1.2.3 <2.0.0"
Ветка git

Composer в большой степени ориентирован на использование систем контроля версий, таких как git. Поэтому в качестве номера версии ему можно указать название ветки разработки используя используя специальный префикс dev-. Это может быть полезно при разработке, но в будущем может стать причиной ошибок.

"vendor/package": "dev-master",
"vendor/package2": "dev-hotfixes"
Коммит

Кроме указания ветки Composer позволяет определить конкретный коммит через #<ref>.

"vendor/package": "dev-master#2eb0c0978d290a1c45346a1955188929cb4e5db7",

Заключение

Надеюсь, эти базовые знания помогут быстрее вникнуть в принцип работы Composer.