当 CRUD 表格不再足够时

发布: (2026年1月10日 GMT+8 05:43)
7 min read
原文: Dev.to

Source: Dev.to

Cover image for “When CRUD Tables Are No Longer Enough”

giuliopanda

任何使用 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 NovaFilament 这样的工具功能强大,在结构化团队中表现良好。但当流程非常具体时,往往很难弄清后端和前端之间到底发生了什么。

我想要一种不同的方式:保持一切在 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');
    }
}

其他所有内容——列表、侧边抽屉表单、模态框、自动刷新——都使用相同的构建器和相同的思维模型实现。

MilkAdmin 演示

为什么采用这种方式

不是 另一个框架。它是一种思考后台管理面板的方式。

  • 构建器减少了模板代码,但代码仍然是可读的 PHP。
  • 如果构建器不能满足需求,你随时可以回落到普通 PHP。
  • 将 JSON 作为后端与前端之间的契约,易于调试——没有隐藏状态,没有复杂的生命周期。PHP 发送指令,浏览器执行。
  • 最重要的是,关系的处理使用与基本 CRUD 相同的工具。无需学习专门的关系系统。

适用人群

如果你使用 Laravel 并且对它很满意,这可能不适合你。

但如果你处于以下情形之一:

  • 需要维护无法迁移的现有 PHP 项目。
  • 在内部工具、CRM 或管理系统上工作。
  • 需要快速理解代码,即使是几个月后。
  • 不想追逐复杂的生态系统。
  • 想要看起来和感觉都像 PHP 的代码。

那么重新思考我们构建后台界面的方式可能是值得的。

后台面板 不是 公开网站。它们是工作工具。最好的工具能够出色完成任务,随时间保持可理解性,并且在需求变化时不强迫你重写所有内容。

这些思考来源于 MilkAdmin 的开发,这是我积极维护的 PHP 后台面板系统。代码、演示和文档可在 milkadmin.org 获取,采用 MIT 许可证。

https://github.com/giuliopanda/milk-admin

Back to Blog

相关文章

阅读更多 »