应用资源

资源主要为后台的增删差改等控制器提供了路由、权限的整合配置,避免配置过于繁琐。

名词解释

管理层

在一个系统中可能存在多个后台入口,例如商城中的总后台与店铺端,企业平台中的总后台与企业端,每个后台均有自己不同的功能,我们把每个后台统称为管理层。

路由

路由即为通过 url 访问时定义的路径。

元数据

返回给接口数据时除去 data 的主数据外,meta 字段内的数据均为元数据,例如分页的列表数据为主数据,分页信息则为元数据。

管理资源

定义资源

自定义一个资源,该资源包含路由与权限中间件,通过该方法我们可以创建多个管理层。

资源需要在应用入口文件 App.php 中的 init 方法进行初始化,如下:


$app->getResource()->set(
    "admin",       // 资源名
    (new DuxResource(
        'admin',   // 路由、权限注册名
        '/admin'   // 路由前缀
    ))
    ->addAuthMiddleware(
        new PermissionMiddleware("admin", SystemUser::class),  // 权限检测中间件
        new AuthMiddleware("admin")  // 登录鉴权中间件
    )
);

初始化资源后您可以通过路由、权限、菜单类的各自方法来进行后续操作。

使用资源

资源类需要继承框架内的基础类,并配合资源注解进行使用,继承后该类将会自动扩展 curd 等方法。

引入基础类

use Dux\Resources\Action\Resources;
use Dux\Resources\Attribute\Resource;

继承基础类

<?php

declare(strict_types=1);

namespace App\Example\Admin;

use Dux\Resources\Action\Resources;

class Page extends Resources{
...

注册资源

使用注解方法将资源类注册到资源中:

<?php

declare(strict_types=1);

namespace App\Example\Admin;

use Dux\Resources\Action\Resources;
use Dux\Resources\Attribute\Resource;

#[Resource(app: 'admin',  route: '/example/list', name: 'example.list')] class Example extends Resources
{
...

使用 Resource() 注解类注册资源时可用参数如下:

app required string
路由注册名
route required string
路由路径
name required string
路由名
auth required bool
授权检查 - 如果为 false 则非强制登录
actionsarray|false
[]
路由方法,自动生成子路由,如果为 false 则关闭任何继承方法,可用方法为 many 列表、one 详情、create 创建、edit 编辑、store 保存(单字段更新)、delete 删除、deleteMany 批量删除、trash 彻底删除(软删除)、restore 恢复(软删除),默认为 many、one、create、edit、delete、deleteMany、store
middlewarearray
[]
中间件扩展,可传递多个中间件的类名
softDeletebool
false
软删除支持,开启后将在默认值的路由方法中加入软删除的操作路由
canbool
true
权限检测,如果为 false 将强制忽略该资源的权限验证

创建后将会生成下面下面的路由,可以看到每个路由会自动加入资源路由的前缀 /admin,生成的路由供管理前端进行使用。

GET /admin/example/list/page
GET /admin/example/list/page/{id}
POST /admin/example/list/page
PUT /admin/example/list/page/{id}
DELETE /admin/example/list/page/{id}

资源方法

列表条件

获取列表数据时执行该附加方法,可创建该方法来对前端传递的 url 参数做列表数据筛选。

public function queryMany(Builder $query, ServerRequestInterface $request, array $args): void
{
    // 获取 url 参数 (slimphp 自带方法)
    $params = $request->getQueryParams();

    // 通过参数设置查询条件
    if ($params['num']) {
        $query->where('num', $params['num']);
    }
}

单数据条件

获取单条数据时执行该方法附加方法。

public function queryOne(Builder $query, ServerRequestInterface $request, array $args): void

全局条件

执行增删差改等任何资源方法时会调用该方法,该方法对于多管理层的继承很有用,可以使用处理不通管理层的权限。

public function query(Builder $query)
{
}

数据转换

因为后台使用前后端分离,后端程序不建议将所有字段暴漏出来,所以需要将数据模型中查询出的数据进行过滤或者转换,声明如下方法进行转换。


public function transform(object $item): array
{
    // $item 为通过模型查询出的单条对象,列表与详情数据公用该转换
    return [
        "id" => $item->id,
        "name" => $item->name,
        "time" => $item->created_at?->toDateTimeString(),
    ];
}

表单验证

通过后台操作的添加、编辑或修改均通过此方法进行验证。


public function validator(array $data, ServerRequestInterface $request, array $args): array
{
    return [
        // 验证规则
    ];
}

表单格式化

通过后台操作的添加、编辑或修改均通过此方法进行格式化,格式化后返回数据则为入库数据,需要和模型表字段对应。

public function format(Data $data, ServerRequestInterface $request, array $args): array
{
    // $data 为通过表单验证后的数据对象
    return [
        "name" => $data->name,
        "title" => $data->title,
        "num" => $data->num,
        // 可自定义保存数据
        "status" => true,
    ];
}

附加列表元数据

/**
 * @param object|array $query  模型对象
 * @param array $data 数据转换后的主数据
 * @param ServerRequestInterface $request 请求体对象
 * @param array $args 路由参数
 * @return array
 */
public function metaMany(object|array $query, array $data, ServerRequestInterface $request, array $args): array

附加详情元数据

/**
 * @param object|array $query  模型对象
 * @param array $data 数据转换后的主数据
 * @param ServerRequestInterface $request 请求体对象
 * @param array $args 路由参数
 * @return array
 */
public function metaOne(object|array $query, array $data, ServerRequestInterface $request, array $args): array

附加资源

创建前执行

/**
 * @param Data $data 表单验证后数据
 * @param mixed $info 详情数据对象
 * @return void
 */
public function createBefore(Data $data, mixed $info): void

创建后执行

/**
 * @param Data $data 表单验证后数据
 * @param mixed $info 详情数据对象
 * @return void
 */
public function createAfter(Data $data, mixed $info): void

编辑前执行

/**
 * @param Data $data 表单验证后数据
 * @param mixed $model 详情数据对象
 * @return void
 */
public function editBefore(Data $data, mixed $info): void

编辑后执行

/**
 * @param Data $data 表单验证后数据
 * @param mixed $model 详情数据对象
 * @return void
 */
public function editAfter(Data $data, mixed $info): void

异步保存前执行

/**
 * @param Data $data 表单验证后数据
 * @param mixed $model 详情数据对象
 * @return void
 */
public function storeBefore(Data $data, mixed $info): void

异步保存后执行

/**
 * @param Data $data 表单验证后数据
 * @param mixed $model 详情数据对象
 * @return void
 */
public function storeAfter(Data $data, mixed $info): void

删除前执行

/**
 * @param mixed $model 详情数据对象
 * @return void
 */
public function delBefore(mixed $info): void

删除后执行

/**
 * @param mixed $model 详情数据对象
 * @return void
 */
public function delAfter(mixed $info): void

强制删除前执行

/**
 * @param mixed $model 详情数据对象
 * @return void
 */
public function trashBefore(mixed $info): void

强制删除后执行

/**
 * @param mixed $model 详情数据对象
 * @return void
 */
public function trashAfter(mixed $info): void

删除恢复前执行

/**
 * @param mixed $model 详情数据对象
 * @return void
 */
public function restoreBefore(mixed $info): void

删除恢复后执行

/**
 * @param mixed $model 详情数据对象
 * @return void
 */
public function restoreAfter(mixed $info): void

资源扩展

软删除

软删除支持需要开启资源参数 softDelete: true,并且需要在模型中进行使用复用类增加软删除字段,示例如下:

<?php

declare(strict_types=1);

namespace App\Example\Models;

use Dux\Database\Attribute\AutoMigrate;
use Illuminate\Database\Connection;
use Illuminate\Database\Schema\Blueprint;

#[AutoMigrate]
class Example extends \Dux\Database\Model
{
    public $table = 'example';

    use \Illuminate\Database\Eloquent\SoftDeletes;  
    public function migration(Blueprint $table)
    {
        $table->id();
        $table->timestamps();
        $table->softDeletes();  
    }


    public function seed(Connection $db)
    {
    }
}

::: tip 增加字段后别忘记同步结构至数据库,使用 php dux db:sync 应用名 :::

树形列表

资源类中配置该参数让列表数据支持树形数据。

protected bool $tree = true;

开启后模型必须声明树形方法和增加树形结构字段:

<?php

declare(strict_types=1);

namespace App\Example\Models;

use Dux\Database\Attribute\AutoMigrate;
use Illuminate\Database\Connection;
use Illuminate\Database\Schema\Blueprint;

#[AutoMigrate]
class Example extends \Dux\Database\Model
{
    public $table = 'example';

    use \Kalnoy\Nestedset\NodeTrait;  
    public function migration(Blueprint $table)
    {
        $table->id();
        $table->timestamps();
        \Kalnoy\Nestedset\NestedSet::columns($table);      }

    public function seed(Connection $db)
    {
    }
}

::: tip 增加字段后别忘记同步结构至数据库,使用 php dux db:sync 应用名 :::

分页参数

分页数量主要通过 url 传递 pageSize 参数来定义,并且如果 pageSize 参数为空,则不开启分页。此处设置为不传递参数情况下的默认配置。

protected array $pagination = [
    'status' => true,   // 默认开启分页
    'pageSize' => 10,   // 默认每页数量
];

自定义方法

自带方法不够处理可以使用自定义方法的资源注解,如下:

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Dux\Resources\Attribute\Action;

#[Action(methods: 'POST', route: '/custom')]  public function list(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
{
    return send($response, 'ok');
}

使用 Action() 注解是可以使用以下参数:

参数类型必填默认说明
methodsarray*http 请求方法,GET、POST、DELETE 等支持的方法。
routestring*子路径,该路径会和资源路径拼接
namestring''权限名,不填会自动使用 类名.方法名 作为权限名
authboolnull授权检查 - 如果为 true 则制登录,配合资源注解的 auth 使用
canbooltrue权限检测,如果为 false 将不验证该方法权限,配合资源注解的 can 使用

完整示例

通过指引中的创建一个示例应用我们已经创建了一个基础的资源控制器,下面是最基本的常用示例。

<?php

declare(strict_types=1);

namespace App\Example\Admin;

use Dux\Resources\Action\Resources;

#[Resource(app: 'admin', route: '/example/example', name: 'example.example')]
class Page extends Resources
{
    protected string $model = \App\Example\Models\Example::class;

    public function queryMany(Builder $query, ServerRequestInterface $request, array $args): void
    {
        $params = $request->getQueryParams();
    }

    public function transform(object $item): array
    {
        return [
            "id" => $item->id,
        ];
    }

    public function validator(array $data, ServerRequestInterface $request, array $args): array
    {
        return [
            "name" => ["required", "please enter name"],
        ];
    }

    public function format(Data $data, ServerRequestInterface $request, array $args): array
    {
        return [
            "name" => $data->name,
        ];
    }

    // 自定义动作路由
    #[Action(methods: 'POST', route: '/custom')]
    public function list(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
    {
        return send($response, 'ok');
    }

}