【问题标题】:Wrong parameters error in extended Exception class in PHP 7.0PHP 7.0 扩展异常类中的错误参数错误
【发布时间】:2016-11-02 01:39:15
【问题描述】:

我正在 PHP 7.0 (Ubuntu 16.04) 上使用 Slim 框架构建 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);
    }

}

但是当我在验证期间抛出这个异常时():

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中明确实现了不带参数的构造函数时,它如何显示定义为EmailAlreadyUsedException([string $message [, long $code [, Throwable $previous = NULL]]])

更新

我试图调试并找出为什么 $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
        ]);

EmailAlreadyUsedExceptionJsonApiException 的构造函数,$params 数组似乎是空的。我又错过了什么吗?

【问题讨论】:

    标签: php exception-handling slim php-7


    【解决方案1】:

    您的所有异常类都调用parent::__construct。这意味着最终将调用\Exception 类构造函数。您没有为该构造函数提供正确的参数。

    您可以从堆栈跟踪中看出:

    跟踪:#0 /var/www/ElectroAbhi/app/Exceptions/JsonApiException.php(33): Exception->__construct(Array, 0, Array)

    您的EmailAlreadyUsedException 调用JsonApiException 的构造函数,它使用(Array, 0, Array) 调用PHP 原生\Exception 的构造函数,这不是构造函数所期望的。

    你必须修复这两行:

     $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 的参数必须与签名匹配

    [字符串 $message [, long $code [, Throwable $previous = NULL]]]]

    在派生类中重写构造函数不会重写父类中的构造函数。

    【讨论】:

    • 但是我将正确的参数从JsonApiException 传递给\Exception 的构造函数?重写构造函数可以让你改变它的定义,不是吗?
    • @abishek 堆栈跟踪表明您正在传递一个数组、一个数字和一个数组,这不是父构造函数的签名所期望的。覆盖派生类中的构造函数不会覆盖父类中的构造函数。
    • 我试图调试为什么$this->params["message"] 是构造函数中的一个数组并得到了全新的结果。你能检查我的更新吗?
    • @abhishek 我无法重现 print_r($params) 以提供空数组。不知道你为什么会得到这个,但你在问题中显示的代码不会产生这个。
    • 问题已解决,请看我的回答。但是,是的,这很奇怪, print_r($params) 仍然给我一个空数组,即使 array_replace_recursive 采用正确的值。
    【解决方案2】:

    经过长时间的调试,故障不在 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 设置了正确的值。

    【讨论】:

      猜你喜欢
      • 2023-03-16
      • 2012-08-26
      • 2015-07-11
      • 2012-08-19
      • 2016-01-03
      • 1970-01-01
      • 2015-11-10
      • 1970-01-01
      • 2016-07-03
      相关资源
      最近更新 更多