1
0
Fork 0
composer/doc/articles/plugins.md

13 KiB

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 attribute must be composer-plugin.
  2. The extra 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 as a normal package's rules.

The current Composer plugin API version is 2.3.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):

{
    "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. The activate() method of the plugin is called after the plugin is loaded and receives an instance of Composer\Composer as well as an instance of Composer\IO\IOInterface. Using these two objects all configuration can be read and all internal objects and state can be manipulated as desired.

Example:

<?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 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 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.

public static function getSubscribedEvents()
{
    return array(
        '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.

public static function getSubscribedEvents()
{
    return array(
        // Will be called before events with priority 0
        'post-autoload-dump' => array('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.

public static function getSubscribedEvents()
{
    return array(
        'post-autoload-dump' => array(
            array('methodToBeCalled'      ), // Priority defaults to 0
            array('someOtherMethodName', 1), // This fires first
        )
    );
}

Here's a complete example:

<?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 array(
            PluginEvents::PRE_FILE_DOWNLOAD => array(
                array('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's internal state, by providing explicit extension points for common plugin requirements.

Capable Plugins classes must implement the Composer\Plugin\Capable 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

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 array(
            'Composer\Plugin\Capability\CommandProvider' => 'My\Composer\CommandProvider',
        );
    }
}

Command provider

The Composer\Plugin\Capability\CommandProvider capability allows to register additional commands for Composer:

<?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 array(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');
    }
}

Now the custom-plugin-command is available alongside Composer commands.

Composer commands are based on the Symfony Console Component.

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.

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 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 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" 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

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, which includes the phpstan/rules.neon file:

includes:
	- vendor/composer/composer/phpstan/rules.neon

// your remaining config..