最近业余时间在写一个Web游戏,技术选项是Go+TypeScript,使用gRPC-Web进行通讯。在做数值策划,生成数据的时候,还是想用顺手的PHP来写工具。所以,有了这篇文章。
我之前用Go写过一个批量操作远程主机的工具,用的命令行软件包是spf13/cobra
。写PHP的命令行工具,我也是想找一个比较出名好用的软件包来减少无意义的重复工作。经过一番搜索 symfony/console
出现在我眼前,简单阅读了一下官方文档,开始了我的第一个PHP命令行程序之旅。
入口文件:tools.php
#!/usr/bin/env php
<?php
use Game\Generator\Chapter;
use Symfony\Component\Console\Application;
require_once __DIR__ . '/vendor/autoload.php';
$application = new Application('Game Tools', '0.1');
$application->add(new Chapter());
$application->run();
生成数据的命令行类Chapter.php
实现
<?php
namespace Game\Generator;
use Symfony\Component\Console\Command\Command;
class Chapter extends Command
{
}
运行:
chmod +x ./tools.php && ./tools.php
出乎意料,竟然报错了 !
PHP Fatal error: Uncaught Symfony\Component\Console\Exception\LogicException: The command defined in "Game\Generator\Chapter" cannot have an empty name. in /Users/nemo/workspace/go/game001/tools/vendor/symfony/console/Application.php:470
Stack trace:
#0 /Users/nemo/workspace/go/game001/tools/tools.php(12): Symfony\Component\Console\Application->add(Object(Game\Generator\Chapter))
#1 {main}
thrown in /Users/nemo/workspace/go/game001/tools/vendor/symfony/console/Application.php on line 470
看样子是需要我指定一个name呢,我加上name后,命令类如下:
<?php
namespace Game\Generator;
use Symfony\Component\Console\Command\Command;
class Chapter extends Command
{
function __construct()
{
parent::__construct('chapter');
}
}
这下应该正常工作了吧?运行结果:
You must override the execute() method in the concrete command class.
这个时候已经让我想起了Windows的“正在处理一些事情”,如这般可笑。
我笑着实现了execute方法,内容如下:
<?php
namespace Game\Generator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Chapter extends Command
{
function __construct()
{
parent::__construct('chapter');
}
function execute(InputInterface $input, OutputInterface $output)
{
}
}
然后,真的觉得应该可以了吧的时候,又Fatal了!!!
PHP Fatal error: Uncaught TypeError: Return value of "Game\Generator\Chapter::execute()" must be of the type int, "NULL" returned. in /Users/nemo/workspace/go/game001/tools/vendor/symfony/console/Command/Command.php:258
Stack trace:
#0 /Users/nemo/workspace/go/game001/tools/vendor/symfony/console/Application.php(912): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#1 /Users/nemo/workspace/go/game001/tools/vendor/symfony/console/Application.php(264): Symfony\Component\Console\Application->doRunCommand(Object(Game\Generator\Chapter), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#2 /Users/nemo/workspace/go/game001/tools/vendor/symfony/console/Application.php(140): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#3 /Users/nemo/workspace/go/game001/tools/tools.ph in /Users/nemo/workspace/go/game001/tools/vendor/symfony/console/Command/Command.php on line 258
此时,我已经决定不用这个软件包了。。。但是,既然尝试了,hello world是肯定要有的~ 所以,耐心按照提示继续修改,如下:
<?php
namespace Game\Generator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Chapter extends Command
{
function __construct()
{
parent::__construct('chapter');
}
function execute(InputInterface $input, OutputInterface $output)
{
return 0;
}
}
终于,再次运行,没有报错了。
正确的实现应该是什么样呢?
Command应该是一个抽象类,因为要求子类必须提供一个execute方法实现。
而且还要有一个getName()的抽象方法,要求子类明确自己的name。
对于我所使用的这个软件包,已经是5.0版本了 ,最小版本要求是PHP7.2.5
requires
php: ^7.2.5
symfony/polyfill-mbstring: ~1.0
symfony/polyfill-php73: ^1.8
symfony/service-contracts: ^1.1|^2
那么,Command类应该改写成这样(省略N多无关内容):
/**
* Base class for all commands.
*/
abstract class Command
{
/**
* Return the command's name
* @return string
*/
abstract public function getName(): string;
/**
* Executes the current command.
*
* This method is not abstract because you can use this class
* as a concrete class. In this case, instead of defining the
* execute() method, you set the code to execute by passing
* a Closure to the setCode() method.
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int 0 if everything went fine, or an exit code
*
* @see setCode()
*/
abstract protected function execute(InputInterface $input, OutputInterface $output): int
}
这个时候,再尝试写一个命令是什么体验呢?
首先,我们依旧写一个空类:
<?php
namespace Game\Generator;
use Symfony\Component\Console\Command\Command;
class Chapter extends Command
{
}
此时,IDE会提醒你,基类有两个抽象方法需要实现。然后,我们按照提示,实现两个抽象方法(IDE可以直接帮忙生成这俩方法)
<?php
namespace Game\Generator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Chapter extends Command
{
/**
* Return the command's name
* @return string
*/
public function getName(): string
{
// TODO: Implement getName() method.
}
/**
* Executes the current command.
*
* This method is not abstract because you can use this class
* as a concrete class. In this case, instead of defining the
* execute() method, you set the code to execute by passing
* a Closure to the setCode() method.
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int 0 if everything went fine, or an exit code
*
* @see setCode()
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
// TODO: Implement execute() method.
}
}
此时,可以看到两个方法的签名要求返回值,我们补上返回值。
<?php
namespace Game\Generator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Chapter extends Command
{
/**
* Return the command's name
* @return string
*/
public function getName(): string
{
return 'chapter';
}
/**
* Executes the current command.
*
* This method is not abstract because you can use this class
* as a concrete class. In this case, instead of defining the
* execute() method, you set the code to execute by passing
* a Closure to the setCode() method.
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int 0 if everything went fine, or an exit code
*
* @see setCode()
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
return 0;
}
}
至此,一个基本命令就实现完了。行云流水形容的也就是大概如此吧。
《“PHP让人意外的软件包生态”》 有 1 条评论
沙发