В этой ситуации я всегда сначала думаю об интерфейсе, а затем пишу код PHP для его поддержки.
- Это REST API, поэтому значимые коды состояния HTTP просто необходимы.
- Вы хотите, чтобы согласованные, гибкие структуры данных отправлялись клиенту и от него.
Давайте подумаем обо всех вещах, которые могут пойти не так, и их кодах статуса HTTP:
- Сервер выдает ошибку (500)
- Ошибка аутентификации (401)
- Запрашиваемый ресурс не найден (404)
- Изменяемые данные были изменены с момента загрузки (409)
- Ошибки проверки при сохранении данных (422)
- Клиент превысил свой запрос (429)
- Неподдерживаемый тип файла (415)
Обратите внимание, что есть другие, которые вы можете исследовать позже.
Для большинства условий сбоя возвращается только одно сообщение об ошибке. 422 Unprocessable Entity
Ответ, который я использовал для «ошибки проверки» может вернуть более одной ошибки --- Один или несколько ошибок в поле формы.
Нам нужна гибкая структура данных для ответов об ошибках.
Возьмите в качестве примера 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!"
]
}
}
Сравните это с простыми ошибками проверки при попытке отправить что-то на сервер:
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"
]
}
}
Ключевым моментом здесь является тип контента text/json
. Это сообщает клиентским приложениям, что они могут декодировать тело ответа с помощью JSON-декодера. Если, скажем, внутренняя ошибка сервера не обнаружена и вместо этого доставляется ваша общая веб-страница «Что-то пошло не так», тип контента должен быть text/html; charset=utf-8
таким, чтобы клиентские приложения не пытались декодировать тело ответа как JSON.
Это выглядит все найти и модно, пока вам не нужно поддерживать ответы JSONP . Вы должны вернуть 200 OK
ответ, даже за сбои. В этом случае вам нужно будет определить, что клиент запрашивает ответ JSONP (обычно путем определения вызываемого параметра запроса URL callback
), и немного изменить структуру данных:
(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!"
]
}
}
});
Тогда обработчик ответа на клиенте (в веб-браузере) должен иметь глобальную функцию JavaScript, displayBlogPost
которая принимает один аргумент. Эта функция должна определить, был ли ответ успешным:
function displayBlogPost(response) {
if (response.status == 500) {
alert(response.data.errors.general[0]);
}
}
Итак, мы позаботились о клиенте. Теперь позаботимся о сервере.
<?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;
}
}
И использовать это в случае ошибки сервера:
try {
// some code that throws an exception
}
catch (Exception $ex) {
return new ResponseError(ResponseError::STATUS_INTERNAL_SERVER_ERROR, $ex->message);
}
Или при проверке ввода пользователя:
// 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;
После этого вам просто нужно что-то, что возьмет возвращенный объект ответа и преобразует его в JSON и отправит ответ по-своему.