Browse Source

Document how to write and use plugins

Nils Adermann 11 years ago
parent
commit
98e5eabf75
2 changed files with 193 additions and 22 deletions
  1. 46 22
      doc/articles/custom-installers.md
  2. 147 0
      doc/articles/plugins.md

+ 46 - 22
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

+ 147 - 0
doc/articles/plugins.md

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