1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
|
<!--
tagline: Modify and extend Composer's functionality
-->
# Setting up and using plugins
## Synopsis
You may wish to alter or expand Composer's functionality with your own. For
example if your environment poses special requirements on the behaviour of
Composer which do not apply to the majority of its users or if you wish to
accomplish something with Composer in a way that is not desired by most users.
In these cases you could consider creating a plugin to handle your
specific logic.
## Creating a Plugin
A plugin is a regular Composer package which ships its code as part of the
package and may also depend on further packages.
### Plugin Package
The package file is the same as any other package file but with the following
requirements:
1. The [type][1] attribute must be `composer-plugin`.
2. The [extra][2] attribute must contain an element `class` defining the
class name of the plugin (including namespace). If a package contains
multiple plugins, this can be an array of class names.
3. You must require the special package called `composer-plugin-api`
to define which Plugin API versions your plugin is compatible with.
Requiring this package doesn't actually include any extra dependencies,
it only specifies which version of the plugin API to use.
> **Note:** When developing a plugin, although not required, it's useful to add
> a require-dev dependency on `composer/composer` to have IDE autocompletion on Composer classes.
The required version of the `composer-plugin-api` follows the same [rules][7]
as a normal package's rules.
The current Composer plugin API version is `2.6.0`.
An example of a valid plugin `composer.json` file (with the autoloading
part omitted and an optional require-dev dependency on `composer/composer` for IDE auto completion):
```json
{
"name": "my/plugin-package",
"type": "composer-plugin",
"require": {
"composer-plugin-api": "^2.0"
},
"require-dev": {
"composer/composer": "^2.0"
},
"extra": {
"class": "My\\Plugin"
}
}
```
### Plugin Class
Every plugin has to supply a class which implements the
[`Composer\Plugin\PluginInterface`][3]. The `activate()` method of the plugin
is called after the plugin is loaded and receives an instance of
[`Composer\Composer`][4] as well as an instance of
[`Composer\IO\IOInterface`][5]. Using these two objects all configuration can
be read and all internal objects and state can be manipulated as desired.
Example:
```php
<?php
namespace phpDocumentor\Composer;
use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
class TemplateInstallerPlugin implements PluginInterface
{
public function activate(Composer $composer, IOInterface $io)
{
$installer = new TemplateInstaller($io, $composer);
$composer->getInstallationManager()->addInstaller($installer);
}
}
```
## Event Handler
Furthermore plugins may implement the
[`Composer\EventDispatcher\EventSubscriberInterface`][6] in order to have its
event handlers automatically registered with the `EventDispatcher` when the
plugin is loaded.
To register a method to an event, implement the method `getSubscribedEvents()`
and have it return an array. The array key must be the
[event name](./scripts.md#event-names)
and the value is the name of the method in this class to be called.
> **Note:** If you don't know which event to listen to, you can run a Composer
> command with the COMPOSER_DEBUG_EVENTS=1 environment variable set, which might
> help you identify what event you are looking for.
```php
public static function getSubscribedEvents()
{
return [
'post-autoload-dump' => 'methodToBeCalled',
// ^ event name ^ ^ method name ^
];
}
```
By default, the priority of an event handler is set to 0. The priority can be
changed by attaching a tuple where the first value is the method name, as
before, and the second value is an integer representing the priority.
Higher integers represent higher priorities. Priority 2 is called before
priority 1, etc.
```php
public static function getSubscribedEvents()
{
return [
// Will be called before events with priority 0
'post-autoload-dump' => ['methodToBeCalled', 1]
];
}
```
If multiple methods should be called, then an array of tuples can be attached
to each event. The tuples do not need to include the priority. If it is
omitted, it will default to 0.
```php
public static function getSubscribedEvents()
{
return [
'post-autoload-dump' => [
['methodToBeCalled' ], // Priority defaults to 0
['someOtherMethodName', 1], // This fires first
]
];
}
```
Here's a complete example:
```php
<?php
namespace Naderman\Composer\AWS;
use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PreFileDownloadEvent;
class AwsPlugin implements PluginInterface, EventSubscriberInterface
{
protected $composer;
protected $io;
public function activate(Composer $composer, IOInterface $io)
{
$this->composer = $composer;
$this->io = $io;
}
public function deactivate(Composer $composer, IOInterface $io)
{
}
public function uninstall(Composer $composer, IOInterface $io)
{
}
public static function getSubscribedEvents()
{
return [
PluginEvents::PRE_FILE_DOWNLOAD => [
['onPreFileDownload', 0]
],
];
}
public function onPreFileDownload(PreFileDownloadEvent $event)
{
$protocol = parse_url($event->getProcessedUrl(), PHP_URL_SCHEME);
if ($protocol === 's3') {
// ...
}
}
}
```
## Plugin capabilities
Composer defines a standard set of capabilities which may be implemented by plugins.
Their goal is to make the plugin ecosystem more stable as it reduces the need to mess
with [`Composer\Composer`][4]'s internal state, by providing explicit extension points
for common plugin requirements.
Capable Plugins classes must implement the [`Composer\Plugin\Capable`][8] interface
and declare their capabilities in the `getCapabilities()` method.
This method must return an array, with the _key_ as a Composer Capability class name,
and the _value_ as the Plugin's own implementation class name of said Capability:
```php
<?php
namespace My\Composer;
use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
use Composer\Plugin\Capable;
class Plugin implements PluginInterface, Capable
{
public function activate(Composer $composer, IOInterface $io)
{
}
public function getCapabilities()
{
return [
'Composer\Plugin\Capability\CommandProvider' => 'My\Composer\CommandProvider',
];
}
}
```
### Command provider
The [`Composer\Plugin\Capability\CommandProvider`][9] capability allows to register
additional commands for Composer:
```php
<?php
namespace My\Composer;
use Composer\Plugin\Capability\CommandProvider as CommandProviderCapability;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Command\BaseCommand;
class CommandProvider implements CommandProviderCapability
{
public function getCommands()
{
return [new Command];
}
}
class Command extends BaseCommand
{
protected function configure(): void
{
$this->setName('custom-plugin-command');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('Executing');
return 0;
}
}
```
Now the `custom-plugin-command` is available alongside Composer commands.
> _Composer commands are based on the [Symfony Console Component][10]._
## Running plugins manually
Plugins for an event can be run manually by the `run-script` command. This works the same way as
[running scripts manually](scripts.md#running-scripts-manually).
If it is another type of plugin the best way to test it is probably using a [path repository](../05-repositories.md#path)
to require the plugin in a test project. If you are developing locally and want to test frequently, you can make sure the path repository uses symlinks, as changes are updated immediately. Otherwise, you'll have to run `rm -rf vendor && composer update`
every time you want to install/run it again.
## Using Plugins
Plugin packages are automatically loaded as soon as they are installed and will
be loaded when Composer starts up if they are found in the current project's
list of installed packages. Additionally all plugin packages installed in the
`COMPOSER_HOME` directory using the Composer global command are loaded before
local project plugins are loaded.
> You may pass the `--no-plugins` option to Composer commands to disable all
> installed plugins. This may be particularly helpful if any of the plugins
> causes errors and you wish to update or uninstall it.
## Plugin Helpers
As of Composer 2, due to the fact that DownloaderInterface can sometimes return Promises
and have been split up in more steps than they used to, we provide a [SyncHelper][11]
to make downloading and installing packages easier.
## Plugin Extra Attributes
A few special plugin capabilities can be unlocked using extra attributes in the plugin's composer.json.
### class
[See above](#plugin-package) for an explanation of the class attribute and how it works.
### plugin-modifies-downloads
Some special plugins need to update package download URLs before they get downloaded.
As of Composer 2.0, all packages are downloaded before they get installed. This means
on the first installation, your plugin is not yet installed when the download occurs,
and it does not get a chance to update the URLs on time.
Specifying `{"extra": {"plugin-modifies-downloads": true}}` in your composer.json will
hint to Composer that the plugin should be installed on its own before proceeding with
the rest of the package downloads. This slightly slows down the overall installation
process however, so do not use it in plugins which do not absolutely require it.
### plugin-modifies-install-path
Some special plugins modify the install path of packages.
As of Composer 2.2.9, you can specify `{"extra": {"plugin-modifies-install-path": true}}`
in your composer.json to hint to Composer that the plugin should be activated as soon
as possible to prevent any bad side-effects from Composer assuming packages are installed
in another location than they actually are.
### plugin-optional
Because Composer plugins can be used to perform actions which are necessary for installing
a working application, like modifying which path files get stored in, skipping required
plugins unintentionally can result in broken applications. So, in non-interactive mode,
Composer will fail if a new plugin is not listed in ["allow-plugins"](../06-config.md#allow-plugins)
to force users to decide if they want to execute the plugin, to avoid silent failures.
As of Composer 2.5.3, you can use the setting `{"extra": {"plugin-optional": true}}` on
your plugin, to tell Composer that skipping the plugin has no catastrophic consequences,
and it can safely be disabled in non-interactive mode if it is not yet listed in
"allow-plugins". The next interactive run of Composer will still prompt users to choose if
they want to enable or disable the plugin.
## Plugin Autoloading
Due to plugins being loaded by Composer at runtime, and to ensure that plugins which
depend on other packages can function correctly, a runtime autoloader is created whenever
a plugin is loaded. That autoloader is only configured to load with the plugin dependencies,
so you may not have access to all the packages which are installed.
## Static Analysis support
As of Composer 2.3.7 we ship a `phpstan/rules.neon` PHPStan config file, which provides additional error checking when working on Composer plugins.
### Usage with [PHPStan Extension Installer][13]
The necessary configuration files are automatically loaded, in case your plugin projects declares a dependency to `phpstan/extension-installer`.
### Alternative manual installation
To make use of it, your Composer plugin project needs a [PHPStan config file][12], which includes the `phpstan/rules.neon` file:
```neon
includes:
- vendor/composer/composer/phpstan/rules.neon
// your remaining config..
```
[1]: ../04-schema.md#type
[2]: ../04-schema.md#extra
[3]: https://github.com/composer/composer/blob/main/src/Composer/Plugin/PluginInterface.php
[4]: https://github.com/composer/composer/blob/main/src/Composer/Composer.php
[5]: https://github.com/composer/composer/blob/main/src/Composer/IO/IOInterface.php
[6]: https://github.com/composer/composer/blob/main/src/Composer/EventDispatcher/EventSubscriberInterface.php
[7]: ../01-basic-usage.md#package-versions
[8]: https://github.com/composer/composer/blob/main/src/Composer/Plugin/Capable.php
[9]: https://github.com/composer/composer/blob/main/src/Composer/Plugin/Capability/CommandProvider.php
[10]: https://symfony.com/doc/current/components/console.html
[11]: https://github.com/composer/composer/blob/main/src/Composer/Util/SyncHelper.php
[12]: https://phpstan.org/config-reference#multiple-files
[13]: https://github.com/phpstan/extension-installer#usage
|