前言
最近在使用 DcatAdmin 开发后台管理系统时,遇到了一个让我困惑很久的问题。明明已经做了 API 分离,为什么还需要在主控制器里手动编写"更新"、"删除"等方法?这不是 DcatAdmin 应该自动处理的吗?
经过深入研究代码和路由配置,我终于搞清楚了这个"坑"的本质。在这里记录下来,希望能帮到遇到类似问题的朋友。
问题描述
我的项目中有两个控制器:
-
`AutoOrderController.php` - 负责页面展示和 CRUD
-
`AutoOrderApiController.php` - 负责业务逻辑 API
看起来已经做了 API 分离,但是在 `AutoOrderController` 中却还需要写这些方法:
```php
/**
* 更新網址
*/
public function updateUrl($id)
{
return $this->urlForm()->update($id);
}
/**
* 刪除網址
*/
public function destroyUrl($id)
{
return $this->urlForm()->destroy($id);
}
```
为什么其他正常使用 DcatAdmin 的项目,都不需要写这些方法,Grid 和 Form 就能自动完成 CRUD 操作?
原因分析
- 标准 DcatAdmin CRUD 流程
在典型的 DcatAdmin 项目中,只需要:
```php
class OrderController extends AdminController
{
protected function grid()
{
return Grid::make(new Order(), function (Grid $grid) {
$grid->column('id');
$grid->column('name');
// ...
});
}
protected function form()
{
return Form::make(new Order(), function (Form $form) {
$form->text('name');
// ...
});
}
}
```
然后在路由中注册:
```php
Admin::resource('orders', OrderController::class);
```
这样 DcatAdmin 会自动生成 RESTful 路由:
-
GET /orders → index
-
POST /orders → store
-
PUT /orders/{id} → update
-
DELETE /orders/{id} → destroy
这种情况下,确实不需要手写 update/destroy 方法。
- 我的项目为什么不同?
关键在于:我在一个页面中实现了两个独立的 Grid(表格)
```php
public function index(Content $content)
{
return $content
->header('下單工具')
->body(function (Row $row) {
$row->column(12, $this->orderDataGrid()); // 订单资料表格
$row->column(12, $this->urlManagementGrid()); // 网址管理表格
$row->column(12, $this->logCard());
});
}
```
两个 Grid 各自设置了不同的 resource:
```php
// 订单表格
$grid->setResource('auto-order/order-tool');
// 网址表格
$grid->setResource('auto-order/urls');
```
这意味着:
-
订单表格的操作 → 请求 `/admin/auto-order/order-tool/{id}`
-
网址表格的操作 → 请求 `/admin/auto-order/urls/{id}`
- 路由配置证实了这一点
查看 `app/Admin/routes.php`:
```php
// 订单资料路由
$router->get('auto-order/order-tool/{id}/edit', 'AutoOrderController@edit');
$router->put('auto-order/order-tool/{id}', 'AutoOrderController@update');
$router->delete('auto-order/order-tool/{id}', 'AutoOrderController@destroy');
// 网址路由
$router->get('auto-order/urls/{id}/edit', 'AutoOrderController@editUrl');
$router->put('auto-order/urls/{id}', 'AutoOrderController@updateUrl');
$router->delete('auto-order/urls/{id}', 'AutoOrderController@destroyUrl');
```
当用户在页面中点击"编辑网址"按钮时:
-
Dcat 前端发送请求:`PUT /admin/auto-order/urls/{id}`
-
路由匹配到:`AutoOrderController@updateUrl`
-
**如果删除这个方法 → 直接 404 错误!**
核心要点
| 场景 | 是否需要手写 CRUD 方法 |
|------|----------------------|
| 一个页面一个 Grid,使用 `Admin::resource()` | ❌ 不需要 |
| 一个页面多个 Grid,各自独立 resource | ✅ 必须手写 |
| 自定义路由,不使用标准 RESTful 结构 | ✅ 必须手写 |
最佳实践建议
如果你的页面只管理一个模型
推荐使用标准方式,让 DcatAdmin 自动处理:`
``php
// 路由
Admin::resource('orders', OrderController::class);
// 控制器
class OrderController extends AdminController
{
protected function grid() { /* ... */ }
protected function form() { /* ... */ }
// 不需要写 update/destroy 等方法
}
```
如果你需要在一个页面管理多个模型
就像我的项目这样,必须:
-
为每个 Grid 设置独立的 `resource`
-
为每个 resource 手动实现对应的 CRUD 方法
-
在路由文件中手动注册这些路由
php// Grid 设置 $grid->setResource('auto-order/urls'); // 控制器方法 public function editUrl($id, Content $content) { /* ... */ } public function updateUrl($id) { /* ... */ } public function destroyUrl($id) { /* ... */ } // 路由 $router->get('auto-order/urls/{id}/edit', 'AutoOrderController@editUrl'); $router->put('auto-order/urls/{id}', 'AutoOrderController@updateUrl'); $router->delete('auto-order/urls/{id}', 'AutoOrderController@destroyUrl');
关于 API 分离
我的项目确实做了 API 分离:
-
`AutoOrderController` - 负责页面渲染和基础 CRUD
-
`AutoOrderApiController` - 负责复杂业务逻辑(处理订单、管理进程、生成报告等)
但 CRUD 操作仍然需要在主控制器中实现,因为:
-
Dcat 的 Grid/Form 组件需要标准的 CRUD 端点
-
这些端点必须返回 Dcat 期望的响应格式
-
API 控制器主要服务于外部调用或复杂业务流程
总结
DcatAdmin 的自动 CRUD 功能很强大,但前提是你遵循它的标准模式。当你需要在一个页面中管理多个模型,或者需要自定义路由结构时,就必须手动实现相应的方法。
这不是框架的 bug,而是设计上的灵活性。理解了这一点,就不会再对"为什么需要手写这些方法"感到困惑了。