使用必须指定值的选项创建 Artisan 命令
Create Artisan command with option that must specify a value
Laravel's documentation 说(强调我的):
If the user must specify a value for an option, you should suffix the option name with a =
sign…
但接着说:
If the option is not specified when invoking the command, its value will be null
…
这表明“必须”并不代表我认为的意思。确实如此。带有如下签名的简单命令:
protected $signature = "mycommand {-t|test=}";
运行 像 artisan mycommand -t
这样调用时会很好。更糟糕的是,如果您指定了默认值,则在这种情况下不会应用。
protected $signature = "mycommand {-t|test=42}";
当运行宁artisan mycommand
时,$this->option('test')
会给你一个值42,但是当运行作为artisan mycommand -t
它给你一个值null
.
那么,有没有办法要求用户必须(实际上)为给定选项指定一个值(如果它出现在命令行上)?
在 the Laravel code, I confirmed that there is no way to have a truly "required" value. Although Symfony does provide 中查找所需的值,Laravel 不使用此功能。相反,选项都是作为可选的创建的,所以我将不得不编写自己的解析器...
这很简单;我必须编写自定义解析器 class 来覆盖 Illuminate\Console\Parser::parseOption()
方法,然后覆盖 Illuminate\Console\Command::configureUsingFluentDefinition()
以使用新的 class.
我选择创建一个新的选项类型,而不是更改任何现有命令选项的行为。所以现在当我想强制一个值时,我这样声明我的签名:
<?php
namespace App\Console\Commands;
use App\Console\Command;
class MyCommand extends Command
{
/** @var string The double == means a required value */
protected $signature = "mycommand {--t|test==}";
...
}
尝试 运行 artisan mycommand -t
现在将抛出 Symfony\Component\Console\Exception\RuntimeException
消息“--test 选项需要一个值。 " 这也适用于具有默认值的数组选项 (--t==*
) and/or 选项 (--t==42
或 --t==*42
.)
这是新解析器的代码 class:
<?php
namespace App\Console;
use Illuminate\Console\Parser as BaseParser;
use Symfony\Component\Console\Input\InputOption;
class Parser extends BaseParser
{
protected static function parseOption($token): InputOption
{
[$mytoken, $description] = static::extractDescription($token);
$matches = preg_split("/\s*\|\s*/", $mytoken, 2);
if (isset($matches[1])) {
$shortcut = $matches[0];
$mytoken = $matches[1];
} else {
$shortcut = null;
}
switch (true) {
case str_ends_with($mytoken, "=="):
return new InputOption(
trim($mytoken, "="),
$shortcut,
InputOption::VALUE_REQUIRED,
$description
);
case str_ends_with($mytoken, "==*"):
return new InputOption(
trim($mytoken, "=*"),
$shortcut,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
$description
);
case preg_match("/(.+)==\*(.+)/", $mytoken, $matches):
return new InputOption(
$matches[1],
$shortcut,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
$description,
preg_split('/,\s?/', $matches[2])
);
case preg_match("/(.+)==(.+)/", $mytoken, $matches):
return new InputOption(
$matches[1],
$shortcut,
InputOption::VALUE_REQUIRED,
$description,
$matches[2]
);
default:
// no == here, fall back to the standard parser
return parent::parseOption($token);
}
}
}
和新命令class:
<?php
namespace App\Console;
use Illuminate\Console\Command as BaseCommand;
class Command extends BaseCommand
{
/**
* Overriding the Laravel parser so we can have required arguments
*
* @inheritdoc
* @throws ReflectionException
*/
protected function configureUsingFluentDefinition(): void
{
// using our parser here
[$name, $arguments, $options] = Parser::parse($this->signature);
// need to call the great-grandparent constructor here; probably
// could have hard-coded to Symfony, but better safe than sorry
$reflectionMethod = new ReflectionMethod(
get_parent_class(BaseCommand::class),
"__construct"
);
$reflectionMethod->invoke($this, $name);
$this->getDefinition()->addArguments($arguments);
$this->getDefinition()->addOptions($options);
}
}
Laravel's documentation 说(强调我的):
If the user must specify a value for an option, you should suffix the option name with a
=
sign…
但接着说:
If the option is not specified when invoking the command, its value will be
null
…
这表明“必须”并不代表我认为的意思。确实如此。带有如下签名的简单命令:
protected $signature = "mycommand {-t|test=}";
运行 像 artisan mycommand -t
这样调用时会很好。更糟糕的是,如果您指定了默认值,则在这种情况下不会应用。
protected $signature = "mycommand {-t|test=42}";
当运行宁artisan mycommand
时,$this->option('test')
会给你一个值42,但是当运行作为artisan mycommand -t
它给你一个值null
.
那么,有没有办法要求用户必须(实际上)为给定选项指定一个值(如果它出现在命令行上)?
在 the Laravel code, I confirmed that there is no way to have a truly "required" value. Although Symfony does provide 中查找所需的值,Laravel 不使用此功能。相反,选项都是作为可选的创建的,所以我将不得不编写自己的解析器...
这很简单;我必须编写自定义解析器 class 来覆盖 Illuminate\Console\Parser::parseOption()
方法,然后覆盖 Illuminate\Console\Command::configureUsingFluentDefinition()
以使用新的 class.
我选择创建一个新的选项类型,而不是更改任何现有命令选项的行为。所以现在当我想强制一个值时,我这样声明我的签名:
<?php
namespace App\Console\Commands;
use App\Console\Command;
class MyCommand extends Command
{
/** @var string The double == means a required value */
protected $signature = "mycommand {--t|test==}";
...
}
尝试 运行 artisan mycommand -t
现在将抛出 Symfony\Component\Console\Exception\RuntimeException
消息“--test 选项需要一个值。 " 这也适用于具有默认值的数组选项 (--t==*
) and/or 选项 (--t==42
或 --t==*42
.)
这是新解析器的代码 class:
<?php
namespace App\Console;
use Illuminate\Console\Parser as BaseParser;
use Symfony\Component\Console\Input\InputOption;
class Parser extends BaseParser
{
protected static function parseOption($token): InputOption
{
[$mytoken, $description] = static::extractDescription($token);
$matches = preg_split("/\s*\|\s*/", $mytoken, 2);
if (isset($matches[1])) {
$shortcut = $matches[0];
$mytoken = $matches[1];
} else {
$shortcut = null;
}
switch (true) {
case str_ends_with($mytoken, "=="):
return new InputOption(
trim($mytoken, "="),
$shortcut,
InputOption::VALUE_REQUIRED,
$description
);
case str_ends_with($mytoken, "==*"):
return new InputOption(
trim($mytoken, "=*"),
$shortcut,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
$description
);
case preg_match("/(.+)==\*(.+)/", $mytoken, $matches):
return new InputOption(
$matches[1],
$shortcut,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
$description,
preg_split('/,\s?/', $matches[2])
);
case preg_match("/(.+)==(.+)/", $mytoken, $matches):
return new InputOption(
$matches[1],
$shortcut,
InputOption::VALUE_REQUIRED,
$description,
$matches[2]
);
default:
// no == here, fall back to the standard parser
return parent::parseOption($token);
}
}
}
和新命令class:
<?php
namespace App\Console;
use Illuminate\Console\Command as BaseCommand;
class Command extends BaseCommand
{
/**
* Overriding the Laravel parser so we can have required arguments
*
* @inheritdoc
* @throws ReflectionException
*/
protected function configureUsingFluentDefinition(): void
{
// using our parser here
[$name, $arguments, $options] = Parser::parse($this->signature);
// need to call the great-grandparent constructor here; probably
// could have hard-coded to Symfony, but better safe than sorry
$reflectionMethod = new ReflectionMethod(
get_parent_class(BaseCommand::class),
"__construct"
);
$reflectionMethod->invoke($this, $name);
$this->getDefinition()->addArguments($arguments);
$this->getDefinition()->addOptions($options);
}
}