本文目录导读:

在PHP开发中,抽取公共模块(或公共代码)的核心原则可以概括为:“高内聚、低耦合” + “避免重复(DRY)” + “职责单一(SRP)”。
但实际落地时,针对不同粒度(函数、类、包、服务),有很多具体的坑要避开,以下是PHP特有的、且实用性极强的公共模块抽取原则:
核心架构原则
依赖倒置(面向接口,而非具体实现)
- 原则:抽取公共模块时,不要让上层业务依赖底层模块的具体类,而应依赖其接口或抽象类。
- PHP实践:使用 Composer 管理包时,通过
composer.json中的require引入接口包,具体实现由容器(DI Container)注入,这样,当底层从Redis换成Memcached时,上层业务代码零改动。
优先组合,而不是继承
- 原则:Java/C++ 喜欢用抽象类(Abstract Class)来抽取公共逻辑,但 PHP(尤其是现代PHP)更推荐使用 Trait 或 组合模式。
- PHP具体场景:
- Trait:用于抽取出多个不相关类中的重复方法(例如日志记录、权限校验),虽然好,但不要滥用,Trait 用多了会导致代码难以追踪。
- 组合/服务类:如果一个模块有状态(比如数据库连接、文件句柄),应该拆成一个独立的 Service 类(如
LoggerService),通过构造函数注入,而不是让业务类去继承它。
单一职责(Single Responsibility Principle)
- 原则:一个公共模块只做一件事,并且做好。
- PHP反例:不要创建一个
CommonHelper.php文件,里面塞满了formatDate()、sendSms()、generateToken()这些毫无关联的函数,这种“上帝类”很难测试和维护。 - 正解:拆分为
DateHelper、SmsNotifier、TokenService。
开闭原则(Open-Closed)
- 原则:对扩展开放,对修改关闭。
- PHP实践:公共模块抽出来后,需求变更时应通过新增代码(如策略模式、回调函数)来扩展,而不是直接修改公共模块的代码本身。
PHP 工程实现原则(具体细节)
避免“万能”函数文件
- 错误:有些项目习惯建立一个
app/helpers.php,把乱七八糟的全局函数塞进去,并用 Composer 的files自动加载。 - 正确:尽量用静态方法或门面(Facade),除非是极简的数组操作或字符串操作(如
array_get),函数文件一旦多了,命名冲突(尤其和第三方包冲突)是致命的。
明确“公共”的边界(上下文无关)
- 原则:抽出来的模块不能携带具体业务上下文。
- PHP反例:在
Common/ExcelExport模块中,exportUserList方法里包含WHERE user_status = 1这样的SQL,这就是错误,公共模块只负责接收参数并输出文件,具体查询逻辑应留在业务层调用。
配置外部化(Config Externalization)
- 原则:公共模块中不要写死任何环境相关的常数(如API Key、数据库表名、文件路径)。
- PHP实践:使用环境变量(
getenv()或$_ENV),通过依赖注入的方式将配置传递给公共模块。
分层的粒度控制(不要过度抽取)
- 应用层(Laravel/ThinkPHP):只抽取跨项目的通用逻辑(如支付接口、物流查询、统一响应格式)。不要把业务表字段的格式化逻辑抽到公共层。
- 基础层(框架无关):抽取纯逻辑(日期计算、字符串加密、设计模式组件),这部分则一定要保证和框架完全解耦(不依赖
Illuminate或ThinkPHP的类)。
命名与依赖管理原则
依赖关系严格定向
- 公共模块 不允许 反向依赖业务模块。
- 依赖方向:
业务代码->业务公共模块->基础公共模块->框架/Vendor包。
命名空间(Namespace)的统一
- 抽取的模块要有固定的命名空间前缀,
App\Lib\或App\Support\。 - 防止和
Vendor中的全局命名空间冲突,避免简单的 前缀命名(如class _Common是古老的PHP4风格,不推荐)。
用 Composer 进行物理隔离(重要)
- 如果项目是多应用程序(如管理后台 + API + 前端网站),应将公共模块抽取为独立的内部 Composer 包(private repository),而不是用
git submodule或复制目录。 - 这能让版本管理更清晰(
require acme/common: ^1.2)。
实战反例诊断(哪些不该抽?)
- “看起来像”但不属于公共的业务规则:
UserModel::getGold()方法,如果只有这一个项目使用,它属于业务模型,不要抽入公共Common目录。
- 只出现过一次的代码:如果一个代码块只在两个极端场景下被复用,但逻辑差异巨大,强行通过参数切换只能导致“面条式”代码。
- 包含 HTTP 请求或 Session 的逻辑:公共模块严禁直接用
$_SESSION或$_GET,如果需要,必须通过参数传递跨层数据。
判断是否抽取成功的“三看”清单
| 指标 | 优秀 | 差 |
|---|---|---|
| 一看依赖 | 模块不依赖外部的全局状态,测试时只需new即可。 | 模块里写死了 DB::table('xxx') 或 file_put_contents。 |
| 二看调用 | 调用者只依赖于1个方法或1个职责。 | 调用者必须传入 $flag=1 或者 $type='admin' 来走不同的分支。 |
| 三看变更 | 修改同步需求时,只需改公共模块1处。 | 修改了公共模块,导致另外3个无关业务报错(强耦合)。 |
最后的建议:在PHP中,“不成熟的抽象”(The Wrong Abstraction)比“重复代码”(Duplication)更危险,如果你不确定那个重复逻辑未来会不会变,先复制一份,等第三次出现时再考虑抽取,这往往比一开始就设计过度要好。