diff --git a/doc/articles/custom-installers.md b/doc/articles/custom-installers.md new file mode 100644 index 000000000..e136ebacd --- /dev/null +++ b/doc/articles/custom-installers.md @@ -0,0 +1,156 @@ +# Custom installers + +## Synopsis + +At times it may be necessary for a package to require additional actions during +installation, such as installing packages outside of the default `vendor` +library. + +In these cases you could consider creating a Custom Installer to handle your +specific logic. + +## Calling a Custom Installer + +Suppose that your project already has a Custom Installer for specific modules +then invoking that installer is a matter of defining the correct [type][1] in +your package file. + +> _See the next chapter for an instruction how to create Custom Installers._ + +Every Custom Installer defines which [type][1] string it will recognize. Once +recognized it will completely override the default installer and only apply its +own logic. + +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. + +An example composer.json of such a template would be: + + { + "name": "phpdocumentor/template-responsive", + "type": "phpdocumentor-template" + } + +With the package definition shown in the example will any project that includes +this package try to find an appropriate installer and use that to execute this +[type][1]. + +> **IMPORTANT**: the host project will not recognize the [type][1] if the +> installer's package is not included. Thus a composer.json consuming a template +> package will always need to _require_ the template's installer as well. + +## Creating an Installer + +A Custom Installer is a 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`. + +A basic Installer would thus compose of two files: + +1. the package file: composer.json +2. The Installer class, i.e.: \Composer\Installer\MyInstaller.php + +> **NOTE**: _The namespace does not need to be \Composer\Installer, it may be +> anything that you would want._ + +### composer.json + +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`. +2. the [extra][2] attribute must contain an element `class` defining the + class name of the installer (including namespace). + +Example: + + { + "name": "phpdocumentor/template-installer", + "type": "composer-installer", + "license": "MIT", + "autoload": { + "psr-0": {"phpDocumentor\\Composer": "src/"} + }, + "extra": {"class": "\\phpDocumentor\\Composer\\TemplateInstaller"} + } + +### The Custom Installer 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 may be placed in any location and have any name, as long as it +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 +> the format: `vendor-type`_. For example: `phpdocumentor-template`. + +The InstallerInterface class defines the following methods (please see the +source for the exact signature): + +* **supports()**, here you test whether the passed [type][1] matches the name + that you declared for this installer (see the example). +* **isInstalled()**, determines whether a supported package is installed or not. +* **install()**, here you can determine the actions that need to be executed + upon installation. +* **update()**, here you define the behavior that is required when Composer is + invoked with the update argument. +* **uninstall()**, here you can determine the actions that need to be executed + when the package needs to be removed. +* **getInstallPath()**, this method should return the location where the + package is to be installed, _relative from the location of composer.json._ + +Example: + + namespace phpDocumentor\Composer; + + use Composer\Package\PackageInterface; + use Composer\Installer\LibraryInstaller; + + class TemplateInstaller extends LibraryInstaller + { + /** + * {@inheritDoc} + */ + public function getInstallPath(PackageInterface $package) + { + $prefix = substr($package->getPrettyName(), 0, 23); + if ('phpdocumentor/template-' != $prefix) { + throw new \InvalidArgumentException( + 'Unable to install template, phpdocumentor templates ' + .'should always start their package name with ' + .'"phpdocumentor/template-"' + ); + } + + return 'data/templates/'.substr($package->getPrettyName(), 23); + } + + /** + * {@inheritDoc} + */ + public function supports($packageType) + { + return ('phpdocumentor-template' === $packageType); + } + } + +The example demonstrates that it is quite simple to extend the +[\Composer\Installer\LibraryInstaller][4] class to strip a prefix +(`phpdocumentor/template-`) and use the remaining part to assemble a completely +different installation path. + +> _Instead of installing to `/vendor` will any package installed using this +> Installer be put in the `/data/templates/` folder._ + +[1]: http://getcomposer.org/doc/04-schema.md#type +[2]: http://getcomposer.org/doc/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 \ No newline at end of file