Skip to content

内核

Testing Is Documentation

tests/Kernel/KernelTest.php

QueryPHP 流程为入口接受 HTTP 请求,经过内核 kernel 传入请求,经过路由解析调用控制器执行业务,最后返回响应结果。

入口文件 www/index.php

php
<?php

declare(strict_types=1);

use App\Infra\Exceptions\Runtime;
use App\Infra\Kernel\Kernel;
use Leevel\Di\Container;
use Leevel\Di\IContainer;
use Leevel\Http\Request;
use Leevel\Kernel\App;
use Leevel\Kernel\Exceptions\IRuntime;
use Leevel\Kernel\IApp;
use Leevel\Kernel\IKernel;

// 加载 Composer
require_once __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(IRuntime::class, Runtime::class);

// 执行应用
// 根据内核调度请求返回响应
/** @var IKernel $kernel */
$kernel = $container->make(IKernel::class);
$response = $kernel->handle($request = Request::createFromGlobals());
$response->send();
$kernel->terminate($request, $response);

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

handle 原型

php
# Leevel\Kernel\Kernel::handle
public function handle(Request $request): Response;

Uses

php
<?php

use Leevel\Config\IConfig;
use Leevel\Di\Container;
use Leevel\Di\IContainer;
use Leevel\Http\JsonResponse;
use Leevel\Http\Request;
use Leevel\Kernel\App as Apps;
use Leevel\Kernel\Exceptions\HttpException;
use Leevel\Kernel\Exceptions\IRuntime;
use Leevel\Kernel\Exceptions\Runtime;
use Leevel\Kernel\IApp;
use Leevel\Kernel\IKernel;
use Leevel\Kernel\Kernel;
use Leevel\Kernel\Utils\Api;
use Leevel\Log\ILog;
use Leevel\Router\IRouter;
use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Component\HttpFoundation\Response;
use Tests\Kernel\Middlewares\Demo1;
use Tests\Kernel\Middlewares\Demo2;
use Tests\Kernel\Middlewares\Demo3;

基本使用

fixture 定义

Tests\Kernel\Kernel1

php
namespace Tests\Kernel;

class Kernel1 extends Kernel
{
    protected array $bootstraps = [
        DemoBootstrapForKernel::class,
    ];
}

Tests\Kernel\DemoBootstrapForKernel

php
namespace Tests\Kernel;

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

    $request = $this->createMock(Request::class);
    $response = $this->createMock(Response::class);

    $router = $this->createRouter($response);
    $this->createConfig($container, $debug);
    $this->createLog($container);
    $this->createRuntime($container);

    $kernel = new Kernel1($app, $router);
    $this->assertInstanceof(IKernel::class, $kernel);
    $this->assertInstanceof(IApp::class, $kernel->getApp());
    $this->assertInstanceof(Response::class, $resultResponse = $kernel->handle($request));
    $kernel->terminate($request, $resultResponse);
    self::assertTrue($GLOBALS['DemoBootstrapForKernel']);
    unset($GLOBALS['DemoBootstrapForKernel']);
}

JSON 响应例子

php
public function testWithResponseIsJson(): void
{
    $app = new AppKernel($container = new Container(), '');
    $container->instance('app', $app);

    $request = $this->createMock(Request::class);
    $response = new JsonResponse(['foo' => 'bar']);

    $router = $this->createRouter($response);
    $this->createConfig($container, true);
    $this->createLog($container);
    $this->createRuntime($container);

    $kernel = new Kernel1($app, $router);
    $this->assertInstanceof(IKernel::class, $kernel);
    $this->assertInstanceof(IApp::class, $kernel->getApp());

    $this->assertInstanceof(Response::class, $resultResponse = $kernel->handle($request));
    self::assertSame('{"foo":"bar"}', $resultResponse->getContent());
}

异常处理

路由抛出异常,返回异常响应。

php
# Tests\Kernel\KernelTest::createRouterWithException
protected function createRouterWithException(): IRouter
{
    $this->createMock(Request::class);
    $router = $this->createMock(IRouter::class);
    $router->method('dispatch')->will(self::throwException(new \Exception('hello foo bar.')));

    return $router;
}
php
public function testRouterWillThrowException(): void
{
    $app = new AppKernel($container = new Container(), '');
    $container->instance('app', $app);

    $request = $this->createMock(Request::class);

    $router = $this->createRouterWithException();
    $this->createConfig($container, true);
    $this->createLog($container);
    $this->createRuntimeWithRender($container);

    $kernel = new Kernel1($app, $router);
    $this->assertInstanceof(IKernel::class, $kernel);
    $this->assertInstanceof(IApp::class, $kernel->getApp());

    $this->assertInstanceof(Response::class, $resultResponse = $kernel->handle($request));
    self::assertStringContainsString('hello foo bar.', $resultResponse->getContent());
    self::assertStringContainsString('Exception: hello foo bar. in file', $resultResponse->getContent());
    self::assertStringContainsString('Exception-&gt;()', $resultResponse->getContent());
}

错误处理

路由出现错误,返回错误响应。

php
# Tests\Kernel\KernelTest::createRouterWithError
protected function createRouterWithError(): IRouter
{
    $request = $this->createMock(Request::class);
    $router = $this->createMock(IRouter::class);
    $router->method('dispatch')->will(self::throwException(new \Error('hello bar foo.')));

    return $router;
}
php
public function testRouterWillThrowError(): void
{
    $app = new AppKernel($container = new Container(), '');
    $container->instance('app', $app);

    $request = $this->createMock(Request::class);

    $router = $this->createRouterWithError();
    $this->createConfig($container, true);
    $this->createLog($container);
    $this->createRuntimeWithRender($container);

    $kernel = new Kernel1($app, $router);
    $this->assertInstanceof(IKernel::class, $kernel);
    $this->assertInstanceof(IApp::class, $kernel->getApp());

    $this->assertInstanceof(Response::class, $resultResponse = $kernel->handle($request));

    self::assertStringContainsString('ErrorException: hello bar foo', $resultResponse->getContent());
    self::assertStringContainsString('ErrorException-&gt;()', $resultResponse->getContent());
}

系统中间件

fixture 定义

Tests\Kernel\Kernel2

php
namespace Tests\Kernel;

class Kernel2 extends Kernel
{
    protected array $middlewares = [
        Demo1::class,
    ];
}

Tests\Kernel\Middlewares\Demo1

php
namespace Tests\Kernel\Middlewares;

use Leevel\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class Demo1
{
    public function terminate(\Closure $next, Request $request, Response $response): void
    {
        $GLOBALS['demo_middlewares'][] = 'Demo1::terminate';
        $next($request, $response);
    }
}
php
public function test2(): void
{
    $app = new AppKernel($container = new Container(), '');
    $container->instance('app', $app);

    $request = $this->createMock(Request::class);
    $response = $this->createMock(Response::class);

    $router = $this->createRouter($response);
    $this->createConfig($container, true);
    $this->createLog($container);
    $this->createRuntime($container);

    $kernel = new Kernel2($app, $router);
    $this->assertInstanceof(IKernel::class, $kernel);
    $this->assertInstanceof(IApp::class, $kernel->getApp());
    $this->assertInstanceof(Response::class, $resultResponse = $kernel->handle($request));
    $kernel->terminate($request, $resultResponse);
    self::assertSame(['Demo1::terminate'], $GLOBALS['demo_middlewares']);
    unset($GLOBALS['demo_middlewares']);
}

系统中间件支持 handle 和 terminate

fixture 定义

Tests\Kernel\Kernel3

php
namespace Tests\Kernel;

class Kernel3 extends Kernel
{
    protected array $middlewares = [
        Demo2::class,
    ];
}

Tests\Kernel\Middlewares\Demo2

php
namespace Tests\Kernel\Middlewares;

use Leevel\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class Demo2
{
    public function handle(\Closure $next, Request $request): Response
    {
        $GLOBALS['demo_middlewares'][] = 'Demo2::handle';

        return $next($request);
    }

    public function terminate(\Closure $next, Request $request, Response $response): void
    {
        $GLOBALS['demo_middlewares'][] = 'Demo2::terminate';
        $next($request, $response);
    }
}
php
public function test3(): void
{
    $app = new AppKernel($container = new Container(), '');
    $container->instance('app', $app);

    $request = $this->createMock(Request::class);
    $response = $this->createMock(Response::class);

    $router = $this->createRouter($response);
    $this->createConfig($container, true);
    $this->createLog($container);
    $this->createRuntime($container);

    $kernel = new Kernel3($app, $router);
    $this->assertInstanceof(IKernel::class, $kernel);
    $this->assertInstanceof(IApp::class, $kernel->getApp());
    $this->assertInstanceof(Response::class, $resultResponse = $kernel->handle($request));
    $kernel->terminate($request, $resultResponse);
    self::assertSame(['Demo2::handle', 'Demo2::terminate'], $GLOBALS['demo_middlewares']);
    unset($GLOBALS['demo_middlewares']);
}

系统中间件支持参数

fixture 定义

Tests\Kernel\Kernel4

php
namespace Tests\Kernel;

class Kernel4 extends Kernel
{
    protected array $middlewares = [
        Demo3::class.':5,foo',
    ];
}

Tests\Kernel\Middlewares\Demo3

php
namespace Tests\Kernel\Middlewares;

use Leevel\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class Demo3
{
    public function handle(\Closure $next, Request $request, int $arg1 = 1, string $arg2 = 'hello'): Response
    {
        $GLOBALS['demo_middlewares'][] = sprintf('Demo3::handle(arg1:%s,arg2:%s)', $arg1, $arg2);

        return $next($request);
    }
}
php
public function test4(): void
{
    $app = new AppKernel($container = new Container(), '');
    $container->instance('app', $app);

    $request = $this->createMock(Request::class);
    $response = $this->createMock(Response::class);

    $router = $this->createRouter($response);
    $this->createConfig($container, true);
    $this->createLog($container);
    $this->createRuntime($container);

    $kernel = new Kernel4($app, $router);
    $this->assertInstanceof(IKernel::class, $kernel);
    $this->assertInstanceof(IApp::class, $kernel->getApp());
    $this->assertInstanceof(Response::class, $resultResponse = $kernel->handle($request));
    $kernel->terminate($request, $resultResponse);
    self::assertSame(['Demo3::handle(arg1:5,arg2:foo)'], $GLOBALS['demo_middlewares']);
    unset($GLOBALS['demo_middlewares']);
}