跳转到内容

脚本

在Composer术语中,脚本可以是PHP回调(定义为静态方法)或任何命令行可执行命令。脚本有助于在Composer执行过程中运行包的自定义代码或特定于包的命令。

从Composer 2.5开始,脚本也可以是Symfony控制台命令类,这使你能够轻松运行它们,包括传递选项。不过,这不建议用于处理事件。

**注意:**只有在根包的composer.json中定义的脚本才会被执行。如果根包的某个依赖项指定了自己的脚本,Composer不会执行这些额外的脚本。

Composer在其执行过程中会触发以下命名事件:

  • 安装前命令:在存在锁定文件的情况下执行install命令之前发生。
  • 安装后命令:在带有锁定文件的情况下执行install命令后发生。
  • 更新前命令:在执行update命令之前发生,或者在没有锁文件的情况下执行install命令之前发生。
  • post-update-cmd:在update命令执行后发生,或者在没有锁文件的情况下执行install命令后发生。
  • pre-status-cmd:在status命令执行前发生。
  • post-status-cmd:在status命令执行后发生。
  • pre-archive-cmd:在archive命令执行前发生。
  • post-archive-cmd:在archive命令执行后发生。
  • 预自动加载-转储:发生在自动加载器被转储之前,可能是在install/update期间,也可能是通过dump-autoload命令。
  • post-autoload-dump:发生在自动加载器被转储之后,可能是在install/update期间,也可能是通过dump-autoload命令。
  • post-root-package-install:在create-project命令执行期间,根包安装完成后(但在其依赖项安装之前)发生。
  • post-create-project-cmd:在create-project命令执行后发生。
  • pre-operations-exec:在安装锁定文件时,发生在安装/升级/……等操作执行之前。需要挂钩此事件的插件必须全局安装才能使用,否则在全新安装项目时,这些插件还不会被加载。
  • pre-package-install:在安装软件包之前发生。
  • post-package-install:在软件包安装后发生。
  • pre-package-update:在软件包更新前发生。
  • post-package-update:在包更新后发生。
  • pre-package-uninstall:在卸载软件包之前发生。
  • post-package-uninstall:在软件包卸载后发生。
  • init:在Composer实例完成初始化后发生。
  • 命令:在CLI上执行任何Composer命令之前发生。它使你能够访问程序的输入和输出对象。
  • 文件下载前:发生在文件下载之前,允许您根据要下载的URL在下载文件前操作HttpDownloader对象。
  • post-file-download:在包的发行文件下载后发生,允许你在需要时对文件执行额外检查。
  • pre-command-run:在命令执行前发生,允许你操作InputInterface对象的选项和参数,以调整命令的行为。
  • pre-pool-create:发生在包池创建之前,允许您过滤将要进入求解器的包列表。

**注意:**Composer 不会对 installupdate 之前的依赖状态做任何假设。因此,你不应在 pre-update-cmdpre-install-cmd 事件钩子中指定需要 Composer 管理的依赖的脚本。如果你需要在 installupdate 之前执行脚本,请确保这些脚本在你的根包中是自包含的。

xxxxxxxxxx vendor/package-name is not required in your composer.json and has not been removed./composer.json has been updatedRunning composer update vendor/package-nameLoading composer repositories with package informationUpdating dependenciesLock file operations: 0 installs, 0 updates, 1 removal - Removing vendor/package-name (1.0)Writing lock fileInstalling dependencies from lock file (including require-dev)Nothing to install, update or removebashell

Section titled “xxxxxxxxxx vendor/package-name is not required in your composer.json and has not been removed./composer.json has been updatedRunning composer update vendor/package-nameLoading composer repositories with package informationUpdating dependenciesLock file operations: 0 installs, 0 updates, 1 removal - Removing vendor/package-name (1.0)Writing lock fileInstalling dependencies from lock file (including require-dev)Nothing to install, update or removebashell”

composer.json中的根JSON对象应该有一个名为"scripts"的属性,该属性包含命名事件对以及每个事件对应的脚本。事件的脚本可以定义为字符串(仅用于单个脚本)或数组(用于单个或多个脚本)。

对于任何给定的事件:

  • Scripts execute in the order defined when their corresponding event is fired.
  • An array of scripts wired to a single event can contain both PHP callbacks and command-line executable commands.
  • PHP classes and commands containing defined callbacks must be autoloadable via Composer’s autoload functionality.
  • Callbacks can only autoload classes from psr-0, psr-4 and classmap definitions. If a defined callback relies on functions defined outside of a class, the callback itself is responsible for loading the file containing these functions.

Script definition example:

{
"scripts": {
"post-update-cmd": "MyVendor\\MyClass::postUpdate",
"post-package-install": [
"MyVendor\\MyClass::postPackageInstall"
],
"post-install-cmd": [
"MyVendor\\MyClass::warmCache",
"phpunit -c app/"
],
"post-autoload-dump": [
"MyVendor\\MyClass::postAutoloadDump"
],
"post-create-project-cmd": [
"php -r \"copy('config/local-example.php', 'config/local.php');\""
]
}
}

Using the previous definition example, here’s the class MyVendor\MyClass that might be used to execute the PHP callbacks:

<?php
namespace MyVendor;
use Composer\Script\Event;
use Composer\Installer\PackageEvent;
class MyClass
{
public static function postUpdate(Event $event)
{
$composer = $event->getComposer();
// do stuff
}
public static function postAutoloadDump(Event $event)
{
$vendorDir = $event->getComposer()->getConfig()->get('vendor-dir');
require $vendorDir . '/autoload.php';
some_function_from_an_autoloaded_file();
}
public static function postPackageInstall(PackageEvent $event)
{
$installedPackage = $event->getOperation()->getPackage();
// do stuff
}
public static function warmCache(Event $event)
{
// make cache toasty
}
}

Note: During a Composer install or update command run, a variable named COMPOSER_DEV_MODE will be added to the environment. If the command was run with the --no-dev flag, this variable will be set to 0, otherwise it will be set to 1. The variable is also available while dump-autoload runs, and it will be set to the same as the last install or update was run in.

When an event is fired, your PHP callback receives as first argument a Composer\EventDispatcher\Event object. This object has a getName() method that lets you retrieve the event name.

Depending on the script types you will get various event subclasses containing various getters with relevant data and associated objects:

如果您想手动运行某个事件的脚本,语法如下:

php composer.phar run-script [--dev] [--no-dev] script

例如,composer run-script post-install-cmd 将运行所有已定义的post-install-cmd脚本和插件

You can also give additional arguments to the script handler by appending -- followed by the handler arguments. e.g. composer run-script post-install-cmd -- --check will pass--check along to the script handler. Those arguments are received as CLI arg by CLI handlers, and can be retrieved as an array via $event->getArguments() by PHP handlers.

如果你添加的自定义脚本不符合上述任何一个预定义的事件名称,你可以使用run-script来运行它们,或者将其作为原生的Composer命令运行。例如,下面定义的处理器可以通过运行composer test来执行:

{
"scripts": {
"test": "phpunit",
"do-something": "MyVendor\\MyClass::doSomething",
"my-cmd": "MyVendor\\MyCommand"
}
}

run-script命令类似,你可以为脚本提供额外的参数,例如composer test -- --filter <pattern>会将--filter <pattern>传递给phpunit脚本。

Using a PHP method via composer do-something arg lets you execute a static function doSomething(\Composer\Script\Event $event) and arg becomes available in $event->getArguments(). This however does not let you easily pass custom options in the form of --flags.

Using a symfony/console Command class you can describe your script, define and access arguments and options more easily.

For example, with the command below you can then simply call composer my-cmd --arbitrary-flag without even the need for a -- separator. To be detected as symfony/console commands, the class name must end with Command and extend Symfony’s Command class. Also note that this will run using Composer’s built-in symfony/console version, which may not match the one you have required in your project and may change between Composer minor releases. If you need more safety guarantees, you should rather use your own binary file that runs your own symfony/console version in isolation in its own process then.

Script names and descriptions defined inside a Command class will override the details from your composer.json: the key for the entry in scripts (used as the command passed to run-script) will be replaced with either $defaultName or the value passed to setName(), and similar replacement will happen with anything included in scripts-descriptions for that script class.

<?php
namespace MyVendor;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class MyCommand extends Command
{
protected function configure(): void
{
$this
// ->setName('custom-cmd') //if this gets included, it would execute with `composer custom-cmd` instead
->setDescription('Custom description for this command')
->setDefinition([
new InputOption('arbitrary-flag', null, InputOption::VALUE_NONE, 'Example flag'),
new InputArgument('foo', InputArgument::OPTIONAL, 'Optional arg'),
])
->setHelp(
"Here you can define a long description for your command\n".
"This would be visible with composer my-cmd --help"
);
}
public function execute(InputInterface $input, OutputInterface $output): int
{
if ($input->getOption('arbitrary-flag')) {
$output->writeln('The flag was used');
}
return 0;
}
}

**注意:**执行脚本前,Composer的bin目录会被临时添加到PATH环境变量的顶部,因此依赖项的二进制文件可直接访问。在此示例中,无论phpunit二进制文件实际位于vendor/bin/phpunit还是bin/phpunit,都能被找到并执行。

虽然Composer并非旨在管理PHP项目中长期运行的进程以及其他诸如此类的方面,但在自定义命令上禁用进程超时有时会很方便。此超时默认值为300秒,可根据预期效果通过多种方式进行覆盖。

  • 使用配置键process-timeout为所有命令禁用它,
  • 可以通过环境变量COMPOSER_PROCESS_TIMEOUT为当前或未来的composer调用禁用它,
  • 对于使用run-script命令的--timeout标志的特定调用,
  • 对特定脚本使用静态助手。

要使用静态助手在composer.json中直接为特定脚本禁用超时:

{
"scripts": {
"test": [
"Composer\\Config::disableProcessTimeout",
"phpunit"
]
}
}

要禁用特定项目中所有脚本的超时设置,您可以使用composer.json配置:

{
"config": {
"process-timeout": 0
}
}

也可以设置全局环境变量,以禁用当前终端环境中所有后续脚本的超时功能:

export COMPOSER_PROCESS_TIMEOUT=0

要禁用单个脚本调用的超时,必须使用run-script编写器命令并指定--timeout参数:

php composer.phar run-script --timeout=0 test

为了实现脚本复用并避免重复,您可以通过在命令名前加上@来从另一个脚本调用该脚本:

{
"scripts": {
"test": [
"@clearCache",
"phpunit"
],
"clearCache": "rm -rf cache/*"
}
}

你也可以引用一个脚本并向其传递新参数:

{
"scripts": {
"tests": "phpunit",
"testsVerbose": "@tests -vvv"
}
}

要调用Composer命令,您可以使用@composer,它会自动解析为当前正在使用的任何composer.phar:

{
"scripts": {
"test": [
"@composer install",
"phpunit"
]
}
}

这种方式的一个限制是,你不能连续调用多个composer命令,例如@composer install && @composer foo。你必须将它们拆分成一个JSON命令数组。

要执行PHP脚本,你可以使用@php,它会自动解析为当前正在使用的任何php进程:

{
"scripts": {
"test": [
"@php script.php",
"phpunit"
]
}
}

这样做的一个限制是,你不能连续调用多个命令,比如@php install && @php foo。你必须将它们拆分成一个命令的JSON数组。

你也可以调用一个shell/bash脚本,其中PHP可执行文件的路径将作为PHP_BINARY环境变量可用。

从 Composer 2.8 开始,你可以控制额外参数如何传递给脚本命令。

运行诸如composer script-name arg arg2composer script-name -- --option之类的脚本时,Composer默认会将argarg2--option附加到脚本命令中。

如果你不希望在特定命令中包含这些参数,可以在命令的任意位置添加@no_additional_args,这会取消默认行为,并且该标记在运行命令前也会被移除。

如果您希望参数添加在除末尾之外的其他位置,可以使用@additional_args来精确指定它们的位置。

例如,使用以下配置运行composer run-commands ARG

{
"scripts": {
"run-commands": [
"echo hello @no_additional_args",
"command-with-args @additional_args && do-something-without-args --here"
]
}
}

最终会执行这些命令:

echo hello
command-with-args ARG && do-something-without-args --here

要以跨平台的方式设置环境变量,您可以使用@putenv

{
"scripts": {
"install-phpstan": [
"@putenv COMPOSER=phpstan-composer.json",
"@composer install --prefer-dist"
]
}
}

你可以在composer.json中通过以下方式设置自定义脚本描述:

{
"scripts-descriptions": {
"test": "Run all tests!"
}
}

这些描述用于composer listcomposer run -l命令中,以说明运行命令时脚本的作用。

**注意:**您只能为自定义命令设置自定义描述。

从 Composer 2.7 开始,你可以在 composer.json 中通过以下方式设置自定义脚本别名:

{
"scripts-aliases": {
"phpstan": ["stan", "analyze"]
}
}

别名提供了替代的命令名称。

**注意:**您只能为自定义命令设置自定义别名。