Jak przygotować PHP do obsługi żądań XHR JSON?

Treść dodana: 25 maja 2017. Ostatnia modyfikacja: 25 maja 2017.

JSON jest prostym, a zarazem niezwykle często wykorzystywanym formatem podczas transferowania danych w sieci. Obsługiwany jest przez większość języków programowania, a szczególną popularność zyskał przy różnego rodzaju API. Nie każdy jednak zdaje sobie sprawę, jak powinien wyglądać kod po stronie PHP poprawnie obsługujący format JSON i odpowiedzi zwrotne. W poradniku przedstawię dodatkowo kod oparty o popularną bibliotekę jQuery.

Pierwszą bardzo ważną kwestią są nagłówki. Zazwyczaj początkujący programiści pomijają je zupełnie i tylko dzięki automatycznemu rozpoznawaniu formatu przez bibliotekę jQuery kod funkcjonuje prawidłowo. O samych nagłówkach pisałem już we wcześniejszym artykule dlatego zapraszam teraz do zapoznania się z tematem. Najważniejszą kwestią jest, aby nagłówki były wysłane przed jakąkolwiek inną treścią. Najlepiej, aby stanowiły pierwszą linię w naszym kodzie. Nagłówek dla formatu JSON, z kodowaniem znaków w UTF-8, wygląda następująco:

header("Content-Type: application/json;charset=utf-8");

Dzięki tej jednej linii przeglądarka / nasza aplikacja będzie wiedzieć, jaki otrzymała format danych.

Kolejną istotną kwestią są kody odpowiedzi, w PHP ustawiane poprzez funkcję http_response_code. Kody statusu dzielą się na grupy (omówione również w przytoczonym wcześniej artykule) i to na ich podstawie jQuery decyduje, co zrobić dalej z odpowiedzią. Czy uznać za poprawną, czy może za błędną.

$.ajax({
    url: "jakiś adres"
  })
  // odpowiedź poprawna, domyślnie kod 200
  .done(function(data, textStatus, jqXHR) {
  })
  // odpowiedź błędna, np. kod 500
  .fail(function(jqXHR, textStatus, errorThrown) {
  });
  // w starszych wersjach jQuery success / error - usunięte w jQuery 3.0

To właśnie na tym etapie występuje najwięcej problemów. Początkujący programiści pomijając kod odpowiedzi sprawiają, że zwracany jest zawsze kod 200, co z kolei powoduje zawsze wywołanie funkcji done (success) i konieczność dalszego przetwarzania odpowiedzi – sprawdzanie czy przykładowo zawiera flagę error = true i dopiero na tej podstawie określenie błędu. Poprawny kod przedstawiam niżej:

// ... sprawdzanie warunków takich jak walidacja danych
if ($error) {
  http_response_code(500);
  echo json_encode([
      'message' => 'Nieprawidłowo wypełnione pola'
  ]);
  return;
}

W przypadku wystąpienia błędu ustawiony został kod odpowiedzi 500 (Internal Server Error) dzięki czemu zostanie wywołana funkcja fail (error), oraz załączona została informacja zwrotna w postaci tablicy, którą można dalej wykorzystać – przykładowo wyświetlić komunikat `message` na czerwonym tle. Działanie skryptu zostało przerwane, ponieważ w takim przypadku, nie ma sensu dalsze przetwarzanie.

Kwestia trzecia dotyczy samej odpowiedzi. Jako że skrypt oczekuje informacji zwrotnej w formacie JSON, należy ją zakodować przy użyciu funkcji json_encode. Należy pamiętać aby odpowiedź wyświetlić (echo / print itp), lub w przypadku niektórych frameworków PHP po prostu zwrócić (return) do dalszego przetwarzania.

Na co jeszcze można zwrócić uwagę? Najczęściej dane od przeglądarki będziemy chcieli wysłać metodą POST (nie domyślnym GET), dlatego w opcjach obiektu należy ustawić method / type na POST. Można też jawnie wskazać jakiego typu odpowiedzi jQuery powinno się spodziewać, za pomocą dataType:

$.ajax({
    url: "jakiś adres",
    method: "POST",
    dataType: "json"
  })

Jest jeszcze jeden przypadek o którym warto wspomnieć czyli brak jakiejkolwiek reakcji na odpowiedź, na skutek innego błędu. Jeżeli po stronie PHP włączone jest wyświetlanie błędów, może się okazać iż do odpowiedzi została dołączona dodatkowa treść (Notice, Warning, Fatal Error), która nie może być przetworzona jako format JSON. Dlatego w każdym przypadku warto jest korzystać z konsoli przeglądarki. Zobaczyć czy nie pojawiły się w niej błędy lub sprawdzić faktycznie otrzymaną odpowiedź w zakładce “sieć”.

Przykładowe wywołanie skryptu

Podsumowując, utworzymy prosty formularz służący do kasowania użytkownika z bazy.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script>
        $(function() {
            $('#userdelete').on('submit', function(event) {
                event.preventDefault();
                $.ajax({
                    url: "user_ajax.php",
                    method: "POST",
                    data: $(this).serialize()
                  })
                  .done(function(data, textStatus, jqXHR) {
                      $('#message').html(data.message);
                  })
                  .fail(function(jqXHR, textStatus, errorThrown) {
                      $('#message').html(jqXHR.responseJSON.message);
                  });
            });
        });
    </script>
</head>
<body>
    <div id="message"></div>
    <form action="POST" id="userdelete">
        <input type="text" name="userid">
        <input type="hidden" name="action" value="delete">
        <input type="submit">
    </form>
</body>
</html>

Element #message będzie zawierał zwrotną informację z naszego skryptu `user_ajax.php`.

Przykładowa tabela z użytkownikami utworzona została prostym poleceniem:

CREATE TABLE `users` (
	`user_id` INT(11) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR(50) NOT NULL,
	PRIMARY KEY (`user_id`)
)
ENGINE=InnoDB;

Natomiast sam docelowy skrypt `user_ajax.php` prezentuje się w następująco:

<?php
header("Content-Type: application/json;charset=utf-8");

$userId = (int) $_POST['userid'];
$action = $_POST['action'] ?? null;

if ($action === 'delete') {
    try {
        $dbh = new PDO('mysql:dbname=test;host=127.0.0.1', 'user', 'pass', [
                PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
            ]
        );
        $dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
        $sql = "DELETE FROM users WHERE user_id = ?";
        $sth = $dbh->prepare($sql);
        $sth->execute([$userId]);
        echo json_encode([
            'message' => sprintf('Skasowano %d rekordów', $sth->rowCount())
        ]);
    } catch(PDOException $e) {
        http_response_code(500);
        echo json_encode([
            'message' => 'Klasa PDO zwróciła wyjątek: ' . $e->getMessage()
        ]);
    }
} else {
    http_response_code(500);
        echo json_encode([
            'message' => 'Nieprawidłowa akcja'
        ]);
}

Ustawiamy skrypt zgodnie z wcześniejszymi ustaleniami – wysyłamy odpowiednie nagłówki oraz kod błędu. Do kasowania użytkowników korzystamy z klasy PDO. Przechwycone błędy są automatycznie parsowane jako JSON przez bibliotekę jQuery.

Komentarze

Nie ma jeszcze żadnych komentarzy do wyświetlenia. Może chcesz zostać pierwszą osobą która podzieli się swoją opinią?

Dodaj komentarz

*
Nazwa zostanie wyświetlona wraz z komentarzem. Możesz też utworzyć nowe konto w serwisie, dzięki czemu uzyskasz dodatkową funkcjonalność.
*
Akceptowana jest ograniczona składnia Textile. Wszystkie tagi HTML zostaną usunięte.