JSON (JavaScript Object Notation) to lekki format wymiany danych, który jest łatwy do odczytu zarówno dla ludzi, jak i maszyn. Jest standardem w komunikacji między aplikacjami webowymi.
REST API (Representational State Transfer) to architektura tworzenia API wykorzystująca protokół HTTP do komunikacji.
1. JSON – Podstawy
Struktura JSON
{
"name": "Jan Kowalski",
"age": 25,
"active": true,
"email": null,
"roles": ["admin", "editor"],
"address": {
"city": "Warszawa",
"zip": "00-001"
}
}
Typy danych w JSON:
- String:
"tekst" - Number:
42,3.14 - Boolean:
true,false - null:
null - Array:
[1, 2, 3] - Object:
{"key": "value"}
2. json_encode() – Konwersja PHP → JSON
Podstawowe użycie
<?php
// Tablica asocjacyjna → JSON object
$user = [
'id' => 123,
'username' => 'ProGamer',
'email' => 'pro@example.com',
'active' => true,
'balance' => null
];
$json = json_encode($user);
echo $json;
// {"id":123,"username":"ProGamer","email":"pro@example.com","active":true,"balance":null}
// Czytelne formatowanie (pretty print)
$json_pretty = json_encode($user, JSON_PRETTY_PRINT);
echo $json_pretty;
/*
{
"id": 123,
"username": "ProGamer",
"email": "pro@example.com",
"active": true,
"balance": null
}
*/
?>
Opcje json_encode()
<?php
$data = [
'name' => 'Janek',
'city' => 'Kraków',
'tags' => ['php', 'javascript'],
'html' => '<script>alert("XSS")</script>',
'unicode' => 'Zażółć gęślą jaźń',
'url' => 'https://example.com/path?param=value&other=123'
];
// Domyślnie
echo json_encode($data);
// {"name":"Janek","city":"Krak\u00f3w",...}
// JSON_PRETTY_PRINT - czytelne formatowanie
echo json_encode($data, JSON_PRETTY_PRINT);
// JSON_UNESCAPED_UNICODE - nie escapuj Unicode
echo json_encode($data, JSON_UNESCAPED_UNICODE);
// {"city":"Kraków","unicode":"Zażółć gęślą jaźń"}
// JSON_UNESCAPED_SLASHES - nie escapuj ukośników
echo json_encode($data, JSON_UNESCAPED_SLASHES);
// {"url":"https://example.com/path?param=value&other=123"}
// JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_QUOT - bezpieczeństwo XSS
echo json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT);
// {"html":"\u003Cscript\u003Ealert(\"XSS\")\u003C\/script\u003E"}
// JSON_NUMERIC_CHECK - konwertuj string-liczby na liczby
$mixed = ['age' => '25', 'price' => '19.99', 'name' => 'Jan'];
echo json_encode($mixed, JSON_NUMERIC_CHECK);
// {"age":25,"price":19.99,"name":"Jan"}
// JSON_FORCE_OBJECT - wymuś obiekt zamiast tablicy
$indexed_array = ['a', 'b', 'c'];
echo json_encode($indexed_array); // ["a","b","c"]
echo json_encode($indexed_array, JSON_FORCE_OBJECT); // {"0":"a","1":"b","2":"c"}
// Kombinacja opcji (operator |)
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>
Sprawdzanie błędów json_encode()
<?php
$data = [
'name' => 'Test',
'invalid' => "\xB1\x31" // Nieprawidłowy UTF-8
];
$json = json_encode($data);
if ($json === false) {
// Błąd podczas konwersji
echo "Błąd JSON: " . json_last_error_msg() . "<br>";
echo "Kod błędu: " . json_last_error() . "<br>";
/*
Kody błędów:
JSON_ERROR_NONE = 0 - Brak błędu
JSON_ERROR_DEPTH = 1 - Przekroczono maksymalną głębokość
JSON_ERROR_STATE_MISMATCH = 2 - Nieprawidłowy lub źle sformułowany JSON
JSON_ERROR_CTRL_CHAR = 3 - Błąd znaku kontrolnego
JSON_ERROR_SYNTAX = 4 - Błąd składni
JSON_ERROR_UTF8 = 5 - Nieprawidłowe znaki UTF-8
*/
} else {
echo "JSON: $json";
}
// JSON_PARTIAL_OUTPUT_ON_ERROR - zwróć częściowy JSON mimo błędów
$json = json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR);
echo $json; // {"name":"Test","invalid":null}
?>
Przykład – Konwersja danych z bazy do JSON
<?php
mysqli_report(MYSQLI_REPORT_OFF);
$db = @new mysqli('localhost', 'root', '', 'gaming_hub');
if ($db->connect_error) {
die('Błąd połączenia');
}
$db->set_charset('utf8mb4');
// Pobierz użytkowników z bazy
$stmt = $db->prepare("SELECT id, username, email, level, coins, created_at FROM users LIMIT 10");
$stmt->execute();
$wynik = $stmt->get_result();
$users = [];
while ($row = $wynik->fetch_assoc()) {
$users[] = $row;
}
$stmt->close();
$db->close();
// Konwertuj do JSON
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'success' => true,
'count' => count($users),
'data' => $users
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
/*
Output:
{
"success": true,
"count": 10,
"data": [
{
"id": "1",
"username": "ProGamer",
"email": "pro@example.com",
"level": "15",
"coins": "1000",
"created_at": "2024-01-15 10:30:00"
},
...
]
}
*/
?>
3. json_decode() – Konwersja JSON → PHP
Podstawowe użycie
<?php
$json = '{"id":123,"username":"ProGamer","email":"pro@example.com","active":true}';
// Dekoduj jako obiekt (domyślnie)
$obj = json_decode($json);
echo $obj->username; // ProGamer
echo $obj->email; // pro@example.com
// Dekoduj jako tablicę asocjacyjną (drugi parametr: true)
$array = json_decode($json, true);
echo $array['username']; // ProGamer
echo $array['email']; // pro@example.com
?>
Głębokość dekodowania
<?php
$json = '{
"user": {
"profile": {
"address": {
"city": "Warszawa"
}
}
}
}';
// Domyślna głębokość: 512
$data = json_decode($json, true);
echo $data['user']['profile']['address']['city']; // Warszawa
// Ograniczona głębokość (trzeci parametr)
$data = json_decode($json, true, 2); // Max 2 poziomy
// Błąd - przekroczono głębokość
if (json_last_error() !== JSON_ERROR_NONE) {
echo "Błąd: " . json_last_error_msg();
}
?>
Sprawdzanie błędów json_decode()
<?php
// Nieprawidłowy JSON
$invalid_json = '{"name":"Test", "age":25,}'; // Przecinek na końcu!
$data = json_decode($invalid_json, true);
if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
echo "Błąd JSON: " . json_last_error_msg() . "<br>";
echo "Kod błędu: " . json_last_error() . "<br>";
} else {
print_r($data);
}
// Funkcja pomocnicza do bezpiecznego dekodowania
function safeJsonDecode($json, $assoc = true) {
$data = json_decode($json, $assoc);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception('JSON decode error: ' . json_last_error_msg());
}
return $data;
}
try {
$data = safeJsonDecode($invalid_json);
} catch (Exception $e) {
echo "Błąd: " . $e->getMessage();
}
?>
Przykład – Odbieranie JSON od klienta
<?php
// Odbierz surowe dane POST
$json_input = file_get_contents('php://input');
// Dekoduj JSON
$data = json_decode($json_input, true);
if ($data === null) {
http_response_code(400); // Bad Request
echo json_encode([
'success' => false,
'error' => 'Invalid JSON: ' . json_last_error_msg()
]);
exit;
}
// Przetwórz dane
$username = $data['username'] ?? '';
$email = $data['email'] ?? '';
// Walidacja
if (empty($username) || empty($email)) {
http_response_code(400);
echo json_encode([
'success' => false,
'error' => 'Username and email are required'
]);
exit;
}
// Zapisz do bazy...
// ...
// Zwróć odpowiedź JSON
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'message' => 'User registered successfully',
'user_id' => 123
]);
?>
4. REST API – Podstawy
Wyobraź sobie, że masz aplikację, która potrzebuje danych znajdujących się na innym serwerze. Aplikacja nie musi wiedzieć, jak działa baza danych ani jak napisany jest serwer. Wysyła po prostu żądanie do API i otrzymuje odpowiedź.
API (Application Programming Interface) to sposób, w jaki różne programy mogą się ze sobą komunikować.
REST API to API, które wykorzystuje standardowe mechanizmy HTTP, takie jak GET, POST, PUT i DELETE, do pracy z danymi.
Można to porównać do kelnera w restauracji:
Ty (aplikacja)
↓
"Poproszę listę użytkowników"
↓
REST API
↓
pobiera dane z bazy
↓
REST API
↓
zwraca dane w JSON
↓
Ty (aplikacja)
Nie musisz wiedzieć, jak API pobrało dane z bazy. Wystarczy, że znasz sposób komunikacji z API.
W naszym projekcie PHP będzie właśnie takim pośrednikiem:
Aplikacja
↓
żądanie HTTP
↓
REST API w PHP
↓
baza danych
↓
REST API w PHP
↓
odpowiedź JSON
↓
Aplikacja
Co oznacza REST?
REST to sposób projektowania API, w którym zasoby, np. użytkownicy, są dostępne pod określonymi adresami, czyli endpointami.
Przykładowo:
GET /api/v1/users → pobierz użytkowników
GET /api/v1/users/5 → pobierz użytkownika o ID 5
POST /api/v1/users → utwórz użytkownika
PUT /api/v1/users/5 → zmień użytkownika o ID 5
DELETE /api/v1/users/5 → usuń użytkownika o ID 5
Dzięki temu sam adres i metoda HTTP mówią nam, co chcemy zrobić.
REST (Representational State Transfer) to styl architektury API wykorzystujący:
- Metody HTTP: GET, POST, PUT, DELETE
- URL-e jako zasoby:
/users,/users/123 - Statusy HTTP: 200 OK, 404 Not Found, 500 Error
- Format JSON do wymiany danych
Metody HTTP (CRUD)
| Metoda | Operacja | Przykład | Opis |
|---|---|---|---|
| GET | Read | GET /users | Pobierz listę użytkowników |
| GET | Read | GET /users/123 | Pobierz użytkownika o ID 123 |
| POST | Create | POST /users | Utwórz nowego użytkownika |
| PUT | Update | PUT /users/123 | Zaktualizuj użytkownika 123 (cały zasób) |
| PATCH | Update | PATCH /users/123 | Zaktualizuj część danych użytkownika 123 |
| DELETE | Delete | DELETE /users/123 | Usuń użytkownika 123 |
Statusy HTTP – Najważniejsze
| Kod | Status | Znaczenie |
|---|---|---|
| 200 | OK | Sukces |
| 201 | Created | Zasób utworzony |
| 204 | No Content | Sukces, brak treści do zwrócenia |
| 400 | Bad Request | Błędne żądanie |
| 401 | Unauthorized | Brak autoryzacji |
| 403 | Forbidden | Brak uprawnień |
| 404 | Not Found | Nie znaleziono |
| 500 | Internal Server Error | Błąd serwera |
5. Tworzenie prostego REST API
O projekcie – REST API
W tym projekcie zbudujesz własne REST API w PHP dla serwisu gamingowego.
API będzie pośrednikiem między aplikacją a bazą danych. Użytkownik lub inna aplikacja wysyła żądanie, PHP je obsługuje, wykonuje odpowiednią operację na bazie danych i zwraca wynik w formacie JSON.
W praktyce będziesz mógł:
- pobierać użytkowników,
- dodawać nowych użytkowników,
- edytować ich dane,
- usuwać użytkowników,
- wyszukiwać i filtrować dane,
- korzystać z paginacji.
Do komunikacji wykorzystasz metody HTTP:
GET → pobierz dane
POST → dodaj dane
PUT → zmień dane
DELETE → usuń dane
API będziesz testować za pomocą Postmana i curl, a później nauczysz się również korzystać z zewnętrznych API, np. GitHub API.
Na dalszym etapie dodasz także autentykację i tokeny, dzięki czemu zobaczysz, jak API może sprawdzać, kto ma prawo korzystać z określonych danych.
Co będzie efektem?
Na końcu będziesz mieć działające API, które potrafi komunikować się z bazą danych i wymieniać dane z innymi aplikacjami.
Najważniejszy schemat, który poznasz w tym projekcie:
aplikacja
↓
żądanie HTTP
↓
REST API w PHP
↓
baza danych
↓
REST API w PHP
↓
odpowiedź JSON
↓
aplikacja
To będzie Twój pierwszy krok w stronę tworzenia backendu, z którego mogą korzystać różne aplikacje, a nie tylko zwykłej strony internetowej.
Struktura projektu
/api
/v1
index.php # Router
users.php # Endpoint użytkowników
config.php # Konfiguracja
Database.php # Klasa bazy danych
Response.php # Klasa odpowiedzi
Plik: Response.php – Klasa pomocnicza
<?php
// Response.php
class Response {
public static function json($data, $status_code = 200) {
http_response_code($status_code);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
public static function success($data = [], $message = 'Success', $status_code = 200) {
self::json([
'success' => true,
'message' => $message,
'data' => $data
], $status_code);
}
public static function error($message = 'Error', $status_code = 400, $errors = []) {
self::json([
'success' => false,
'message' => $message,
'errors' => $errors
], $status_code);
}
public static function notFound($message = 'Resource not found') {
self::error($message, 404);
}
public static function unauthorized($message = 'Unauthorized') {
self::error($message, 401);
}
public static function serverError($message = 'Internal server error') {
self::error($message, 500);
}
}
?>
Plik: Database.php – Klasa bazy danych
<?php
// Database.php
class Database {
private $connection;
public function __construct() {
mysqli_report(MYSQLI_REPORT_OFF);
$this->connection = @new mysqli('localhost', 'root', '', 'gaming_hub');
if ($this->connection->connect_error) {
throw new Exception('Database connection failed');
}
$this->connection->set_charset('utf8mb4');
}
public function query($sql, $params = [], $types = '') {
$stmt = $this->connection->prepare($sql);
if (!$stmt) {
throw new Exception('Query preparation failed: ' . $this->connection->error);
}
if (!empty($params)) {
$stmt->bind_param($types, ...$params);
}
if (!$stmt->execute()) {
throw new Exception('Query execution failed: ' . $stmt->error);
}
return $stmt;
}
public function fetchAll($sql, $params = [], $types = '') {
$stmt = $this->query($sql, $params, $types);
$result = $stmt->get_result();
$data = $result->fetch_all(MYSQLI_ASSOC);
$stmt->close();
return $data;
}
public function fetchOne($sql, $params = [], $types = '') {
$stmt = $this->query($sql, $params, $types);
$result = $stmt->get_result();
$data = $result->fetch_assoc();
$stmt->close();
return $data;
}
public function insert($sql, $params = [], $types = '') {
$stmt = $this->query($sql, $params, $types);
$insert_id = $stmt->insert_id;
$stmt->close();
return $insert_id;
}
public function update($sql, $params = [], $types = '') {
$stmt = $this->query($sql, $params, $types);
$affected_rows = $stmt->affected_rows;
$stmt->close();
return $affected_rows;
}
public function delete($sql, $params = [], $types = '') {
return $this->update($sql, $params, $types);
}
public function close() {
$this->connection->close();
}
}
?>
Plik: users.php – API użytkowników
<?php
// users.php
require_once 'Response.php';
require_once 'Database.php';
class UsersAPI {
private $db;
private $method;
private $user_id;
public function __construct() {
$this->db = new Database();
$this->method = $_SERVER['REQUEST_METHOD'];
// Pobierz ID z URL (np. /users/123)
$uri_parts = explode('/', trim($_SERVER['REQUEST_URI'], '/'));
$this->user_id = isset($uri_parts[3]) ? (int)$uri_parts[3] : null;
}
public function handleRequest() {
try {
switch ($this->method) {
case 'GET':
$this->handleGet();
break;
case 'POST':
$this->handlePost();
break;
case 'PUT':
$this->handlePut();
break;
case 'DELETE':
$this->handleDelete();
break;
default:
Response::error('Method not allowed', 405);
}
} catch (Exception $e) {
error_log("API Error: " . $e->getMessage());
Response::serverError($e->getMessage());
}
}
// GET - Pobierz użytkowników lub konkretnego użytkownika
private function handleGet() {
if ($this->user_id) {
// GET /users/123 - Pobierz konkretnego użytkownika
$user = $this->db->fetchOne(
"SELECT id, username, email, level, coins, created_at FROM users WHERE id = ?",
[$this->user_id],
'i'
);
if (!$user) {
Response::notFound("User with ID {$this->user_id} not found");
}
Response::success($user, 'User retrieved successfully');
} else {
// GET /users - Pobierz listę użytkowników
// Paginacja
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$limit = isset($_GET['limit']) ? min(100, max(1, (int)$_GET['limit'])) : 10;
$offset = ($page - 1) * $limit;
// Filtrowanie
$where = [];
$params = [];
$types = '';
if (isset($_GET['level_min'])) {
$where[] = "level >= ?";
$params[] = (int)$_GET['level_min'];
$types .= 'i';
}
if (isset($_GET['search'])) {
$where[] = "(username LIKE ? OR email LIKE ?)";
$search = '%' . $_GET['search'] . '%';
$params[] = $search;
$params[] = $search;
$types .= 'ss';
}
$where_sql = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
// Pobierz użytkowników
$sql = "SELECT id, username, email, level, coins, created_at FROM users $where_sql ORDER BY id DESC LIMIT ? OFFSET ?";
$params[] = $limit;
$params[] = $offset;
$types .= 'ii';
$users = $this->db->fetchAll($sql, $params, $types);
// Pobierz całkowitą liczbę
$count_sql = "SELECT COUNT(*) as total FROM users $where_sql";
$count_params = array_slice($params, 0, -2); // Usuń limit i offset
$count_types = substr($types, 0, -2);
$count_result = $this->db->fetchOne($count_sql, $count_params, $count_types);
$total = $count_result['total'];
Response::success([
'users' => $users,
'pagination' => [
'page' => $page,
'limit' => $limit,
'total' => (int)$total,
'pages' => ceil($total / $limit)
]
], 'Users retrieved successfully');
}
}
// POST - Utwórz nowego użytkownika
private function handlePost() {
// Pobierz dane JSON
$json = file_get_contents('php://input');
$data = json_decode($json, true);
if (!$data) {
Response::error('Invalid JSON', 400);
}
// Walidacja
$errors = [];
$username = trim($data['username'] ?? '');
if (empty($username)) {
$errors[] = 'Username is required';
} elseif (strlen($username) < 3) {
$errors[] = 'Username must be at least 3 characters';
}
$email = trim($data['email'] ?? '');
if (empty($email)) {
$errors[] = 'Email is required';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Invalid email format';
}
$password = $data['password'] ?? '';
if (empty($password)) {
$errors[] = 'Password is required';
} elseif (strlen($password) < 8) {
$errors[] = 'Password must be at least 8 characters';
}
if (!empty($errors)) {
Response::error('Validation failed', 400, $errors);
}
// Sprawdź czy username istnieje
$existing = $this->db->fetchOne(
"SELECT id FROM users WHERE username = ?",
[$username],
's'
);
if ($existing) {
Response::error("Username '$username' is already taken", 409);
}
// Utwórz użytkownika
$password_hash = password_hash($password, PASSWORD_DEFAULT);
$new_id = $this->db->insert(
"INSERT INTO users (username, email, password, created_at) VALUES (?, ?, ?, NOW())",
[$username, $email, $password_hash],
'sss'
);
// Pobierz utworzonego użytkownika
$user = $this->db->fetchOne(
"SELECT id, username, email, created_at FROM users WHERE id = ?",
[$new_id],
'i'
);
Response::success($user, 'User created successfully', 201);
}
// PUT - Zaktualizuj użytkownika
private function handlePut() {
if (!$this->user_id) {
Response::error('User ID is required', 400);
}
// Sprawdź czy użytkownik istnieje
$user = $this->db->fetchOne(
"SELECT id FROM users WHERE id = ?",
[$this->user_id],
'i'
);
if (!$user) {
Response::notFound("User with ID {$this->user_id} not found");
}
// Pobierz dane JSON
$json = file_get_contents('php://input');
$data = json_decode($json, true);
if (!$data) {
Response::error('Invalid JSON', 400);
}
// Walidacja
$email = trim($data['email'] ?? '');
if (!empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
Response::error('Invalid email format', 400);
}
$level = isset($data['level']) ? (int)$data['level'] : null;
$coins = isset($data['coins']) ? (int)$data['coins'] : null;
// Buduj dynamiczne zapytanie UPDATE
$updates = [];
$params = [];
$types = '';
if (!empty($email)) {
$updates[] = "email = ?";
$params[] = $email;
$types .= 's';
}
if ($level !== null) {
$updates[] = "level = ?";
$params[] = $level;
$types .= 'i';
}
if ($coins !== null) {
$updates[] = "coins = ?";
$params[] = $coins;
$types .= 'i';
}
if (empty($updates)) {
Response::error('No fields to update', 400);
}
$updates[] = "updated_at = NOW()";
$params[] = $this->user_id;
$types .= 'i';
$sql = "UPDATE users SET " . implode(', ', $updates) . " WHERE id = ?";
$affected = $this->db->update($sql, $params, $types);
// Pobierz zaktualizowanego użytkownika
$updated_user = $this->db->fetchOne(
"SELECT id, username, email, level, coins, updated_at FROM users WHERE id = ?",
[$this->user_id],
'i'
);
Response::success($updated_user, 'User updated successfully');
}
// DELETE - Usuń użytkownika
private function handleDelete() {
if (!$this->user_id) {
Response::error('User ID is required', 400);
}
// Sprawdź czy użytkownik istnieje
$user = $this->db->fetchOne(
"SELECT id, username FROM users WHERE id = ?",
[$this->user_id],
'i'
);
if (!$user) {
Response::notFound("User with ID {$this->user_id} not found");
}
// Usuń użytkownika
$affected = $this->db->delete(
"DELETE FROM users WHERE id = ?",
[$this->user_id],
'i'
);
if ($affected > 0) {
Response::success([
'deleted_id' => $this->user_id,
'username' => $user['username']
], 'User deleted successfully');
} else {
Response::serverError('Failed to delete user');
}
}
}
// Wykonaj API
$api = new UsersAPI();
$api->handleRequest();
?>
Plik: index.php – Router
<?php
// index.php
// Nagłówki CORS (pozwól na żądania z innych domen)
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
// Obsłuż preflight request (OPTIONS)
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
// Router
$uri = $_SERVER['REQUEST_URI'];
$uri_parts = explode('/', trim($uri, '/'));
// /api/v1/users
if (isset($uri_parts[2]) && $uri_parts[2] === 'users') {
require_once 'users.php';
exit;
}
// Nieznany endpoint
require_once 'Response.php';
Response::notFound('Endpoint not found');
?>
Plik: .htaccess – URL Rewriting
# .htaccess
RewriteEngine On
# Przekieruj wszystko na index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
6. Testowanie API
Przykłady żądań (curl)
# GET - Pobierz wszystkich użytkowników
curl -X GET "http://localhost/api/v1/users"
# GET - Pobierz użytkownika o ID 1
curl -X GET "http://localhost/api/v1/users/1"
# GET - Pobierz z paginacją
curl -X GET "http://localhost/api/v1/users?page=2&limit=5"
# GET - Pobierz z filtrowaniem
curl -X GET "http://localhost/api/v1/users?level_min=10&search=Pro"
# POST - Utwórz użytkownika
curl -X POST "http://localhost/api/v1/users" \
-H "Content-Type: application/json" \
-d '{"username":"NewUser","email":"new@example.com","password":"password123"}'
# PUT - Zaktualizuj użytkownika
curl -X PUT "http://localhost/api/v1/users/1" \
-H "Content-Type: application/json" \
-d '{"email":"updated@example.com","level":20}'
# DELETE - Usuń użytkownika
curl -X DELETE "http://localhost/api/v1/users/1"
Testowanie w Postman
- Pobierz Postman z https://www.postman.com/
- Utwórz nową kolekcję „Gaming API”
- Dodaj żądania:
- GET /users
- Method: GET
- URL:
http://localhost/api/v1/users - Send
- POST /users:
- Method: POST
- URL:
http://localhost/api/v1/users - Headers:
Content-Type: application/json - Body → raw → JSON:
- GET /users
{
"username": "TestUser",
"email": "test@example.com",
"password": "password123"
}
7. Konsumowanie zewnętrznych API
file_get_contents() – Proste żądania GET
<?php
// Proste żądanie GET
$url = 'https://jsonplaceholder.typicode.com/users/1';
$response = file_get_contents($url);
if ($response === false) {
die('Błąd połączenia');
}
$data = json_decode($response, true);
print_r($data);
// Z context (nagłówki, timeout)
$options = [
'http' => [
'method' => 'GET',
'header' => "User-Agent: MyApp/1.0\r\n" .
"Accept: application/json\r\n",
'timeout' => 10
]
];
$context = stream_context_create($options);
$response = file_get_contents($url, false, $context);
$data = json_decode($response, true);
?>
cURL – Zaawansowane żądania
<?php
// Funkcja pomocnicza do żądań cURL
function apiRequest($url, $method = 'GET', $data = null, $headers = []) {
$ch = curl_init();
// Ustawienia podstawowe
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
// Metoda HTTP
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
// Dane (dla POST, PUT)
if ($data !== null) {
$json_data = json_encode($data);
curl_setopt($ch, CURLOPT_POSTFIELDS, $json_data);
$headers[] = 'Content-Type: application/json';
$headers[] = 'Content-Length: ' . strlen($json_data);
}
// Nagłówki
if (!empty($headers)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
// Wyślij żądanie
$response = curl_exec($ch);
// Sprawdź błędy
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception("cURL Error: $error");
}
// Pobierz kod statusu
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'status_code' => $status_code,
'body' => json_decode($response, true)
];
}
// Przykłady użycia
// GET
try {
$result = apiRequest('https://jsonplaceholder.typicode.com/users/1', 'GET');
echo "Status: {$result['status_code']}<br>";
print_r($result['body']);
} catch (Exception $e) {
echo "Błąd: " . $e->getMessage();
}
// POST
$new_post = [
'title' => 'Test Post',
'body' => 'This is a test',
'userId' => 1
];
$result = apiRequest(
'https://jsonplaceholder.typicode.com/posts',
'POST',
$new_post
);
echo "Created Post ID: " . $result['body']['id'];
// PUT
$updated_post = [
'title' => 'Updated Title',
'body' => 'Updated content',
'userId' => 1
];
$result = apiRequest(
'https://jsonplaceholder.typicode.com/posts/1',
'PUT',
$updated_post
);
// DELETE
$result = apiRequest(
'https://jsonplaceholder.typicode.com/posts/1',
'DELETE'
);
?>
Przykład – Integracja z GitHub API
<?php
function getGitHubUser($username) {
$url = "https://api.github.com/users/$username";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'MyApp/1.0'); // GitHub wymaga User-Agent
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status_code === 404) {
return null; // Użytkownik nie znaleziony
}
if ($status_code !== 200) {
throw new Exception("GitHub API error: HTTP $status_code");
}
return json_decode($response, true);
}
try {
$user = getGitHubUser('octocat');
if ($user) {
echo "GitHub User: {$user['login']}<br>";
echo "Name: {$user['name']}<br>";
echo "Public Repos: {$user['public_repos']}<br>";
echo "Followers: {$user['followers']}<br>";
echo "<img src='{$user['avatar_url']}' width='100'><br>";
} else {
echo "User not found";
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
?>
8. Autentykacja API
API Key – Prosty klucz dostępu
<?php
// Sprawdź API key w nagłówku
$headers = getallheaders();
$api_key = $headers['X-API-Key'] ?? '';
$valid_keys = [
'abc123xyz' => 'user_1',
'def456uvw' => 'user_2'
];
if (!isset($valid_keys[$api_key])) {
Response::unauthorized('Invalid API key');
}
$user_id = $valid_keys[$api_key];
// Kontynuuj przetwarzanie...
?>
Bearer Token – Token w nagłówku Authorization
<?php
// Sprawdź Bearer token
$headers = getallheaders();
$auth_header = $headers['Authorization'] ?? '';
if (!preg_match('/Bearer\s(\S+)/', $auth_header, $matches)) {
Response::unauthorized('Missing or invalid Authorization header');
}
$token = $matches[1];
// Sprawdź token w bazie
$db = new Database();
$user = $db->fetchOne(
"SELECT id, username FROM users WHERE api_token = ? AND token_expires_at > NOW()",
[$token],
's'
);
if (!$user) {
Response::unauthorized('Invalid or expired token');
}
// Token OK - kontynuuj
$current_user_id = $user['id'];
?>
JWT (JSON Web Tokens) – Podstawy
<?php
// Prosty JWT (w produkcji użyj biblioteki firebase/php-jwt)
function generateJWT($user_id, $secret_key) {
$header = [
'typ' => 'JWT',
'alg' => 'HS256'
];
$payload = [
'user_id' => $user_id,
'iat' => time(), // Issued at
'exp' => time() + (60 * 60) // Expires (1 godzina)
];
$base64_header = base64_encode(json_encode($header));
$base64_payload = base64_encode(json_encode($payload));
$signature = hash_hmac('sha256', "$base64_header.$base64_payload", $secret_key, true);
$base64_signature = base64_encode($signature);
return "$base64_header.$base64_payload.$base64_signature";
}
function verifyJWT($jwt, $secret_key) {
$parts = explode('.', $jwt);
if (count($parts) !== 3) {
return false;
}
list($base64_header, $base64_payload, $base64_signature) = $parts;
// Sprawdź sygnaturę
$signature = hash_hmac('sha256', "$base64_header.$base64_payload", $secret_key, true);
$valid_signature = base64_encode($signature);
if ($base64_signature !== $valid_signature) {
return false;
}
// Dekoduj payload
$payload = json_decode(base64_decode($base64_payload), true);
// Sprawdź expiration
if ($payload['exp'] < time()) {
return false; // Token wygasł
}
return $payload;
}
// Użycie
$secret_key = 'your-secret-key-keep-it-safe';
// Generuj token przy logowaniu
$token = generateJWT(123, $secret_key);
echo "Token: $token<br>";
// Weryfikuj token przy żądaniu API
$payload = verifyJWT($token, $secret_key);
if ($payload) {
echo "User ID: " . $payload['user_id'];
} else {
echo "Invalid token";
}
?>
Przykład kompleksowy – API z autentykacją
<?php
// auth_api.php
require_once 'Response.php';
require_once 'Database.php';
class AuthAPI {
private $db;
private $secret_key = 'your-secret-key-change-this';
public function __construct() {
$this->db = new Database();
}
public function handleRequest() {
$method = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];
// Router
if (strpos($uri, '/login') !== false && $method === 'POST') {
$this->login();
} elseif (strpos($uri, '/register') !== false && $method === 'POST') {
$this->register();
} elseif (strpos($uri, '/me') !== false && $method === 'GET') {
$this->getCurrentUser();
} else {
Response::notFound('Endpoint not found');
}
}
private function login() {
$json = file_get_contents('php://input');
$data = json_decode($json, true);
$username = $data['username'] ?? '';
$password = $data['password'] ?? '';
if (empty($username) || empty($password)) {
Response::error('Username and password are required', 400);
}
// Sprawdź użytkownika
$user = $this->db->fetchOne(
"SELECT id, username, password FROM users WHERE username = ?",
[$username],
's'
);
if (!$user || !password_verify($password, $user['password'])) {
Response::unauthorized('Invalid username or password');
}
// Generuj token
$token = $this->generateToken($user['id']);
Response::success([
'token' => $token,
'user' => [
'id' => $user['id'],
'username' => $user['username']
]
], 'Login successful');
}
private function register() {
$json = file_get_contents('php://input');
$data = json_decode($json, true);
$username = trim($data['username'] ?? '');
$email = trim($data['email'] ?? '');
$password = $data['password'] ?? '';
// Walidacja...
$password_hash = password_hash($password, PASSWORD_DEFAULT);
$new_id = $this->db->insert(
"INSERT INTO users (username, email, password, created_at) VALUES (?, ?, ?, NOW())",
[$username, $email, $password_hash],
'sss'
);
$token = $this->generateToken($new_id);
Response::success([
'token' => $token,
'user' => [
'id' => $new_id,
'username' => $username
]
], 'Registration successful', 201);
}
private function getCurrentUser() {
$user_id = $this->verifyToken();
$user = $this->db->fetchOne(
"SELECT id, username, email, level, coins FROM users WHERE id = ?",
[$user_id],
'i'
);
if (!$user) {
Response::notFound('User not found');
}
Response::success($user);
}
private function generateToken($user_id) {
$payload = [
'user_id' => $user_id,
'iat' => time(),
'exp' => time() + (24 * 60 * 60) // 24 godziny
];
$header = base64_encode(json_encode(['typ' => 'JWT', 'alg' => 'HS256']));
$payload = base64_encode(json_encode($payload));
$signature = base64_encode(hash_hmac('sha256', "$header.$payload", $this->secret_key, true));
return "$header.$payload.$signature";
}
private function verifyToken() {
$headers = getallheaders();
$auth_header = $headers['Authorization'] ?? '';
if (!preg_match('/Bearer\s(\S+)/', $auth_header, $matches)) {
Response::unauthorized('Missing or invalid Authorization header');
}
$token = $matches[1];
$parts = explode('.', $token);
if (count($parts) !== 3) {
Response::unauthorized('Invalid token format');
}
list($header, $payload_encoded, $signature) = $parts;
// Weryfikuj sygnaturę
$valid_signature = base64_encode(hash_hmac('sha256', "$header.$payload_encoded", $this->secret_key, true));
if ($signature !== $valid_signature) {
Response::unauthorized('Invalid token signature');
}
// Dekoduj payload
$payload = json_decode(base64_decode($payload_encoded), true);
// Sprawdź expiration
if ($payload['exp'] < time()) {
Response::unauthorized('Token expired');
}
return $payload['user_id'];
}
}
$api = new AuthAPI();
$api->handleRequest();
?>