在使用 PHP 开发 API 时,经常会遇到需要返回错误信息的场景。前端根据返回的错误码,再进行相应的操作。
发现问题
这里用 ThinkPHP8 举个例子,这是一个用户登录接口的例子:
public function login()
{
$username = $this->request->param('username');
$password = $this->request->param('password');
if (!$username || !$password) {
$result = ['code' => 1001, 'msg' => '参数不完整'];
} else if ($username !== 'admin') {
$result = ['code' => 1002, 'msg' => '用户不存在'];
} else if ($password !== '123456') {
$result = ['code' => 1003, 'msg' => '密码错误'];
} else {
$result = ['code' => 1, 'msg' => '登录成功'];
}
return json($result);
}
上面的代码里,对提交的参数进行一些简单的判断,定义了 $result 数组,并为每个不同的错误都提供了一个错误码和提示信息。最后返回给前端。为了让前端能理解错误码,后端开发人员需要把上面的 code 和 msg 都写在 api 文档的错误码里,以供前端参考。
不过大家是否觉得这种直接在代码里零散地写 code 和 msg 不够直观和优雅(在实际开发过程中,不大可能会如此集中地显示错误信息),而且容易不小心把 code 写错或者重复?或许我们可以换一种方式来表达错误码。
解决方案
我先把调整后的代码贴出:
public function login()
{
$username = $this->request->param('username');
$password = $this->request->param('password');
if (!$username || !$password) {
$status = LoginStatus::PARAM_INCOMPLETE;
} else if ($username !== 'admin') {
$status = LoginStatus::USER_NOT_FOUND;
} else if ($password !== '123456') {
$status = LoginStatus::PASSWORD_WRONG;
} else {
$status = LoginStatus::SUCCESS;
}
return json($status->apiResult());
}
上面代码没有了 code 和 msg 信息,但是却不难理解 $status 所表达的含义,让代码看上去清爽、直观很多。
要实现上面的代码,我们使用到了2个 PHP 8 里的新特性,1、枚举(php8.1 新增),2、注解。下面看下是如何实现上面的代码效果的。
首先看一下枚举类型 LoginStatus 的完整代码:
<?php
namespace app\enum;
use app\attribute\Description;
enum LoginStatus: int
{
#[Description('登录成功')]
case SUCCESS = 1;
#[Description('参数不完整')]
case PARAM_INCOMPLETE = 1001;
#[Description('用户不存在')]
case USER_NOT_FOUND = 1002;
#[Description('密码错误')]
case PASSWORD_WRONG = 1003;
/**
* 获取当前枚举的描述
* @return string
*/
public function getDescription(): string
{
$reflectionClass = new \ReflectionClass($this);
return $reflectionClass->getReflectionConstant($this->name)->getAttributes()[0]->getArguments()[0];
}
/**
* 返回API接口所需的数据
*
* @return array
*/
public function apiResult()
{
return [
'code' => $this->value, // 状态码
'msg' => $this->getDescription() // 描述
];
}
}
可以看到 LoginStatus 里定义了4个枚举值,用来表示登录时会出现的错误代码,通过集中管理错误代码,可以让编码显得直观,也避免一些 code 出现重复的问题。
同时利用注解特性,增加自定义注解 Description ,用以描述枚举值的含义,通过 getDescription 方法就可以获取枚举值对应的描述。
apiResult 方法则用来返回接口所需要的 code 和 msg 信息。
自定义注解类 Description 的代码如下:
<?php
namespace app\attribute;
class Description
{
}
由于我们并没有实例化这个注解类,而是通过反射类里的信息获取注解内容,所以里面并没有任何代码,只做了类的定义。
继续优化
我们可以把 getDescription
和 apiResult
提取出来,放到 trait 中,作为公共方法(也可以封装成父级枚举类型)。这样可以用在更多不同的枚举类型上。以下是一个示例:
<?php
namespace app\traits;
trait EnumHelper
{
/**
* 获取当前枚举的描述
* @return string
*/
public function getDescription(): string
{
$reflectionClass = new \ReflectionClass($this);
return $reflectionClass->getReflectionConstant($this->name)->getAttributes()[0]->getArguments()[0];
}
/**
* 返回 API 接口所需的数据
* @return array
*/
public function apiResult(): array
{
return [
'code' => $this->value,
'msg' => $this->getDescription()
];
}
}
然后我们在 LoginStatus
枚举中使用这个 trait:
<?php
namespace app\enum;
use app\attribute\Description;
use app\traits\EnumHelper;
enum LoginStatus: int
{
use EnumHelper;
#[Description('登录成功')]
case SUCCESS = 1;
#[Description('参数不完整')]
case PARAM_INCOMPLETE = 1001;
#[Description('用户不存在')]
case USER_NOT_FOUND = 1002;
#[Description('密码错误')]
case PASSWORD_WRONG = 1003;
}
这样我们可以将 EnumHelper
trait 重用到其他枚举类型中,提供一致的描述和 API 返回格式。
文章总结
通过使用 PHP8 的枚举和注解特性,我们可以更加优雅地处理 API 中的错误码和错误信息。相比于传统的在代码中直接写 code
和 msg
,这种方式更加直观、清晰,并且可以集中管理错误代码,减少重复和错误的可能性。
利用 PHP8 的新特性,我们能够编写出更加优雅和高效的代码,提高整个项目的可维护性和可读性。希望通过这篇文章,能够给大家带来一些新的思路和启发。在实际项目中,可以根据具体需求进一步扩展和优化代码。