不推荐从零手写完整CMS,绝大多数场景应选用Laravel+Livewire/Inertia或October CMS、Statamic等开源CMS二次开发,因其省时、安全、可维护。

直接说结论:不推荐从零手写一个完整 CMS,除非你明确要练架构能力或满足极特殊定制需求;绝大多数场景下,用 Laravel + Livewire/Inertia 或直接基于开源 CMS(如 October CMS、Statamic)二次开发,才是省时、安全、可维护的正解。
为什么别急着写 index.php 和 config/database.php
新手常把「搭建 CMS」等同于「建个数据库、连上 mysqli、写几个增删改查页面」。这确实能跑起来,但很快会卡在几个真实痛点上:
- 用户登录后,怎么防止别人伪造
admin/edit.php?id=123直接篡改别人的文章?—— 你得自己实现 RBAC 权限校验逻辑,而不是只 check$_SESSION['role'] === 'admin' - 编辑器里贴了一段带
<script>的内容,前端直接echo $post['content']就 XSS 了 —— 你得统一做htmlspecialchars(),还得区分富文本输出和纯文本输出场景 - URL 想写成
/news/how-to-build-cms而不是/index.php?slug=how-to-build-cms,得配.htaccess或 Nginx rewrite,还必须保证后台路由(如/admin/login)不被重写规则误伤 - 上线后发现首页加载慢,想加 Redis 缓存文章列表,但你的手写 DAO 层没抽象出缓存开关,改起来得动七八个文件
这些不是“功能”,而是**基础设施负债**。Laravel 的 Auth::user()->can('update', $post)、Blade 自动转义、Route::get('/{slug}', ...)->whereAlphaNumeric('slug')、Cache::remember(...) 已经把这些问题封进方法里了。
php artisan make:controller Admin/PostController 真的比手写快
用 Laravel 构建 CMS 后台的核心节奏是:
立即学习“PHP免费学习笔记(深入)”;
- 先跑
php artisan make:model Post -mcr,自动生成迁移、模型、控制器、资源路由 - 在 migration 里定义字段:
$table->string('slug')->unique()、$table->text('body')、$table->foreignId('user_id')->constrained() - 在
Post模型里加protected $fillable = ['title', 'slug', 'body', 'user_id']和public function user() { return $this->belongsTo(User::class); } - 在
PostController的store()方法里,用request()->validate([...])做表单验证,再auth()->user()->posts()->create(...)关联创建 - 加中间件:运行
php artisan make:middleware EnsureAdmin,然后在路由组里套上->middleware('ensure.admin')
这一套下来,一个带权限、关联、验证、RESTful 路由的后台内容模块,20 分钟内可交付原型。而手写同样功能,光是处理 CSRF token 插入、隐藏字段、POST 回显错误,就容易漏掉一两个地方导致表单失效。
别低估插件机制和钩子系统的复杂度
很多教程教你「加个 plugins/ 目录,遍历读取 plugin.json,再 require 入口文件」,听起来简单。但真实问题藏在细节里:
- 插件 A 修改了
Post模型的toSearchableArray(),插件 B 又想改同一个方法 —— 没有服务容器和依赖注入,根本没法协调优先级 - 你想让插件在「文章保存前」触发,但又不能阻断主流程(比如某插件网络超时不该让整篇文章发布失败)—— 需要事件总线(Event Bus)或队列(Queue),不是
hook_fire('post.saving')一行代码能搞定的 - 插件上传的 JS/CSS 怎么自动注入到后台页面?要不要支持版本号、CDN 回退?静态资源管理一旦放开给第三方,就绕不开资产编译(Vite)、哈希指纹、缓存头设置
Laravel 的 Event::dispatch(new PostSaving($post)) + php artisan event:generate + 队列驱动切换,已经覆盖了 95% 的扩展场景。自己造轮子之前,先确认是否真需要绕过它的生命周期设计。
最易被忽略的一点:CMS 的「内容」从来不是静态的。它要应对 SEO 友好 URL、多语言翻译、定时发布、草稿与发布状态分离、媒体库版本控制……这些不是功能开关,而是数据模型和状态机的设计选择。从第一天起就用 Eloquent 的状态模式(State 类)或 spatie/laravel-model-status 这类包,比后期硬塞 status ENUM('draft','published','archived') 然后满世界改条件判断,要干净得多。



















