web-dev-qa-db-fra.com

Quelle est la meilleure façon de créer REST API modèle de réponse aux erreurs et système de codes d'erreur?

Mon implémentation REST renverra des erreurs dans JSON avec la structure suivante:

{
 "http_response":400,
 "dev_message":"There is a problem",
 "message_for_user":"Bad request",
 "some_internal_error_code":12345
}

Je suggère de créer un modèle de réponse spécial, où je peux transmettre les valeurs nécessaires pour les propriétés (dev_message, message_for_user, some_internal_error_code), et les renvoyer. Dans le code, cela ressemblerait à ceci:

$responseModel = new MyResponseModel(400,"Something is bad", etc...);

À quoi devrait ressembler ce modèle? Dois-je mettre en œuvre des méthodes, par exemple successResponse () où je ne transmettrai que des informations textuelles, et le code sera par défaut 200 là-bas? Je suis coincé avec ça. Et ceci est la première partie de ma question: dois-je mettre en œuvre ce modèle, est-ce une bonne pratique? Parce que pour l'instant, je retourne juste des tableaux directement à partir du code.

La deuxième partie concerne le système de code d'erreur. Les codes d'erreur seront décrits dans la documentation. Mais le problème que je rencontre est dans le code. Quelle est la meilleure façon de gérer les codes d'erreur? Dois-je les écrire à l'intérieur du modèle? Ou serait-il préférable de créer un service distinct pour gérer cela?

MISE À JOUR 1

J'ai implémenté une classe de modèle pour la réponse. C'est la réponse de Greg similaire, la même logique, mais en plus j'ai codé en dur erreurs écrites dans le modèle et voici à quoi cela ressemble:

    class ErrorResponse
    {
     const SOME_ENTITY_NOT_FOUND = 100;
     protected $errorMessages = [100 => ["error_message" => "That entity doesn't exist!"]];

     ...some code...
    }

Pourquoi j'ai fait ça? Et pour quoi faire?

  1. C'est cool dans le code: return new ErrorResponse(ErrorResponse::SOME_ENTITY_NOT_FOUND );
  2. Message d'erreur facile à modifier. Tous les messages sont au même endroit au lieu de contrôleur/service/etc ou quoi que vous le placiez.

Si vous avez des suggestions pour améliorer cela, veuillez commenter.

16
Grokking

Dans cette situation, je pense toujours à l'interface d'abord, puis j'écris PHP code pour la supporter).

  1. Il s'agit d'une API REST, donc des codes d'état HTTP significatifs sont indispensables.
  2. Vous souhaitez que des structures de données cohérentes et flexibles soient envoyées vers et depuis le client.

Pensons à toutes les choses qui pourraient mal tourner et à leurs codes d'état HTTP:

  • Le serveur renvoie une erreur (500)
  • Échec d'authentification (401)
  • La ressource demandée est introuvable (404)
  • Les données que vous modifiez ont été modifiées depuis que vous les avez chargées (409)
  • Erreurs de validation lors de l'enregistrement des données (422)
  • Le client a dépassé son taux de demande (429)
  • Type de fichier non pris en charge (415)

Notez qu'il y en a d'autres que vous pouvez rechercher plus tard.

Pour la plupart des conditions d'échec, un seul message d'erreur doit être renvoyé. Le 422 Unprocessable Entity la réponse, que j'ai utilisée pour les "erreurs de validation" pourrait renvoyer plusieurs erreurs --- Une ou plusieurs erreurs par champ de formulaire.

Nous avons besoin d'une structure de données flexible pour les réponses aux erreurs.

Prenons comme exemple, le 500 Internal Server Error:

HTTP/1.1 500 Internal Server Error
Content-Type: text/json
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

{
    "errors": {
        "general": [
            "Something went catastrophically wrong on the server! BWOOP! BWOOP! BWOOP!"
        ]
    }
}

Comparez cela avec de simples erreurs de validation lorsque vous essayez de POST quelque chose au serveur:

HTTP/1.1 422 Unprocessable Entity
Content-Type: text/json
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

{
    "errors": {
        "first_name": [
            "is required"
        ],
        "telephone": [
            "should not exceed 12 characters",
            "is not in the correct format"
        ]
    }
}

La clé ici est le type de contenu étant text/json. Cela indique aux applications clientes qu'elles peuvent décoder le corps de réponse avec un décodeur JSON. Si, par exemple, une erreur de serveur interne n'est pas détectée et que votre page Web générique "Quelque chose s'est mal passé" est livrée à la place, le type de contenu doit être text/html; charset=utf-8 pour que les applications clientes n'essaient pas de décoder le corps de la réponse en JSON.

Cela ressemble à tout trouver et dandy, jusqu'à ce que vous ayez besoin de prendre en charge les réponses JSONP . Vous devez renvoyer un 200 OK réponse, même en cas d'échecs. Dans ce cas, vous devrez détecter que le client demande une réponse JSONP (généralement en détectant un paramètre de demande d'URL appelé callback) et modifier un peu la structure des données:

(GET/posts/123? Callback = displayBlogPost)

<script type="text/javascript" src="/posts/123?callback=displayBlogPost"></script>

HTTP/1.1 200 OK
Content-Type: text/javascript
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

displayBlogPost({
    "status": 500,
    "data": {
        "errors": {
            "general": [
                "Something went catastrophically wrong on the server! BWOOP! BWOOP! BWOOP!"
            ]
        }
    }
});

Ensuite, le gestionnaire de réponse sur le client (dans un navigateur Web) doit avoir une fonction JavaScript globale appelée displayBlogPost qui accepte un seul argument. Cette fonction devrait déterminer si la réponse a réussi:

function displayBlogPost(response) {
    if (response.status == 500) {
        alert(response.data.errors.general[0]);
    }
}

Nous avons donc pris soin du client. Maintenant, prenons soin du serveur.

<?php

class ResponseError
{
    const STATUS_INTERNAL_SERVER_ERROR = 500;
    const STATUS_UNPROCESSABLE_ENTITY = 422;

    private $status;
    private $messages;

    public function ResponseError($status, $message = null)
    {
        $this->status = $status;

        if (isset($message)) {
            $this->messages = array(
                'general' => array($message)
            );
        } else {
            $this->messages = array();
        }
    }

    public function addMessage($key, $message)
    {
        if (!isset($message)) {
            $message = $key;
            $key = 'general';
        }

        if (!isset($this->messages[$key])) {
            $this->messages[$key] = array();
        }

        $this->messages[$key][] = $message;
    }

    public function getMessages()
    {
        return $this->messages;
    }

    public function getStatus()
    {
        return $this->status;
    }
}

Et pour l'utiliser en cas d'erreur de serveur:

try {
    // some code that throws an exception
}
catch (Exception $ex) {
    return new ResponseError(ResponseError::STATUS_INTERNAL_SERVER_ERROR, $ex->message);
}

Ou lors de la validation de la saisie utilisateur:

// Validate some input from the user, and it is invalid:

$response = new ResponseError(ResponseError::STATUS_UNPROCESSABLE_ENTITY);
$response->addMessage('first_name', 'is required');
$response->addMessage('telephone', 'should not exceed 12 characters');
$response->addMessage('telephone', 'is not in the correct format');

return $response;

Après cela, vous avez juste besoin de quelque chose qui prend l'objet de réponse retourné et le convertit en JSON et envoie la réponse sur son chemin joyeux.

14
Greg Burghardt