truecrouton/retort

简单的 PHP 路由定义和请求验证

v1.0.1 2023-08-17 00:20 UTC

This package is not auto-updated.

Last update: 2024-09-26 21:54:31 UTC


README

使用 PHP 8 中引入的属性轻松定义 PHP 路由和验证请求!删除了大量的路由样板代码,请求验证非常简单!

只需将 Route 属性添加到控制器类的方法中,以定义路由方法。这消除了重复的路由定义文件的需要。

#[Route('GET', '/thing/{id:\d+}')]
public function thingGet(ThingGetRequest $request): array {}

为了验证请求,将 Validation 属性添加到请求类的属性中,并使用 createObject() 验证和创建请求对象。确保路由方法具有所需请求参数。

use Retort\Validation\ValidNumber;
use Retort\Validation\ValidString;

class ThingGetRequest extends RetortRequest
{
    #[ValidNumber(true, 1)] // $id will be a number >= 1 and is required (true)
    public int $id;
}

$request = Validation::createObject(ThingGetRequest::class, ['id' => 0]); // throws Error
$request = Validation::createObject(ThingGetRequest::class, ['id' => 1]); // creates a ThingGetRequest object with id = 1

使用 Validation 属性属性,可以从 RetortRequest 对象自动生成用于前端使用的 TypeScript 或其他类型!(见下文)

定义和调用路由方法

使用 Route 属性在控制器类内定义路由方法。

use Retort\Mapping\Attributes\Route;

#[Route('GET', '/thing/{id:\d+}')]
public function thingGet(Id $request): array
{
    // return a "thing"
    return ['name' => 'A thing'];
}

使用 getRoutes() 辅助函数获取每个控制器类的路由。

use Retort\Mapping\Helper;

$routes = Helper::getRoutes(YourController::class, AnotherController::class);

在路由中使用路由信息。这里是一个使用 FastRoute 的示例,但它应该与许多不同的路由器一起工作。

$dispatcher = FastRoute\simpleDispatcher(function (FastRoute\RouteCollector $r) {
    $routes = Helper::getRoutes(YourController::class);

    foreach ($routes as $route) {
        $r->addRoute($route['requestMethod'], $route['uri'], [
            'class' => $route['class'],
            'classMethod' => $route['classMethod'],
            'requestType' => $route['requestType']
        ]);
    }
});

使用路由信息调用路由方法。

// Validate the request (see below)
// $route['requestType'] specifies the request class
$request = Validation::createObject($route['requestType'], $payload);

$class = new $route['class'](); // $route['class'] specifies the controller class
$method = $route['classMethod']; // $route['classMethod] specifies the class method

header('Content-Type: application/json; charset=utf-8');
print json_encode($class->$method($request)); // call the class method

验证请求

使用 Validation::createObject() 验证请求。Validation::createObject() 将创建指定的对象,如果验证失败则抛出 Error

class ThingGetRequest extends RetortRequest
{
    #[ValidNumber(true, 1)] // $id will be a number >= 1 and is required (true)
    public int $id;
}

$request = Validation::createObject(ThingGetRequest::class, ['id' => 0]); // throws Error
$request = Validation::createObject(ThingGetRequest::class, ['id' => 1]); // creates a ThingGetRequest object with id = 1

ValidStringValidNumberValidObject 提供了 Validation 对象。检查它们的 类定义 以获取验证选项,例如必需、最小值、最大值等。

ValidObject 可用于嵌套对象和对象数组。

class GoingConcern extends RetortRequest
{
    #[ValidObject(true, Address::class)]
    public Address $address;

    #[ValidObject(true, Employees::class)]
    public array $employees; // use array type
}

参数可以是可选的。

#[ValidString(false, 1)] // $nickname will be a string of length >= 1 and is optional (false)
public ?string $nickname;

依赖注入

提供了一些 DependencyController 作为示例。例如,MysqlController 构造函数接受一个 mysqli 对象 $myDb,然后可以在控制器中使用它,例如通过包含的 executeQuery() 方法。

use Retort\Controller\MysqlController;

class ThingController extends MysqlController
{
    #[Route('GET', '/thing/post')]
    public function thingPost(ThingPostRequest $request): array
    {
        $this->executeQuery('insert into things (name) values(?)', [$request->name]);
        return ['thingId' => $this->myDb->insert_id];
    }
}

$db = new mysqli('localhost', 'user', 'super_secure_password', 'database');
$class = new ThingController($db); // inject $db

生成类型

运行 vendor/bin/retort_typegen -c <file> 与您的配置文件一起生成类型。配置文件是一个 TOML 文件,其中定义了配置选项和类型定义模板。有关详细信息,请参阅示例 配置。模板以 mustache 格式定义。

提供要生成的类。类必须自动加载,并且是 RetortRequest 的实例。

# Generate type definitions for these PHP classes
classes = [
    "Retort\\Test\\Helper\\Jacket"
]

设置模板,在这种情况下是一个 TypeScript 类模板。

# Sample template for Typescript type generation in mustache format
template = '''
interface {{class}} {
    {{#definitions}}
    {{name}}{{#nullable}}?{{/nullable}}: {{type}}{{#iterable}}[]{{/iterable}};
    {{/definitions}}
}
'''

可以定义不同语言的不同模板。例如,这里是一个 flutter 类模板。

class {{class}} {
    {{#definitions}}
    final {{type}}{{#nullable}}?{{/nullable}} {{name}};
    {{/definitions}}

    const {{class}}({
        {{#definitions}}
        {{^nullable}}required {{/nullable}}this.{{name}},
        {{/definitions}}
    });

    factory {{class}}.fromJson(Map<String, dynamic> json) {
        return {{class}}(
        {{#definitions}}
        {{name}}: json['{{name}}'],
        {{/definitions}}
        );
    }

    factory {{class}}.fromObject(Map<String, dynamic> json) {
        return {{class}}(
        {{#definitions}}
        {{name}}: json['{{name}}'],
        {{/definitions}}
        );
    }

    Map<String, dynamic> toJson() {
        return {
        {{#definitions}}
        '{{name}}': {{name}},
        {{/definitions}}
        };
    }
}

可以在 typeMap 下将 PHP 类型映射到其他语言。

# Type mappings, e.g., int (php) to number (ts)
[typeMap]
int = "number"