Setting up and using 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 in your package file.

See the next chapter for an instruction how to create Custom Installers.

Every Custom Installer defines which type 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 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:

{
    "name": "phpdocumentor/template-responsive",
    "type": "phpdocumentor-template",
    "require": {
        "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 plugin package.

Creating an Installer#

A Custom Installer is defined as a class that implements the Composer\Installer\InstallerInterface and is usually distributed in a Composer Plugin.

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.
  3. The Installer class, e.g.: My\Project\Composer\Installer.php, containing a class that implements Composer\Installer\InstallerInterface.

composer.json#

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 array of class names.

Example:

{
    "name": "phpdocumentor/template-installer-plugin",
    "type": "composer-plugin",
    "license": "MIT",
    "autoload": {
        "psr-0": {"phpDocumentor\\Composer": "src/"}
    },
    "extra": {
        "class": "phpDocumentor\\Composer\\TemplateInstallerPlugin"
    },
    "require": {
        "composer-plugin-api": "1.0.0"
    }
}

The Plugin class#

The class defining the Composer plugin must implement the Composer\Plugin\PluginInterface. 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 class that executes the custom installation should implement the Composer\Installer\InstallerInterface (or extend another installer that implements that interface). It defines the type string as it will be recognized by packages that will use this installer in the supports() method.

NOTE: choose your type 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):

Example:

namespace phpDocumentor\Composer;

use Composer\Package\PackageInterface;
use Composer\Installer\LibraryInstaller;

class TemplateInstaller extends LibraryInstaller
{
    /**
     * {@inheritDoc}
     */
    public function getPackageBasePath(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 class to strip a prefix (phpdocumentor/template-) and use the remaining part to assemble a completely different installation path.

Instead of being installed in /vendor any package installed using this Installer will be put in the /data/templates/<stripped name> folder.

Found a typo? Something is wrong in this documentation? Just fork and edit it!