我正在使用Slim Framework在PHP 7.0(Ubuntu 16.04)上构建REST API。为了正确处理异常,我扩展了基类\Exception
类,如下所示:
namespace App\Exceptions;
class AppException extends \Exception
{
}
然后我将此作为所有应用程序异常的基本异常。对于将JSON响应提供给用户的所有异常,我编写了另一个类:
namespace App\Exceptions;
use Slim\Container;
use Slim\Http\Request;
use Slim\Http\Response;
class JsonApiException extends AppException
{
private $params = [
"message" => "",
"code" => 0,
"previous" => null,
"api" => [
"message" => "",
"code" => "",
"status" => 200
]
];
public function __construct(array $params)
{
$this->params = array_merge_recursive($this->params, $params);
parent::__construct($this->params["message"], 0, $this->params["previous"]);
}
public function getApiMessage() {
return $this->params["api"]["message"];
}
public function getApiCode() {
return $this->params["api"]["code"];
}
public function getHttpStatusCode() {
return $this->params["api"]["status"];
}
public function shouldBeLogged() {
return false;
}
public function log(Container $container, Request $request) {
if(!$this->shouldBeLogged()) return;
$logger = $container->get('logger.info');
$logger->info($this, array_merge($request->getHeaders(), [
"method" => $_SERVER["REQUEST_METHOD"],
"time" => $_SERVER["REQUEST_TIME"],
"query_string" => $_SERVER["QUERY_STRING"],
"host" => $_SERVER["HTTP_HOST"],
"referer" => $_SERVER["HTTP_REFERER"],
"user_agent" => $_SERVER["HTTP_USER_AGENT"],
"ip" => $_SERVER["REMOTE_ADDR"],
"uri" => $_SERVER["REQUEST_URI"]
]));
}
public function httpRespond(Response $response) {
return $response->withJson([
"error" => true,
"errors" => [
"server" => [[
"code" => $this->getApiCode(),
"message" => $this->getApiMessage()
]]
]
], $this->getHttpStatusCode());
}
}
然后我将此作为所有JSON错误的基本异常。我使用以下错误让客户知道它提供的用于注册的电子邮件地址已经存在。
namespace App\Exceptions\Validation\User;
use App\Exceptions\JsonApiException;
class EmailAlreadyUsedException extends JsonApiException
{
public function __construct()
{
parent::__construct([
"message" => "The e-mail provided by the exception has already been used",
"api" => [
"message" => "The provided e-mail address has already been used",
"code" => "EmailAlreadyUsed"
],
"previous" => null
]);
}
}
每当发生错误时,我都会将其添加到另一个自定义异常中,以便在验证期间立即启用多个错误的响应:
namespace App\Exceptions;
use Slim\Http\Response;
class JsonApiMultipleException extends JsonApiException
{
private $httpStatusCode = 200;
private $exceptions = [];
public function __construct($httpStatusCode = 200, \Exception $previous = null)
{
parent::__construct([]);
$this->httpStatusCode = 200;
}
public function setHttpStatusCode(int $code) {
$this->httpStatusCode = $code;
}
public function add(string $param, JsonApiException $exception) {
if(!array_key_exists($param, $this->exceptions)) {
$this->exceptions[$param] = [];
}
$this->exceptions[$param][] = $exception;
}
public function length() {
$len = 0;
foreach ($this->exceptions as $param => $exceptions) {
$len += count($exceptions);
}
return $len;
}
public function map() {
$mapped = [];
foreach ($this->exceptions as $param => $exceptions) {
$mapped[$param] = array_map(function (JsonApiException $exception) {
return [
"code" => $exception->getApiCode(),
"message" => $exception->getApiMessage()
];
}, $exceptions);
}
return $mapped;
}
public function getExceptions() {
return $this->exceptions;
}
public function httpRespond(Response $response)
{
return $response->withJson([
"error" => true,
"errors" => $this->map()
], $this->httpStatusCode);
}
}
但是当我在validation()期间抛出此异常时:
namespace App\Validators;
use App\Exceptions\Validation\User\EmailAlreadyUsedException;
use App\Exceptions\Validation\User\InvalidEmailException;
use App\Exceptions\Validation\User\InvalidFirstNameException;
use App\Exceptions\Validation\User\InvalidLastNameException;
use App\Exceptions\Validation\User\InvalidPasswordException;
use Respect\Validation\Validator as v;
class UserValidator extends Validator
{
//...
private function validateEmail() {
//Validate e-mail
if(!v::stringType()->email()->validate($this->user->getEmail())) {
$this->exception->add('email', new InvalidEmailException());
}
//Check if e-mail already used
if(\UserQuery::create()->filterByEmail($this->user->getEmail())->count() > 0) {
$this->exception->add('email', new EmailAlreadyUsedException());
}
}
//...
}
抛出以下异常:
[Thu Jun 30 05:42:47 2016] Slim Application Error:
Type: Error
Message: Wrong parameters for App\Exceptions\Validation\User\EmailAlreadyUsedException([string $message [, long $code [, Throwable $previous = NULL]]])
File: /var/www/ElectroAbhi/app/Exceptions/JsonApiException.php
Line: 33
Trace: #0 /var/www/ElectroAbhi/app/Exceptions/JsonApiException.php(33): Exception->__construct(Array, 0, Array)
#1 /var/www/ElectroAbhi/app/Exceptions/Validation/User/EmailAlreadyUsedException.php(19): App\Exceptions\JsonApiException->__construct(Array)
#2 /var/www/ElectroAbhi/app/Validators/UserValidator.php(58): App\Exceptions\Validation\User\EmailAlreadyUsedException->__construct()
#3 /var/www/ElectroAbhi/app/Validators/UserValidator.php(32): App\Validators\UserValidator->validateEmail()
#4 /var/www/ElectroAbhi/app/Routes/API/User/Create.php(39): App\Validators\UserValidator->validate()
#5 [internal function]: App\Routes\API\User\Create->__invoke(Object(Slim\Http\Request), Object(Slim\Http\Response), Array)
#6 /var/www/ElectroAbhi/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php(41): call_user_func(Object(App\Routes\API\User\Create), Object(Slim\Http\Request), Object(Slim\Http\Response), Array)
#7 /var/www/ElectroAbhi/vendor/slim/slim/Slim/Route.php(325): Slim\Handlers\Strategies\RequestResponse->__invoke(Object(App\Routes\API\User\Create), Object(Slim\Http\Request), Object(Slim\Http\Response), Array)
#8 /var/www/ElectroAbhi/vendor/slim/slim/Slim/MiddlewareAwareTrait.php(116): Slim\Route->__invoke(Object(Slim\Http\Request), Object(Slim\Http\Response))
#9 /var/www/ElectroAbhi/vendor/slim/slim/Slim/Route.php(297): Slim\Route->callMiddlewareStack(Object(Slim\Http\Request), Object(Slim\Http\Response))
#10 /var/www/ElectroAbhi/vendor/slim/slim/Slim/App.php(443): Slim\Route->run(Object(Slim\Http\Request), Object(Slim\Http\Response))
#11 /var/www/ElectroAbhi/vendor/slim/slim/Slim/MiddlewareAwareTrait.php(116): Slim\App->__invoke(Object(Slim\Http\Request), Object(Slim\Http\Response))
#12 /var/www/ElectroAbhi/vendor/slim/slim/Slim/App.php(337): Slim\App->callMiddlewareStack(Object(Slim\Http\Request), Object(Slim\Http\Response))
#13 /var/www/ElectroAbhi/vendor/slim/slim/Slim/App.php(298): Slim\App->process(Object(Slim\Http\Request), Object(Slim\Http\Response))
#14 /var/www/ElectroAbhi/public/index.php(105): Slim\App->run()
#15 {main}
我真的很困惑,当我在EmailAlreadyUsedException([string $message [, long $code [, Throwable $previous = NULL]]])
中明确实现了没有参数的构造函数时,如何将定义显示为EmailAlreadyUsedException
?
更新
我尝试调试并找出为什么$ this-> params [“message”]是JsonApiException
的构造函数中的数组,但现在我更加困惑:
class JsonApiException extends AppException
{
private $params = [
"message" => "",
"code" => 0,
"previous" => null,
"api" => [
"message" => "",
"code" => "",
"status" => 200
]
];
public function __construct(array $params)
{
print_r($params);
die;
$this->params = array_merge_recursive($this->params, $params);
parent::__construct($this->params["message"], 0, $this->params["previous"]);
}
}
结果 - > Array ()
即使我正在传递
parent::__construct([
"message" => "The e-mail provided by the exception has already been used",
"api" => [
"message" => "The provided e-mail address has already been used",
"code" => "EmailAlreadyUsed"
],
"previous" => null
]);
从EmailAlreadyUsedException
到JsonApiException
的构造函数,$ params数组似乎空了。我又错过了什么吗?
答案 0 :(得分:2)
您的所有异常类都会调用parent::__construct
。这意味着,最终将调用\Exception
类构造函数。您没有为该构造函数提供正确的参数。
您可以从堆栈跟踪中了解到:
追踪:#0 /var/www/ElectroAbhi/app/Exceptions/JsonApiException.php(33):异常 - > __ construct(Array,0,Array)
您的EmailAlreadyUsedException
调用JsonApiException
的构造函数,该构造函数使用\Exception
调用PHP的本机(Array, 0, Array)
的构造函数,这不是构造函数所期望的
你必须解决这两行问题:
$this->params = array_merge_recursive($this->params, $params);
parent::__construct($this->params["message"], 0, $this->params["previous"]);
显然,$this->params["message"]
和$this->params["previous"]
是数组。但是传递给parent::__construct
的参数必须与签名
[string $ message [,long $ code [,Throwable $ previous = NULL]]]
覆盖派生类中的构造函数不会覆盖父类中的构造函数。
答案 1 :(得分:0)
经过长时间的调试会话后,故障不在OOP行为中。它是在我用来合并JsonApiException
中的默认参数的函数中。我没有使用array_merge_recursive
merges the elements of one or more arrays together so that the values of one are appended to the end of the previous one
,而是使用了array_replace_recursive
。
使用array_merge_recursive
时,JsonApiException
的$ params属性被赋值:
Array
(
[message] => Array
(
[0] =>
[1] => The e-mail provided by the exception has already been used
)
[code] => Array
(
[0] => 0
[1] => 0
)
[api] => Array
(
[message] => Array
(
[0] =>
[1] => The provided e-mail address has already been used
)
[code] => Array
(
[0] =>
[1] => EmailAlreadyUsed
)
[status] => Array
(
[0] => 200
[1] =>
)
)
)
然而,当使用array_replace_recursive
时,$ params属性变为:
Array
(
[message] => The e-mail provided by the exception has already been used
[code] => 0
[previous] =>
[api] => Array
(
[message] => The provided e-mail address has already been used
[code] => EmailAlreadyUsed
[status] => 200
)
)
反过来为\Exception::__construct()
提供了正确的参数。
但有一件事很奇怪,虽然它有效,但当我尝试这样做时:
class JsonApiException extends AppException
{
private $params = [
"message" => "",
"code" => 0,
"previous" => null,
"api" => [
"message" => "",
"code" => "",
"status" => 200
]
];
public function __construct(array $params)
{
print_r($params);
die;
$this->params = array_replace_recursive($this->params, $params);
parent::__construct($this->params["message"], 0, $this->params["previous"]);
}
}
我仍然得到Array ()
,这很奇怪,因为array_replace_recursive
设置了更正值。