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
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 |
| 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 |
| 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
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();
?>