Laravel returns the unified format error code

Time:2019-11-14

background

Recently, I was learning to develop an Android project. The back-end interface project started a new project with PHP’s yii2.0 framework, and then changed to laravel 5.5. Recently, I saw that laravel upgraded the new version, so I updated the project to laravel 6.4
In the process of using Yii and laravel, both frameworks are very friendly to web API and support restful in different degrees, but they still encounter some problems. Let’s take laravel 6.4 as an example to briefly describe the problems I have encountered.

Question 1: return page code of the provider

The most typical one is laravel new. After the browser directly accesses localhost, it will enter the default welcome page of laravel framework template. There is no big problem. The problem is that you use postman to use this address as an interface
Call, the returned code is the page code. What you call at Android end is the page code. In fact, you will not call / follow the interface when you call the interface, but some other errors such as 4xx and 5xx will return HTML code.
Android can only judge the success or failure of the request by judging the status code, and it is very difficult to get the error information. In fact, we can add header in Android, but… So we checked online how to deal with it
The first way to solve postman debugging is to set headers x-requested-with: XMLHttpRequest in the request of postman to simulate Ajax request
The second way is to make the project only return to JSON format and create a new middleware

namespace App\Http\Middleware;
use Closure;
class JsonApplication
{
    public function handle($request, Closure $next)
    {
        $request->headers->set('Accept', 'application/json');
        return $next($request);
    }
}

Then register middleware globally in the kernel and apply all API requests (here, because the project is a web API project, the namespace of routes / api.php is removed, so the key in $middlewaregroups is an API)

namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
    protected $middlewareGroups = [
        'api' => [
            ......
            'json_application',
        ],
    ];
    protected $routeMiddleware = [
        ......
        'json_application' => \App\Http\Middleware\JsonApplication::class,
    ];
}

In this way, you can never worry about calling the interface after the configuration is completed, and the page code is returned to you.

Question 2: the interface returns the unified JSON format

The data returned through the above configuration interface is in JSON format, but if you continue to develop, you will find that you still need to use HTTP status code to determine whether it is successful or not, and then the different interfaces of the returned key in JSON are very different, even if the same interface is successful or wrong, it will return different keys.
This problem mostly uses the problem of returning to the same format. Since many interfaces have been written to Vue before, the previous key mode is still used

{
    "code": "0",
    "msg": "ok",
    "data": ""
}

But how to return this format in laravel has become a problem. After several times of searching on the Internet, there is no good solution, most of which are incomplete coverage. Moreover, the error code and error information are all written in the logic layer, and the newly added ones don’t know whether there is any conflict at all.
Later, I searched BD and GG for a long time. I also tried to use the exception mechanism and middleware provided by laravel, but I was not satisfied.
As we all know from Java, it is very convenient to deal with error codes in Java. Define an enumeration directly and write all error codes in it. When an exception is thrown, enumeration is passed in as a parameter. Similar to this
enumeration

package *.*.*
public enum ErrorCode {
    OK("ok", 0),
    PARAM_ERROR("param error", 88888),
    UNKNOWN_ERROR("unknown error", 99999);
    ErrorCode(String value, Integer key) {
        this.value = value;
        this.key = key;
    }
    private String value;
    private Integer key;
    public String getValue() {
        return value;
    }
    public Integer getKey() {
        return key;
    }
}

Exception class

package *.*.*;
import *.*.*.ErrorCode;
public class ApiException extends Exception {
    public int code = 0;
    public ApiException(ErrorCode errorCode) {
        super(errorCode.getValue());
        this.code = errorCode.getKey();
    }
    ......
}

Use

throw new ApiException(ErrorCode.UNKNOWN_ERROR);

So I checked the enumeration of PHP, which is really supported, but after a careful study, I found that the enumeration of PHP not only needs to install and open SPL, but the provided method is useless
So I wrote a copy of Java
Base class

namespace App\Enums;
abstract class Enum
{
    public static function __callStatic($name, $arguments)
    {
        return new static(constant('static::' . $name));
    }
}

The error code here needs to be marked in the gaze because of the magic method

namespace App\Enums;
/**
 * @method static CodeEnum OK
 * @method static CodeEnum ERROR
 */
class CodeEnum extends Enum
{
    public const OK = ['0', 'ok'];
    public const ERROR = ['99999', 'fail'];
    private $code;
    private $msg;
    public function __construct($param)
    {
        $this->code = reset($param);
        $this->msg = end($param);
    }
    public function getCode()
    {
        return $this->code;
    }
    public function getMsg()
    {
        return $this->msg;
    }
}

Custom exception class

namespace App\Exceptions;
use App\Enums\CodeEnum;
use Exception;
use Illuminate\Support\Facades\Log;
class ApiException extends Exception
{
    public function __construct(CodeEnum $enum)
    {
        parent::__construct($enum->getMsg(), $enum->getCode());
    }
    public function report()
    {
        Log::error("ApiException {$this->getFile()}({$this->getLine()}): code({$this->getCode()}) msg({$this->getMessage()})");
    }
    public function render($request)
    {
        return response([
            'code' => $this->getCode(),
            'msg' => $this->getMessage(),
            'data' => ''
        ]);
    }
}

call

Throw new apiexception (New codeenum (codeenum:: error)); // it doesn't look good in this way
Throw new apiexception (codeenum:: ok()); // in this way, the call is similar to the Java call

===========To be continued============