Introduzione alle API REST con PHP
Nel panorama dello sviluppo web moderno, la separazione tra il frontend (l'interfaccia utente) e il backend (la logica di business e i dati) è diventata lo standard industriale. Al centro di questa architettura troviamo le API REST (Representational State Transfer). Un'API REST non è un protocollo, ma uno stile architettonico che utilizza i metodi standard del protocollo HTTP per consentire la comunicazione tra sistemi diversi, indipendentemente dal linguaggio di programmazione utilizzato.
Perché utilizzare PHP per creare API? Sebbene esistano framework moderni come Node.js o Python Fast API, PHP rimane una scelta estremamente solida grazie alla sua ubiquità, alla facilità di deployment e alla potenza di versioni recenti (8.x) che hanno introdotto tipizzazione forte e performance significativamente migliorate. In questo tutorial, esploreremo come costruire un'API REST robusta partendo dalle basi fino a implementare pattern di progettazione professionali.
I Principi Fondamentali di REST
Prima di scrivere codice, è fondamentale capire cosa rende un'API "RESTful". Un'API segue i principi REST se rispetta i seguenti vincoli:
1. Client-Server
Il client (es. un'app React o un'app mobile) e il server (il nostro codice PHP) devono essere indipendenti. Il client non deve conoscere nulla di come i dati sono salvati nel database, e il server non deve conoscere nulla dell'interfaccia utente.
2. Statelessness (Assenza di Stato)
Ogni richiesta inviata dal client al server deve contenere tutte le informazioni necessarie per comprendere e processare la richiesta. Il server non deve memorizzare sessioni (come i classici $_SESSION di PHP) per mantenere lo stato tra una richiesta e l'altra. L'autenticazione avviene solitamente tramite token (come JWT).
3. Interfacciabilità Uniforme
Questo è il punto cruciale. L'interazione avviene tramite:
- Risorse: Identificate da URL univoci (es.
/api/users/1). - Metodi HTTP: Definiscono l'azione da compiere.
- Rappresentazioni: Il formato dei dati scambiati, solitamente JSON.
I Metodi HTTP e i Codici di Stato
Per implementare correttamente un'API, dobbiamo mappare le azioni CRUD (Create, Read, Update, Delete) sui metodi HTTP:
GET: Recupera una risorsa o una collezione di risorse.POST: Crea una nuova risorsa.PUT: Aggiorna completamente una risorsa esistente.PATCH: Aggiorna parzialmente una risorsa.DELETE: Rimuove una risorsa.
Allo stesso modo, il server deve rispondere con i codici di stato corretti: 200 OK per il successo, 201 Created dopo un POST, 400 Bad Request per errori di input, 401 Unauthorized per problemi di autenticazione e 404 Not Found quando la risorsa non esiste.
Struttura del Progetto e Configurazione
Per un'API professionale, non possiamo limitarci a un singolo file .php. Dobbiamo organizzare il codice seguendo il principio della singola responsabilità.
Esempio di Struttura Directory
project-api/
├── config/
│ └── database.php
├── src/
│ ├── Controllers/
│ │ └── UserController.php
│ ├── Models/
│ │ └── User.php
│ └── Core/
│ └── Router.php
├── .htaccess
└── index.php
Configurazione del Server (.htaccess)
Per avere URL puliti (es. /users/1 invece di index.php?page=users&id=1), dobbiamo reindirizzare tutte le richieste al file index.php utilizzando il modulo rewrite di Apache.
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
Questo file ci permette di implementare un sistema di routing manuale o di utilizzare un framework, centralizzando la gestione delle richieste.
Implementazione Pratica: Il Core dell'API
Iniziamo creando la connessione al database e la logica di base. Useremo PDO (PHP Data Objects) perché è sicuro contro le SQL Injection e supporta diversi database.
Gestione della Connessione (config/database.php)
<?php
class Database {
private $host = "localhost";
private $db_name = "my_api_db";
private $username = "root";
private $password = "";
public $conn;
public function getConnection() {
$this->conn = null;
try {
$this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
$this->conn->exec("set names utf8");
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $exception) {
echo "Errore di connessione: " . $exception->getMessage();
}
return $this->conn;
}
}
Il Modello User (src/Models/User.php)
Il modello si occupa esclusivamente dell'interazione con i dati. Non sa nulla di HTTP o JSON.
<?php
class User {
private $conn;
private $table_name = "users";
public $id;
public $name;
public $email;
public function __construct($db) {
$this->conn = $db;
}
public function readAll() {
$query = "SELECT id, name, email FROM " . $this->table_name;
$stmt = $this->conn->prepare($query);
$stmt->execute();
return $stmt;
}
public function readOne() {
$query = "SELECT id, name, email FROM " . $this->table_name . " WHERE id = ? LIMIT 0,1";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(1, $this->id);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
public function create() {
$query = "INSERT INTO " . $this->table_name . " SET name=:name, email=:email";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(":name", $this->name);
$stmt->bindParam(":email", $this->email);
if($stmt->execute()) {
return true;
} return false;
}
}
Il Controller e l'Entry Point (index.php)
Il controller riceve la richiesta, interroga il modello e restituisce la risposta in formato JSON. È fondamentale impostare l'header Content-Type: application/json affinché il client sappia come interpretare i dati.
<?php
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
require_once 'config/database.php';
require_once 'src/Models/User.php';
$database = new Database();
$db = $database->getConnection();
$user = new User($db);
$method = $_SERVER['REQUEST_METHOD'];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$uri = explode('/', $uri);
// Semplice router manuale per l'esempio
if ($uri[2] === 'users') {
switch ($method) {
case 'GET':
if (isset($uri[3]) && is_numeric($uri[3])) {
$user->id = $uri[3];
$result = $user->readOne();
if($result) {
echo json_encode($result);
} else {
http_response_code(404);
echo json_encode(["message" => "Utente non trovato"]);
}
} else {
$stmt = $user->readAll();
$users_arr = [];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
extract($row);
$user_item = ["id" => $id, "name" => $name, "email" => $email];
$users_arr[] = $user_item;
}
echo json_encode($users_arr);
}
break;
case 'POST':
$data = json_decode(file_get_contents("php://input"));
if(!empty($data->name) && !empty($data->email)) {
$user->name = $data->name;
$user->email = $data->email;
if($user->create()) {
http_response_code(201);
echo json_encode(["message" => "Utente creato con successo"]);
} else {
http_response_code(503);
echo json_encode(["message" => "Errore nel salvataggio"]);
}
} else {
http_response_code(400);
echo json_encode(["message" => "Dati incompleti"]);
}
break;
default:
http_response_code(405);
echo json_encode(["message" => "Metodo non permesso"]);
break;
}
} else {
http_response_code(404);
echo json_encode(["message" => "Risorsa non trovata"]);
}
Analisi Tecnica: Perché questo approccio?
Gestione dell'Input JSON
Noterete l'uso di file_get_contents("php://input"). A differenza dei form HTML classici, le API REST ricevono i dati nel corpo della richiesta (request body) in formato JSON. $_POST in PHP funziona solo con application/x-www-form-urlencoded o multipart/form-data. Per leggere il JSON grezzo, dobbiamo accedere allo stream di input di PHP.
CORS (Cross-Origin Resource Sharing)
Gli header Access-Control-Allow-Origin: * sono fondamentali. Senza di essi, un browser bloccherà ogni richiesta proveniente da un dominio diverso da quello del server (per motivi di sicurezza). In produzione, è consigliabile sostituire * con il dominio specifico del vostro frontend.
Sicurezza e SQL Injection
L'uso di prepare() e bindParam() di PDO è non negoziabile. Questo processo separa la query SQL dai dati inseriti dall'utente, rendendo impossibile l'iniezione di codice malevolo nel database.
Esempi Pratici e Casi d'Uso
Caso 1: Integrazione con una Single Page Application (SPA)
Immaginiamo di avere un frontend in Vue.js. Il frontend effettuerà una chiamata fetch all'endpoint /users. Il server PHP restituirà un array JSON che Vue.js mapperà in una tabella HTML. Questo permette di aggiornare i dati senza ricaricare l'intera pagina.
Caso 2: Sistema di Autenticazione tramite Token
In un'API reale, non useremmo le sessioni. Implementeremmo un endpoint /login che, dopo aver verificato le credenziali, restituisce un JSON Web Token (JWT). Il client salverà questo token nel localStorage e lo invierà in ogni richiesta successiva nell'header Authorization: Bearer <token>. Il server PHP verificherà la firma del token per ogni richiesta protetta.
Caso 3: Filtri e Paginazione
Per API con migliaia di record, non possiamo restituire tutto in una volta. Implementeremmo query string come /users?page=1&limit=20. In PHP, leggeremmo questi parametri tramite $_GET['page'] e li useremmo nella clausola LIMIT della query SQL.
Errori Comuni e Soluzioni
1. Dimenticare l'Header JSON
Errore: Il client riceve i dati ma li interpreta come testo semplice o HTML, causando errori di parsing nel frontend.
Soluzione: Inserire sempre header("Content-Type: application/json") all'inizio del file di ingresso.
2. Confondere PUT e PATCH
Errore: Usare PUT per aggiornare solo un campo (es. solo l'email).
Soluzione: Per convenzione, PUT sostituisce l'intera risorsa. Se volete aggiornare solo un dettaglio, usate PATCH e gestite in PHP l'aggiornamento parziale dei campi.
3. Errori PHP che rompono il JSON
Errore: Un Warning o un Notice di PHP viene stampato a video, sporcando l'output JSON e rendendolo non valido.
Soluzione: In produzione, disabilitate la visualizzazione degli errori (display_errors = Off nel php.ini) e utilizzate un sistema di logging per registrare gli errori in un file.
Prossimi Passi e Approfondimenti
Ora che avete una base solida per creare API REST con PHP "puro", il passo successivo è scalare la complessità.
- Utilizzo di Framework: Se il progetto cresce, implementare un router manuale diventa insostenibile. Studiate Laravel o Symfony. Laravel, in particolare, offre un sistema di routing potentissimo e strumenti come API Resources per trasformare i modelli in JSON in modo elegante.
- Documentazione con Swagger/OpenAPI: Un'API senza documentazione è inutile. Imparate a usare Swagger per creare interfacce interattive dove gli sviluppatori frontend possono testare gli endpoint senza scrivere codice.
- Implementazione JWT: Approfondite la libreria
firebase/php-jwtper aggiungere un livello di sicurezza professionale alle vostre risorse. - Caching: Studiate l'integrazione di Redis per memorizzare le risposte delle API più richieste, riducendo drasticamente il carico sul database SQL.
Costruire API REST efficaci richiede disciplina e attenzione agli standard. Seguendo questi principi, creerete sistemi interoperabili, sicuri e pronti per l'integrazione con qualsiasi tecnologia moderna.