1
0
Fork 0

Document how to write and use plugins

pull/2179/head
Nils Adermann 2013-09-05 20:08:17 +02:00
parent 92b1ee2f7a
commit 98e5eabf75
2 changed files with 194 additions and 23 deletions

View File

@ -29,8 +29,8 @@ An example use-case would be:
> phpDocumentor features Templates that need to be installed outside of the > phpDocumentor features Templates that need to be installed outside of the
> default /vendor folder structure. As such they have chosen to adopt the > default /vendor folder structure. As such they have chosen to adopt the
> `phpdocumentor-template` [type][1] and create a Custom Installer to send > `phpdocumentor-template` [type][1] and create a plugin providing the Custom
> these templates to the correct folder. > Installer to send these templates to the correct folder.
An example composer.json of such a template package would be: 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", "name": "phpdocumentor/template-responsive",
"type": "phpdocumentor-template", "type": "phpdocumentor-template",
"require": { "require": {
"phpdocumentor/template-installer": "*" "phpdocumentor/template-installer-plugin": "*"
} }
} }
> **IMPORTANT**: to make sure that the template installer is present at the > **IMPORTANT**: to make sure that the template installer is present at the
> time the template package is installed, template packages should require > time the template package is installed, template packages should require
> the installer package. > the plugin package.
## Creating an Installer ## Creating an Installer
A Custom Installer is defined as a class that implements the A Custom Installer is defined as a class that implements the
[`Composer\Installer\InstallerInterface`][3] and is contained in a Composer [`Composer\Installer\InstallerInterface`][3] and is usually distributed in a
package that has the [type][1] `composer-installer`. 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 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`. 2. The Installer class, e.g.: `My\Project\Composer\Installer.php`, containing a class that implements `Composer\Installer\InstallerInterface`.
### composer.json ### 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 The package file is the same as any other package file but with the following
requirements: 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 2. the [extra][2] attribute must contain an element `class` defining the
class name of the installer (including namespace). If a package contains class name of the plugin (including namespace). If a package contains
multiple installers this can be array of class names. multiple plugins this can be array of class names.
Example: Example:
{ {
"name": "phpdocumentor/template-installer", "name": "phpdocumentor/template-installer-plugin",
"type": "composer-installer", "type": "composer-installer-plugin",
"license": "MIT", "license": "MIT",
"autoload": { "autoload": {
"psr-0": {"phpDocumentor\\Composer": "src/"} "psr-0": {"phpDocumentor\\Composer": "src/"}
}, },
"extra": { "extra": {
"class": "phpDocumentor\\Composer\\TemplateInstaller" "class": "phpDocumentor\\Composer\\TemplateInstallerPlugin"
}
}
### The Plugin class
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.
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 Custom Installer class
The class that executes the custom installation should implement the The class that executes the custom installation should implement the
[`Composer\Installer\InstallerInterface`][3] (or extend another installer that [`Composer\Installer\InstallerInterface`][4] (or extend another installer that
implements that interface). 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.
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.
> **NOTE**: _choose your [type][1] name carefully, it is recommended to follow > **NOTE**: _choose your [type][1] name carefully, it is recommended to follow
> the format: `vendor-type`_. For example: `phpdocumentor-template`. > the format: `vendor-type`_. For example: `phpdocumentor-template`.
@ -146,7 +169,7 @@ Example:
} }
The example demonstrates that it is quite simple to extend the 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 (`phpdocumentor/template-`) and use the remaining part to assemble a completely
different installation path. different installation path.
@ -155,5 +178,6 @@ different installation path.
[1]: ../04-schema.md#type [1]: ../04-schema.md#type
[2]: ../04-schema.md#extra [2]: ../04-schema.md#extra
[3]: https://github.com/composer/composer/blob/master/src/Composer/Installer/InstallerInterface.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/LibraryInstaller.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

147
doc/articles/plugins.md Normal file
View File

@ -0,0 +1,147 @@
<!--
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 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