PHP 5.6+ 支持用 const 在独立命名空间中集中定义常量,如 namespace App\Constants; const STATUS_SUCCESS = 1;,须通过完全限定名(如 \App\Constants\STATUS_SUCCESS)或 use const 导入访问,避免全局污染与命名冲突。

用 const 在独立命名空间中集中定义常量
PHP 5.6+ 支持在命名空间中直接声明常量,这是统一管理系统的前提。别再把 STATUS_SUCCESS、API_TIMEOUT 散落在各个文件里——它们应该属于一个明确的上下文,比如 App\Constants 或 App\Errors。
常见错误是直接在全局作用域写 const,或误以为类常量(class::CONST)和命名空间常量可混用。实际上,命名空间常量不依赖类,也不参与自动加载,必须用完全限定名或 use const 导入才能访问。
- 定义示例(
app/constants/Errors.php):namespace App\Errors; const USER_NOT_FOUND = 1001; const INVALID_TOKEN = 1002; const RATE_LIMIT_EXCEEDED = 429;
- 调用时不能省略
\App\Errors\USER_NOT_FOUND,除非显式导入:use const App\Errors\USER_NOT_FOUND; echo USER_NOT_FOUND; // ✅ 正确
- 避免在同一个命名空间里重复定义同名常量,PHP 不会报错但后续引用行为未定义
在 Echo 中间件或控制器里安全读取并注入错误码
Echo 是 Go 框架,它本身不处理 PHP 常量。如果你在 Go 项目里提“系统常量”,实际要解决的是 Go 侧的错误码组织问题——和 PHP 的 const 机制无关。别被命名混淆:Echo 框架的错误码应走 Go 的 const + var + 接口组合方式。
典型做法是定义一个错误类型,把业务码、HTTP 状态、消息打包进去,而不是裸用整数。否则你很快会在日志里看到一堆 1001,却不知道它对应用户不存在还是数据库连接失败。
- 定义结构体和预设错误变量(
pkg/errors/errors.go):type AppError struct { Code int Status int Message string } var ErrUserNotFound = &AppError{Code: 1001, Status: http.StatusNotFound, Message: "user not found"} var ErrInvalidToken = &AppError{Code: 1002, Status: http.StatusUnauthorized, Message: "invalid token"} - 在中间件中统一格式化响应:
e.HTTPErrorHandler = func(err error, c echo.Context) { if appErr, ok := err.(*AppError); ok { c.JSON(appErr.Status, map[string]interface{}{ "code": appErr.Code, "message": appErr.Message, }) return } c.JSON(http.StatusInternalServerError, map[string]interface{}{"code": 500, "message": "internal error"}) } - 控制器中直接抛出预定义变量:
func getUser(c echo.Context) error { user, err := db.FindUserByID(c.Param("id")) if err != nil { return ErrUserNotFound // ✅ 不是 return errors.New("...") } return c.JSON(http.StatusOK, user) }
为什么不用 map[int]string 存错误码?
看起来方便,但运行时查表慢、无编译期检查、IDE 无法跳转、重构困难。更关键的是,Go 的 error 接口要求实现 Error() string 方法,而纯 map 返回值不是 error 类型,无法被 if err != nil 自然捕获。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
另一个坑是把错误码硬编码在 handler 里:c.JSON(404, map[string]string{"code": "1001"})。这样分散定义,一旦码值变更,所有地方都要改,且无法做全局校验。
- 真正需要的是:编译期可验证、运行时可断言、日志可追溯、HTTP 响应可映射
- 如果项目已有大量字符串错误码(如
"ERR_USER_NOT_FOUND"),建议用 iota + 字符串常量过渡:const ( ErrUserNotFound Code = iota + 1000 ErrInvalidToken ) func (c Code) String() string { switch c { case ErrUserNotFound: return "user not found" case ErrInvalidToken: return "invalid token" default: return "unknown error" } }
跨服务/多语言场景下怎么保持错误码一致?
PHP 和 Go 同属一个后端体系时,错误码必须对齐,但实现方式不同。PHP 用命名空间常量,Go 用自定义 error 类型,二者不能共享同一份源文件,也不能靠生成代码硬同步——维护成本太高。
真正可行的做法是:用一份人类可读的 YAML/JSON 描述错误码规范(含 code、http_status、zh/en message、分类标签),然后分别写脚本生成 PHP 命名空间常量文件和 Go 错误变量定义。这样变更只需改一份 spec,CI 流程自动触发生成。
容易被忽略的一点是:HTTP 状态码和业务错误码不该强绑定。例如 429 Rate Limit Exceeded 是标准 HTTP 状态,但它的业务含义可能对应多个内部码(如按 IP 限流 vs 按 token 限流)。所以 Go 侧的 AppError.Status 应该由业务逻辑决定,而不是由 Code 数值自动映射。

















