插件的设置和使用#

概要#

你可能希望用自己的代码来修改或扩展 Composer 的功能。例如,如果你的环境对 Composer 的行为提出了特殊要求,而这些要求不适用于大多数用户,或者如果你希望用大多数用户不希望的方式来完成某些 Composer 的功能。

在这种情况下,你可以考虑创建一个插件来处理你特定的逻辑。

创建插件#

插件是一个普通的 Composer 包,它将代码作为包的一部分提供,并且可能还依赖于其他包。

插件包#

包文件与任何其他包文件相同,但具有以下要求

  1. type 属性必须为 composer-plugin
  2. extra 属性必须包含一个元素 class 定义插件的类名(包括命名空间)。如果一个包包含多个插件,这可以是一个类名数组。
  3. 你必须需要一个名为 composer-plugin-api 的特殊包来定义你的插件兼容的哪个插件 API 版本。需要这个包实际上不包含任何额外的依赖项,它只是指定要使用的插件 API 的版本。

注意:在开发插件时,虽然不是必需的,但将 composer/composer 作为 require-dev 依赖项添加到 IDE 中,可以帮助你实现 Composer 类别的自动完成。

composer-plugin-api 的所需版本遵循与普通包相同的 规则

当前的 Composer 插件 API 版本是 2.6.0

一个有效的插件 composer.json 文件示例(省略了自动加载部分,并且可选地将 composer/composer 作为 require-dev 依赖项添加到 IDE 中以实现自动完成)

{
    "name": "my/plugin-package",
    "type": "composer-plugin",
    "require": {
        "composer-plugin-api": "^2.0"
    },
    "require-dev": {
        "composer/composer": "^2.0"
    },
    "extra": {
        "class": "My\\Plugin"
    }
}

插件类#

每个插件都必须提供一个类,该类实现 Composer\Plugin\PluginInterface。插件的 activate() 方法在加载插件后调用,并接收一个 Composer\Composer 的实例以及一个 Composer\IO\IOInterface 的实例。使用这两个对象,可以读取所有配置,并根据需要操作所有内部对象和状态。

示例

<?php

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);
    }
}

事件处理程序#

此外,插件可以实现 Composer\EventDispatcher\EventSubscriberInterface,以便在加载插件时自动将它的事件处理程序注册到 EventDispatcher

要将方法注册到事件,请实现 getSubscribedEvents() 方法,并让它返回一个数组。数组键必须是 事件名称,而值是该类中要调用的方法的名称。

注意:如果你不知道要监听哪个事件,你可以使用 COMPOSER_DEBUG_EVENTS=1 环境变量设置运行 Composer 命令,这可能会帮助你确定你正在寻找的事件。

public static function getSubscribedEvents()
{
    return array(
        'post-autoload-dump' => 'methodToBeCalled',
        // ^ event name ^         ^ method name ^
    );
}

默认情况下,事件处理程序的优先级设置为 0。可以通过附加一个元组来更改优先级,元组的第一个值为方法名(与以前相同),第二个值为表示优先级的整数。较高的整数表示较高的优先级。优先级 2 在优先级 1 之前调用,依此类推。

public static function getSubscribedEvents()
{
    return array(
        // Will be called before events with priority 0
        'post-autoload-dump' => array('methodToBeCalled', 1)
    );
}

如果需要调用多个方法,则可以将元组数组附加到每个事件。元组不需要包含优先级。如果省略,它将默认为 0。

public static function getSubscribedEvents()
{
    return array(
        'post-autoload-dump' => array(
            array('methodToBeCalled'      ), // Priority defaults to 0
            array('someOtherMethodName', 1), // This fires first
        )
    );
}

以下是一个完整的示例

<?php

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 function deactivate(Composer $composer, IOInterface $io)
    {
    }

    public function uninstall(Composer $composer, IOInterface $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') {
            // ...
        }
    }
}

插件功能#

Composer 定义了一组标准功能,这些功能可以由插件实现。它们的目的是使插件生态系统更加稳定,因为它减少了与 Composer\Composer 的内部状态发生冲突的需要,通过为常见的插件需求提供明确的扩展点。

Capable Plugins 类必须实现 Composer\Plugin\Capable 接口并在 getCapabilities() 方法中声明它们的功能。此方法必须返回一个数组,其中为 Composer 功能类名,而为插件对该功能的自己的实现类名

<?php

namespace My\Composer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
use Composer\Plugin\Capable;

class Plugin implements PluginInterface, Capable
{
    public function activate(Composer $composer, IOInterface $io)
    {
    }

    public function getCapabilities()
    {
        return array(
            'Composer\Plugin\Capability\CommandProvider' => 'My\Composer\CommandProvider',
        );
    }
}

命令提供者#

Composer\Plugin\Capability\CommandProvider 功能允许为 Composer 注册额外的命令

<?php

namespace My\Composer;

use Composer\Plugin\Capability\CommandProvider as CommandProviderCapability;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Command\BaseCommand;

class CommandProvider implements CommandProviderCapability
{
    public function getCommands()
    {
        return array(new Command);
    }
}

class Command extends BaseCommand
{
    protected function configure(): void
    {
        $this->setName('custom-plugin-command');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $output->writeln('Executing');

        return 0;
    }
}

现在,custom-plugin-command 与 Composer 命令一起可用。

Composer 命令基于 Symfony Console 组件

手动运行插件#

可以通过 run-script 命令手动运行某个事件的插件。这与 手动运行脚本 的方式相同。

如果是其他类型的插件,最好使用 路径存储库 在测试项目中需要插件来测试它。如果你在本地开发并且想要频繁地进行测试,你可以确保路径存储库使用符号链接,因为更改会立即更新。否则,你将不得不每次都运行 rm -rf vendor && composer update 来重新安装/运行它。

使用插件#

插件包在安装后会自动加载,并且如果它们在当前项目的已安装包列表中找到,则在 Composer 启动时也会加载。此外,使用 Composer 全局命令安装在 COMPOSER_HOME 目录中的所有插件包,都会在加载本地项目插件之前加载。

你可以将 --no-plugins 选项传递给 Composer 命令以禁用所有已安装的插件。如果任何插件导致错误并且你希望更新或卸载它,这可能特别有用。

插件助手#

从 Composer 2 开始,由于 DownloaderInterface 有时可以返回 Promises,并且比以前拆分成了更多步骤,因此我们提供了一个 SyncHelper 来简化下载和安装包的操作。

插件额外属性#

可以使用插件的 composer.json 中的额外属性来解锁一些特殊的插件功能。

class#

参见上文了解 class 属性的工作原理和用法。

plugin-modifies-downloads#

一些特殊的插件需要在包下载之前更新包下载 URL。

从 Composer 2.0 开始,所有包都在安装之前下载。这意味着在第一次安装时,你的插件在下载发生时还没有安装,它没有机会及时更新 URL。

在你的 composer.json 中指定 {"extra": {"plugin-modifies-downloads": true}} 会暗示 Composer 在继续其他包下载之前应该单独安装插件。但是,这会稍微减慢整个安装过程,因此不要在不需要此功能的插件中使用它。

plugin-modifies-install-path#

一些特殊的插件修改了包的安装路径。

从 Composer 2.2.9 开始,你可以在你的 composer.json 中指定 {"extra": {"plugin-modifies-install-path": true}},以暗示 Composer 应该尽快激活插件,以防止 Composer 假设包安装在与实际安装位置不同的位置,从而导致任何不良副作用。

plugin-optional#

因为 Composer 插件可以用于执行安装工作应用程序所必需的操作,例如修改文件存储的路径,所以无意中跳过所需的插件会导致应用程序出现故障。因此,在非交互式模式下,如果一个新插件未列在 "allow-plugins" 中,Composer 会失败,以强制用户决定是否要执行插件,以避免静默失败。

从 Composer 2.5.3 开始,你可以在你的插件上使用设置 {"extra": {"plugin-optional": true}},告诉 Composer 跳过插件不会造成灾难性的后果,如果它还没有列在 "allow-plugins" 中,它可以在非交互式模式下安全地禁用。Composer 的下一个交互式运行仍然会提示用户选择是否要启用或禁用插件。

插件自动加载#

由于插件在 Composer 运行时加载,并且为了确保依赖于其他包的插件能够正常工作,每当加载插件时都会创建一个运行时自动加载程序。该自动加载程序仅配置为加载插件依赖项,因此你可能无法访问已安装的所有包。

静态分析支持#

从 Composer 2.3.7 开始,我们提供了一个 phpstan/rules.neon PHPStan 配置文件,它在使用 Composer 插件时提供额外的错误检查。

PHPStan 扩展安装程序 一起使用#

如果你的插件项目声明对 phpstan/extension-installer 的依赖关系,则会自动加载必要的配置文件。

替代手动安装#

要使用它,你的 Composer 插件项目需要一个 PHPStan 配置文件,该文件包含 phpstan/rules.neon 文件

includes:
    - vendor/composer/composer/phpstan/rules.neon

// your remaining config..

发现错字?文档中有什么问题? 分叉并编辑 它!