From 98e5eabf759baf103e657e6e098ff48a0f3b55a7 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 5 Sep 2013 20:08:17 +0200 Subject: [PATCH] Document how to write and use plugins --- doc/articles/custom-installers.md | 68 +++++++++----- doc/articles/plugins.md | 147 ++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+), 22 deletions(-) create mode 100644 doc/articles/plugins.md diff --git a/doc/articles/custom-installers.md b/doc/articles/custom-installers.md index 1eb55436e..7c7117248 100644 --- a/doc/articles/custom-installers.md +++ b/doc/articles/custom-installers.md @@ -29,8 +29,8 @@ An example use-case would be: > phpDocumentor features Templates that need to be installed outside of the > default /vendor folder structure. As such they have chosen to adopt the -> `phpdocumentor-template` [type][1] and create a Custom Installer to send -> these templates to the correct folder. +> `phpdocumentor-template` [type][1] and create a plugin providing the Custom +> Installer to send these templates to the correct folder. An example composer.json of such a template package would be: @@ -38,23 +38,24 @@ An example composer.json of such a template package would be: "name": "phpdocumentor/template-responsive", "type": "phpdocumentor-template", "require": { - "phpdocumentor/template-installer": "*" + "phpdocumentor/template-installer-plugin": "*" } } > **IMPORTANT**: to make sure that the template installer is present at the > time the template package is installed, template packages should require -> the installer package. +> the plugin package. ## Creating an Installer A Custom Installer is defined as a class that implements the -[`Composer\Installer\InstallerInterface`][3] and is contained in a Composer -package that has the [type][1] `composer-installer`. +[`Composer\Installer\InstallerInterface`][3] and is usually distributed in a +Composer Plugin. -A basic Installer would thus compose of two files: +A basic Installer Plugin would thus compose of three files: 1. the package file: composer.json +2. The Plugin class, e.g.: `My\Project\Composer\Plugin.php`, containing a class that implements `Composer\Plugin\PluginInterface`. 2. The Installer class, e.g.: `My\Project\Composer\Installer.php`, containing a class that implements `Composer\Installer\InstallerInterface`. ### composer.json @@ -62,35 +63,57 @@ A basic Installer would thus compose of two files: The package file is the same as any other package file but with the following requirements: -1. the [type][1] attribute must be `composer-installer`. +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 installer (including namespace). If a package contains - multiple installers this can be array of class names. + class name of the plugin (including namespace). If a package contains + multiple plugins this can be array of class names. Example: { - "name": "phpdocumentor/template-installer", - "type": "composer-installer", + "name": "phpdocumentor/template-installer-plugin", + "type": "composer-installer-plugin", "license": "MIT", "autoload": { "psr-0": {"phpDocumentor\\Composer": "src/"} }, "extra": { - "class": "phpDocumentor\\Composer\\TemplateInstaller" + "class": "phpDocumentor\\Composer\\TemplateInstallerPlugin" } } -### The Custom Installer class +### The Plugin class -The class that executes the custom installation should implement the -[`Composer\Installer\InstallerInterface`][3] (or extend another installer that -implements that interface). +The class defining the Composer plugin must implement the +[`Composer\Plugin\PluginInterface`][3]. It can then register the Custom +Installer in its `activate()` method. The class may be placed in any location and have any name, as long as it is autoloadable and matches the `extra.class` element in the package definition. -It will also define the [type][1] string as it will be recognized by packages -that will use this installer in the `supports()` method. + +Example: + + 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); + } + } + +### The Custom Installer class + +The class that executes the custom installation should implement the +[`Composer\Installer\InstallerInterface`][4] (or extend another installer that +implements that interface). It defines the [type][1] string as it will be +recognized by packages that will use this installer in the `supports()` method. > **NOTE**: _choose your [type][1] name carefully, it is recommended to follow > the format: `vendor-type`_. For example: `phpdocumentor-template`. @@ -146,7 +169,7 @@ Example: } The example demonstrates that it is quite simple to extend the -[`Composer\Installer\LibraryInstaller`][4] class to strip a prefix +[`Composer\Installer\LibraryInstaller`][5] class to strip a prefix (`phpdocumentor/template-`) and use the remaining part to assemble a completely different installation path. @@ -155,5 +178,6 @@ different installation path. [1]: ../04-schema.md#type [2]: ../04-schema.md#extra -[3]: https://github.com/composer/composer/blob/master/src/Composer/Installer/InstallerInterface.php -[4]: https://github.com/composer/composer/blob/master/src/Composer/Installer/LibraryInstaller.php +[3]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/PluginInterface.php +[4]: https://github.com/composer/composer/blob/master/src/Composer/Installer/InstallerInterface.php +[5]: https://github.com/composer/composer/blob/master/src/Composer/Installer/LibraryInstaller.php diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md new file mode 100644 index 000000000..57296f8fe --- /dev/null +++ b/doc/articles/plugins.md @@ -0,0 +1,147 @@ + + +# 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 array of class names. + +Additionally you must require the special package called `composer-plugin-api` +to define which composer API versions your plugin is compatible with. The +current composer plugin API version is 1.0.0. + +For example + + { + "name": "my/plugin-package", + "type": "composer-plugin", + "require": { + "composer-plugin-api": "1.0.0" + } + } + +### 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: + + 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. + +The events available for plugins are: + +* **COMMAND**, is called at the beginning of all commands that load plugins. + It provides you with access to the input and output objects of the program. +* **PRE_FILE_DOWNLOAD**, is triggered before files are downloaded and allows + you to manipulate the `RemoteFilesystem` object prior to downloading files + based on the URL to be downloaded. + +Example: + + 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 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') { + $awsClient = new AwsClient($this->io, $this->composer->getConfig()); + $s3RemoteFilesystem = new S3RemoteFilesystem($this->io, $event->getRemoteFilesystem()->getOptions(), $awsClient); + $event->setRemoteFilesystem($s3RemoteFilesystem); + } + } + } + +## 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 commands. This may be particularly helpful if any of the plugins +> causes errors and you wish to update or uninstall it. + +[1]: ../04-schema.md#type +[2]: ../04-schema.md#extra +[3]: https://github.com/composer/composer/blob/master/src/Composer/Plugin/PluginInterface.php +[4]: https://github.com/composer/composer/blob/master/src/Composer/Composer.php +[5]: https://github.com/composer/composer/blob/master/src/Composer/IO/IOInterface.php +[6]: https://github.com/composer/composer/blob/master/src/Composer/EventDispatcher/EventSubscriberInterface.php