Skip to content

命令行内核

Testing Is Documentation

tests/Kernel/KernelConsoleTest.php

QueryPHP 命令行流程为入口接受输入,经过内核 kernel 传入输入,经过命令行应用程序调用命令执行业务,最后返回输出结果。

入口文件 leevel

php
#!/usr/bin/env php
<?php

declare(strict_types=1);

use App\Infra\Exceptions\Runtime;
use App\Kernel;
use App\KernelConsole;
use Leevel\Di\Container;
use Leevel\Di\IContainer;
use Leevel\Kernel\App;
use Leevel\Kernel\IApp;
use Leevel\Kernel\Exceptions\IRuntime;
use Leevel\Kernel\IKernel;
use Leevel\Kernel\IKernelConsole;
use Symfony\Component\Console\Input\ArgvInput;

// 加载 Composer
require __DIR__.'/vendor/autoload.php';

// 创建应用
// 注册应用基础服务
$container = Container::singletons();
$container->singleton(IContainer::class, $container);

// 应用路径
$path = str_starts_with(__DIR__, 'phar://') ?
     Phar::running() :
     realpath(__DIR__);
$container->singleton('app', $app = new App($container, $path));

// PHAR 缓存路径不能是 phar 内部路径,因为 phar 内部路径是只读的
if (str_starts_with(__DIR__, 'phar://')) {
    $app->setStoragePath(substr(dirname(Phar::running()), 7).\DIRECTORY_SEPARATOR.'storage');
}

$container->alias('app', [IApp::class, App::class]);
$container->singleton(IKernel::class, Kernel::class);
$container->singleton(IKernelConsole::class, KernelConsole::class);
$container->singleton(IRuntime::class, Runtime::class);

// 载入环境
$input = new ArgvInput();
if ($input->hasParameterOption('--env')) {
    $env = $input->getParameterOption('--env');
} else {
    $env = 'env';
}
putenv('RUNTIME_ENVIRONMENT='.$env);

// 执行应用
// 根据内核调度请求返回响应
$kernel = $container->make(IKernelConsole::class);
$status = $kernel->handle();
$kernel->terminate($status);
exit($status);

内核通过 \Leevel\Kernel\KernelConsole 的 handle 方法来实现请求。

handle 原型

php
# Leevel\Kernel\KernelConsole::handle
/**
 * {@inheritDoc}
 */
public function handle(?InputInterface $input = null, ?OutputInterface $output = null): int;

Uses

php
<?php

use Leevel\Console\Application;
use Leevel\Di\Container;
use Leevel\Di\IContainer;
use Leevel\Kernel\App as Apps;
use Leevel\Kernel\IApp;
use Leevel\Kernel\IKernelConsole;
use Leevel\Kernel\KernelConsole;
use Leevel\Kernel\Utils\Api;
use Leevel\Option\IOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

基本使用

fixture 定义

Tests\Kernel\AppKernelConsole

php
namespace Tests\Kernel;

class AppKernelConsole extends Apps
{
    public function namespacePath(string $specificClass, bool $throwException = true): string
    {
        return __DIR__.'/Commands/Console';
    }

    protected function registerBaseProvider(): void
    {
    }
}

Tests\Kernel\Application1

php
namespace Tests\Kernel;

class Application1 extends Application
{
    public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
    {
        return 0;
    }
}

Tests\Kernel\KernelConsole1

php
namespace Tests\Kernel;

class KernelConsole1 extends KernelConsole
{
    protected array $bootstraps = [
        DemoBootstrapForKernelConsole::class,
    ];

    protected function getConsoleApplication(): Application
    {
        if ($this->consoleApplication) {
            return $this->consoleApplication;
        }

        return $this->consoleApplication = new Application1($this->app->container(), $this->app->version());
    }
}

Tests\Kernel\Commands\Test

php
namespace Tests\Kernel\Commands;

use Leevel\Console\Command;

final class Test extends Command
{
    protected string $name = 'test';

    protected string $description = 'This is a test command';

    protected string $help = <<<'EOF'
        The <info>%command.name%</info> command to show how to make a command:

          <info>php %command.full_name%</info>
        EOF;

    public function handle(): int
    {
        $this->info('Hello my test command.');

        return 0;
    }
}

Tests\Kernel\Commands\Console\Foo

php
namespace Tests\Kernel\Commands\Console;

use Leevel\Console\Command;

class Foo extends Command
{
    protected string $name = 'console:foo';

    protected string $description = 'This is a foo command';

    protected string $help = <<<'EOF'
        The <info>%command.name%</info> command to show how to make a command:

          <info>php %command.full_name%</info>
        EOF;

    public function handle(): int
    {
        $this->info('Hello my foo command.');

        return 0;
    }
}

Tests\Kernel\Commands\Console\Bar

php
namespace Tests\Kernel\Commands\Console;

use Leevel\Console\Command;

class Bar extends Command
{
    protected string $name = 'console:bar';

    protected string $description = 'This is a foo command';

    protected string $help = <<<'EOF'
        The <info>%command.name%</info> command to show how to make a command:

          <info>php %command.full_name%</info>
        EOF;

    public function handle(): int
    {
        $this->info('Hello my foo command.');

        return 0;
    }
}

Tests\Kernel\DemoBootstrapForKernelConsole

php
namespace Tests\Kernel;

class DemoBootstrapForKernelConsole
{
    public function handle(IApp $app): void
    {
        $GLOBALS['DemoBootstrapForKernelConsole'] = true;
    }
}
php
public function testBaseUse(): void
{
    $app = new AppKernelConsole($container = new Container(), '');
    $container->instance('app', $app);

    $this->createOption($container);

    $kernel = new KernelConsole1($app);
    $this->assertInstanceof(IKernelConsole::class, $kernel);
    $this->assertInstanceof(IApp::class, $kernel->getApp());
    static::assertSame(0, $kernel->handle());
    $kernel->terminate(0);
    static::assertTrue($GLOBALS['DemoBootstrapForKernelConsole']);
    unset($GLOBALS['DemoBootstrapForKernelConsole']);
}