titasgailius / terminal
Terminal 是 Symfony 的 Process 组件的一个优雅包装器。
Requires
- symfony/process: ^4.4.2|^5.0|^6.0
Requires (Dev)
- mockery/mockery: ^1.3
- phpunit/phpunit: ^9.0
- symfony/var-dumper: ^5.0|^6.0
README
Terminal
Symfony 的 Process 组件的优雅包装器。
内容
安装
composer require titasgailius/terminal
执行命令
要执行一个命令,你可以使用 run
方法。首先,让我们看看如何执行一个基本的 shell 命令。
$response = Terminal::run('rm -rf vendor');
响应
run
方法返回一个 TitasGailius\Terminal\Response
实例,它提供了一系列用于检查响应的方法
$response->getExitCode() : int; $response->ok() : bool; $response->successful() : bool; $response->lines() : array; $response->output() : string; (string) $response: string;
输出到数组
你可以通过使用 lines
方法在一个数组中获取整个命令输出
foreach ($response->lines() as $line) { // }
输出流(推荐)
如果内存消耗很重要,你可以通过在响应实例上使用 foreach
循环逐行读取整个输出
foreach ($response as $line) { // }
输出行
每个 $line
项都是一个 TitasGailius\Terminal\OutputLine
对象的实例,该对象提供了一系列用于检查输出行的方法。
你可以检查输出行是否是错误
$line->error(); // true|false
你可以将此对象用作字符串以获取行的内容
(string) $line;
或者,你可以使用 content
方法来获取行的内容
$line->content();
通过 Laravel Artisan 命令输出
如果你从 Laravel 的 Artisan 命令中运行 Terminal,你可以通过将命令实例传递给 output
方法将输出发送到控制台
public function handle() { Terminal::output($this)->run('echo Hello, World'); }
通过 Symfony Console 命令输出
如果你从 Symfony 的 Console 命令中运行 Terminal,你可以通过将 OutputInterface 实例传递给 output
方法将输出发送到控制台
protected function execute(InputInterface $input, OutputInterface $output) { Terminal::output($output)->run('echo Hello, World'); }
抛出异常
如果你希望在命令不成功时抛出异常,你可以使用 throw
方法
$response = Terminal::run(...); $response->throw(); return (string) $response;
在发生错误时将抛出 Symfony\Component\Process\Exception\ProcessFailedException
实例。
数据
如果你需要将任何数据传递到你的命令行,最好使用 with
方法进行绑定。Terminal 可以为你转义和准备这些值。使用 {{ $key }}
语法引用这些值。
Terminal::with([ 'firstname' => 'John', 'lastname' => 'Doe', ])->run('echo Hello, {{ $firstname}} {{ $lastname }}');
或者,你可以将键值对作为单独的参数传递。
Terminal::with('greeting', 'World') ->run('echo Hello, {{ $greeting }}');
工作目录
如果你想要更改执行脚本的当前工作目录,你可以使用接受路径的 in
方法
Terminal::in(storage_path('framework'))->run('rm -rf views');
超时
如果你想要给你的命令添加一个超时,你可以使用接受秒数的整数或 DateTime、DateInterval 和 Carbon 实例的 timeout
方法
Terminal::timeout(25)->run('rm -rf vendor');
使用 DateInterval
$duration = new DateInterval('PT25S'); Terminal::timeout($duration)->run('rm -rf vendor');
使用 DateTime
$date = (new DateTime)->add(new DateInterval('PT25S')); Terminal::timeout($date)->run('rm -rf vendor');
使用 Carbon
$date = Carbon::now()->addSeconds(25); Terminal::timeout($date)->run('rm -rf vendor');
重试
如果你希望在出现错误时自动重试命令,你可以使用 retries
方法。该 retries
方法接受两个参数:命令应尝试的次数和尝试之间等待的毫秒数
Terminal::retries(3, 100)->run('rm -rf vendor');
环境变量
默认情况下,shell 脚本会使用与当前 PHP 进程相同的环境变量来运行。如果您想使用不同的环境变量集运行脚本,可以使用 withEnvironmentVariables
方法。该方法接受一个包含环境变量键值对的数组。
Terminal::withEnvironmentVariables([ 'APP_ENV' => 'testing', ])->run('rm -rf $DIRECTORY');
命令
在某些情况下,您可能想使用 command
方法在真正执行之前定义可执行命令。
$command = Terminal::command('rm -rf vendor'); if ($inBackground) { $command->inBackground(); } $command->run();
Symfony Process
您可以通过调用 process
方法来获取 Symfony\Component\Process\Process
类的底层实例。
$process = Terminal::timeout(25)->process();
您也可以从 TitasGailius\Terminal\Response
对象中获取进程实例。
$response = Terminal::run(...); $process = $response->process();
最后,所有对 TitasGailius\Terminal\Response
实例缺失的方法调用都会自动传递到底层进程实例。
$response = Terminal::run(...); $response->isRunning(); // "isRunning" method is passed to the \Symfony\Component\Process\Process class
扩展
extend
方法允许您定义自定义方法。
Terminal::extend('removeVendors', function ($terminal) { return $terminal->run('rm -rf vendors'); }); Terminal::removeVendors();
测试
Terminal 提供了一些特殊功能,帮助您轻松且具有表现力地编写测试。Terminal 的 fake 方法允许您在执行命令时指示 Terminal 返回模拟的/虚拟的响应。
伪造响应
要指示 Terminal 对于每个执行的命令都返回空响应,您可以不带参数调用 fake
方法。
Terminal::fake(); $response = Terminal::run(...);
伪造特定命令
或者,您可以将一个数组传递给 fake 方法。数组的键应代表您希望模拟的命令及其关联的响应。
Terminal::fake([ 'php artisan inspire' => 'Simplicity is the ultimate sophistication. - Leonardo da Vinci', 'cowsay Hi, How are you' => [ ' _________________ ', '< Hi, How are you > ', ' ----------------- ', ' \ ^__^ ', ' \ (oo)\_______ ', ' (__)\ )\/\ ', ' ||----w | ', ' || || ', ], ]);
响应行
除了传递字符串或行数组外,您还可以显式指定每行类型。Terminal 的 line
和 error
方法可以帮助您创建更精确的响应。
Terminal::fake([ 'wp cli update' => [ Terminal::line('Downloading WordPress files.'), Terminal::error('WordPress is down.'), ], ]);
失败响应
模拟失败响应非常简单。将您的响应行移动到 Terminal 的 response
方法,并在其上方调用 shouldFail
。
Terminal::fake([ 'php artisan migrate' => Terminal::response([ 'Migrating: 2012_12_12_000000_create_users_table', 'Migrated: 2012_12_12_000000_create_users_table', ])->shouldFail(), ]);
检查命令
当模拟响应时,您可能偶尔希望检查 Terminal 收到的命令,以确保您的应用程序正在执行正确的命令。
您可以通过在调用 Terminal::fake
后调用 Terminal::assertExecuted
方法来实现这一点。
Terminal::fake(); Terminal::run('php artisan migrate'); Terminal::assertExecuted('php artisan migrate');
或者,您也可以检查给定的命令是否未执行。您可以通过在调用 Terminal::fake
后调用 Terminal::assertNotExecuted
方法来实现这一点。
Terminal::fake(); Terminal::assertNotExecuted('php artisan migrate');
模拟 Symfony Process
如果您需要模拟底层的 Symfony 的 Process,您可以使用 Terminal 的 response
方法。
Terminal 的 response
方法可以使用多种方式
- 传递响应行和一个可选的进程实例。
- 只传递进程实例。
$process = Mockery::mock(Process::class, function ($mock) { $mock->shouldReceive('getPid') ->twice() ->andReturn(123, 321); }); Terminal::fake([ // Empty response with a mocked \Symfony\Component\Process\Process instance. 'factor 12' => Terminal::response($process) // Response lines with a mocked \Symfony\Component\Process\Process instance. 'php artisan migrate' => Terminal::response([ 'Migrating: 2012_12_12_000000_create_users_table', 'Migrated: 2012_12_12_000000_create_users_table', ], $process), ]); $this->assertEquals(123, Terminal::run('factor 12')->getPid()); $this->assertEquals(321, Terminal::run('php artisan migrate')->getPid());
注意事项
Terminal 使用一些静态方法来提供这些美丽的测试功能。具体来说,Terminal 将模拟响应存储在静态属性中,这意味着它们在每次测试之间不会清除。
为了避免这种情况,您可以使用 Terminal::reset
方法。最佳调用位置是 PhpUnit 的 teardown
方法。
/** * This method is called after each test. */ protected function tearDown(): void { parent::tearDown(); Terminal::reset(); }
PHP 8 支持
要使用 PHP 8.x 的 Terminal,请将 Terminal 升级到 ^1.0
版本。
- 更新您的
composer.json
以使用 terminal 的最新版本:"titasgailius/terminal": "^1.0"
。 - 请注意,
Builder::retry
现在是一个protected
方法。
您不太可能使用此方法。.