当 CRUD 表格不再足够时
Source: Dev.to

任何使用 PHP 构建过后台面板的人都知道这个故事通常是怎么结束的。一开始,一切都很简单:一张表格、一个表单、几个过滤器。随后真实需求出现——数据之间的关联、更复杂的流程、部分更新——经典的“表格 + 表单” CRUD 开始出现问题。
这不是 CRUD 的错。问题在于后台界面不仅仅是 CRUD。
一个真正的后台需要:
- 在不同的上下文中查看数据,
- 在不丢失状态的情况下导航关联关系,且
- 快速编辑相关实体。
这些思考促使我探索了各种方法,直到我决定从零开始构建一个系统,脱离最流行的框架。这就是我最终的选择。
对问题的另一种思考方式
与其想着 “我需要构建一个页面”,不如换成 “我需要描述这段数据的视图”。
这让我想到了 builders:描述 显示什么 以及 如何显示 的 PHP 对象。它们不是前端组件,也不是神奇的小部件——仅仅是生成 HTML、处理查询并返回 HTML 或 JSON 响应的 PHP 结构。
代码仍然是 PHP。流程仍然是 request → processing → response。
一个具体示例:文章
让我们从最简单的情况开始:一个管理标题和内容的文章模块。
模型定义数据结构
class PostsModel extends AbstractModel
{
protected function configure($rule): void
{
$rule->table('#__posts')
->id()
->string('title')->index()
->text('content')->formType('editor');
}
#[Validate('title')]
public function validateTitle($current_record_obj): string
{
$value = $current_record_obj->title;
if (strlen($value) model, 'idTablePosts')
->field('content')->truncate(50)
->field('title')->link('?page=posts&action=edit&id=%id%')
->setDefaultActions();
$response = array_merge($this->getCommonData(), $tableBuilder->getResponse());
Response::render(MILK_DIR . '/Theme/SharedViews/list_page.php', $response);
}
#[RequestAction('edit')]
public function postEdit()
{
$response = $this->getCommonData();
$response['form'] = FormBuilder::create($this->model, $this->page)->getForm();
Response::render(MILK_DIR . '/Theme/SharedViews/edit_page.php', $response);
}
}
TableBuilder 已经会处理搜索、分页和排序。FormBuilder 知道要显示哪些字段以及如何验证它们。你只需要描述 想要做什么——构建器会生成其余部分。
截图
视图是普通的 PHP 模板——没有模板引擎,也没有特殊语法。如果需要修改,只需打开文件直接编辑 HTML。
当事情变得复杂时:关系
简单的 CRUD 在任何地方都能工作。问题出现在实体之间有关联时,例如:
Recipes
└─ Comments
当关系变得交互式时,通常的选项是:
- 将所有内容拆分到多个页面(失去流畅性),或
- 依赖管理状态的重量级框架(失去透明度和控制权)。
像 Laravel Nova 或 Filament 这样的工具功能强大,在结构化团队中表现良好。但当流程非常具体时,往往很难弄清后端和前端之间到底发生了什么。
我想要一种不同的方式:保持一切在 PHP 中,使用 fetch 来避免页面重新加载,但又不隐藏实际的运行过程。
食谱:使用 Fetch 完全实现的 CRUD 与关系
这是完整示例:带评论的食谱、两层界面、无需页面刷新。
食谱模型
class RecipeModel extends AbstractModel
{
protected function configure($rule): void
{
$rule->table('#__recipes')
->id()
->hasMany('comments', RecipeCommentsModel::class, 'recipe_id')
->image('image');
}
}
title('name')->index()
->text('ingredients')->formType('textarea')
->select('difficulty', ['Easy', 'Medium', 'Hard']);
}
评论模型
class RecipeCommentsModel extends AbstractModel
{
protected function configure($rule): void
{
$rule->table('#__recipe_comments')
->id()
->int('recipe_id')->formType('hidden')
->text('comment');
}
}
其他所有内容——列表、侧边抽屉表单、模态框、自动刷新——都使用相同的构建器和相同的思维模型实现。
为什么采用这种方式
这 不是 另一个框架。它是一种思考后台管理面板的方式。
- 构建器减少了模板代码,但代码仍然是可读的 PHP。
- 如果构建器不能满足需求,你随时可以回落到普通 PHP。
- 将 JSON 作为后端与前端之间的契约,易于调试——没有隐藏状态,没有复杂的生命周期。PHP 发送指令,浏览器执行。
- 最重要的是,关系的处理使用与基本 CRUD 相同的工具。无需学习专门的关系系统。
适用人群
如果你使用 Laravel 并且对它很满意,这可能不适合你。
但如果你处于以下情形之一:
- 需要维护无法迁移的现有 PHP 项目。
- 在内部工具、CRM 或管理系统上工作。
- 需要快速理解代码,即使是几个月后。
- 不想追逐复杂的生态系统。
- 想要看起来和感觉都像 PHP 的代码。
那么重新思考我们构建后台界面的方式可能是值得的。
后台面板 不是 公开网站。它们是工作工具。最好的工具能够出色完成任务,随时间保持可理解性,并且在需求变化时不强迫你重写所有内容。
这些思考来源于 MilkAdmin 的开发,这是我积极维护的 PHP 后台面板系统。代码、演示和文档可在 milkadmin.org 获取,采用 MIT 许可证。



