Creazione di API PHP RESTful per il Frontend
Parte 2: Implementazione pratica di endpoint PHP per il tuo frontend Vanilla
Introduzione: dal Design all'Implementazione
Nella Parte 1 abbiamo imparato i principi di REST, come strutturare gli endpoint e quali metodi HTTP usare. Ora passiamo alla pratica: come implementare questi endpoint in PHP, in modo che il tuo frontend Vanilla JS possa comunicare correttamente.
Questa guida si concentra su:
- Struttura di un progetto PHP per API
- Gestione delle richieste HTTP in PHP
- Response JSON e codici di stato HTTP appropriati
- Validazione e gestione errori
- Integrazione con il frontend tramite fetch/AJAX
Struttura base di un progetto API PHP
Un progetto ben organizzato rende il codice mantenibile e scalabile. Ecco una struttura consigliata:
api/
├── config/
│ └── database.php
├── controllers/
│ └── UsersController.php
├── models/
│ └── User.php
├── routes.php
├── index.php
└── .htaccess
File .htaccess per URL friendly
Per rendere le URL pulite (es. /api/users/123 invece di /api/index.php?route=users&id=123), usa un file .htaccess:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /api/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
Il file principale: index.php
Qui gestisci il routing delle richieste verso i controller appropriati:
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
// Gestisci preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path = str_replace('/api/', '', $path);
// Router base
$parts = explode('/', trim($path, '/'));
$resource = $parts[0] ?? '';
$id = $parts[1] ?? null;
// Includi il controller appropriato
if ($resource === 'users') {
require 'controllers/UsersController.php';
$controller = new UsersController();
switch ($method) {
case 'GET':
if ($id) {
$controller->getById($id);
} else {
$controller->getAll();
}
break;
case 'POST':
$controller->create();
break;
case 'PUT':
$controller->update($id);
break;
case 'DELETE':
$controller->delete($id);
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Metodo non consentito']);
}
} else {
http_response_code(404);
echo json_encode(['error' => 'Risorsa non trovata']);
}
?>
Access-Control-Allow-* permettono al tuo frontend di altre origini di accedere l'API. Modifica Access-Control-Allow-Origin in produzione con il tuo dominio specifico.
Implementazione di un Controller: UsersController
Il controller gestisce la logica per una risorsa specifica. Esempio semplificato:
<?php
class UsersController {
private $users = []; // In produzione, leggi dal DB
public function getAll() {
// Leggi querystring per filtri e paginazione
$page = $_GET['page'] ?? 1;
$limit = $_GET['limit'] ?? 10;
$city = $_GET['citta'] ?? null;
$filtered = $this->users;
if ($city) {
$filtered = array_filter($filtered,
fn($u) => $u['indirizzo']['citta'] === $city
);
}
$total = count($filtered);
$offset = ($page - 1) * $limit;
$paginated = array_slice($filtered, $offset, $limit);
http_response_code(200);
echo json_encode([
'page' => (int)$page,
'limit' => (int)$limit,
'total' => $total,
'data' => $paginated
]);
}
public function getById($id) {
$user = null;
foreach ($this->users as $u) {
if ($u['id'] == $id) {
$user = $u;
break;
}
}
if (!$user) {
http_response_code(404);
echo json_encode(['error' => 'Utente non trovato']);
return;
}
http_response_code(200);
echo json_encode($user);
}
public function create() {
$data = json_decode(file_get_contents('php://input'), true);
// Validazione
if (!isset($data['nome']) || !isset($data['email'])) {
http_response_code(400);
echo json_encode(['error' => 'Nome ed email sono obbligatori']);
return;
}
// Crea nuovo utente
$newUser = [
'id' => count($this->users) + 1,
'nome' => $data['nome'],
'email' => $data['email']
];
$this->users[] = $newUser;
http_response_code(201);
echo json_encode($newUser);
}
public function update($id) {
$data = json_decode(file_get_contents('php://input'), true);
$found = false;
foreach ($this->users as &$u) {
if ($u['id'] == $id) {
$u = array_merge($u, $data);
$found = true;
break;
}
}
if (!$found) {
http_response_code(404);
echo json_encode(['error' => 'Utente non trovato']);
return;
}
http_response_code(200);
echo json_encode($u);
}
public function delete($id) {
$found = false;
foreach ($this->users as $key => $u) {
if ($u['id'] == $id) {
unset($this->users[$key]);
$found = true;
break;
}
}
if (!$found) {
http_response_code(404);
echo json_encode(['error' => 'Utente non trovato']);
return;
}
http_response_code(204);
}
}
?>
Codici di Stato HTTP Appropriati
Restituisci il codice di stato corretto per indicare il risultato dell'operazione:
| Codice | Significato | Uso tipico |
|---|---|---|
200 OK |
Richiesta riuscita con risposta | GET, PUT, PATCH con successo |
201 Created |
Risorsa creata | POST con successo |
204 No Content |
Richiesta riuscita, nessun contenuto | DELETE con successo |
400 Bad Request |
Dati inviati non validi | Validazione fallita |
401 Unauthorized |
Autenticazione richiesta | Mancanza token JWT/Bearer |
403 Forbidden |
Accesso negato | Autorizzazione insufficiente |
404 Not Found |
Risorsa non trovata | ID inesistente |
500 Server Error |
Errore del server | Eccezione non gestita |
Funzioni di Validazione Riutilizzabili
Crea una classe o funzioni helper per validare i dati in ingresso:
<?php
class Validator {
public static function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
public static function validateRequired($data, $fields) {
foreach ($fields as $field) {
if (!isset($data[$field]) || trim($data[$field]) === '') {
return "Il campo '{$field}' è obbligatorio";
}
}
return null;
}
public static function validateLength($string, $min, $max) {
$len = strlen($string);
if ($len < $min || $len > $max) {
return "Lunghezza deve essere tra {$min} e {$max} caratteri";
}
return null;
}
}
// Utilizzo nel controller
$error = Validator::validateRequired($data, ['nome', 'email']);
if ($error) {
http_response_code(400);
echo json_encode(['error' => $error]);
return;
}
if (!Validator::validateEmail($data['email'])) {
http_response_code(400);
echo json_encode(['error' => 'Email non valida']);
return;
}
?>
Integrazione con Frontend Vanilla JavaScript
Ecco come utilizzare l'API da Vanilla JS con fetch:
GET - Recuperare dati
fetch('https://tuosito.it/api/users')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error('Errore:', err));
POST - Creare una risorsa
const newUser = {
nome: 'Marco',
email: '[email protected]'
};
fetch('https://tuosito.it/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newUser)
})
.then(res => res.json())
.then(data => console.log('Creato:', data))
.catch(err => console.error('Errore:', err));
PUT - Aggiornare completamente
const updated = {
nome: 'Marco Rossi',
email: '[email protected]',
eta: 35
};
fetch('https://tuosito.it/api/users/123', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updated)
})
.then(res => res.json())
.then(data => console.log('Aggiornato:', data));
DELETE - Eliminare
fetch('https://tuosito.it/api/users/123', {
method: 'DELETE'
})
.then(res => {
if (res.status === 204) {
console.log('Eliminato con successo');
}
});
Gestione Errori e Risposte Coerenti
Mantieni un formato di risposta coerente per errori e successi:
Formato di risposta con errore
{
"success": false,
"error": "Email non valida",
"code": 400,
"timestamp": "2024-01-15T10:30:00Z"
}
Formato di risposta di successo
{
"success": true,
"data": { ... },
"timestamp": "2024-01-15T10:30:00Z"
}
Helper PHP per risposte uniformi
<?php
class Response {
public static function success($data, $statusCode = 200) {
http_response_code($statusCode);
echo json_encode([
'success' => true,
'data' => $data,
'timestamp' => date('c')
]);
}
public static function error($message, $statusCode = 400) {
http_response_code($statusCode);
echo json_encode([
'success' => false,
'error' => $message,
'code' => $statusCode,
'timestamp' => date('c')
]);
}
}
// Utilizzo
Response::error('Utente non trovato', 404);
?>
Best Practices per API PHP RESTful
- Usa middleware per CORS: Gestisci CORS centralizzato, non in ogni endpoint.
- Implementa autenticazione: Usa JWT o Bearer tokens per proteggere endpoint sensibili.
- Rate limiting: Limita le richieste per IP per prevenire abusi.
- Logging: Registra tutte le richieste e errori per debugging.
- Versionamento URL: Usa
/api/v1/per facilità di manutenzione. - Documentazione: Mantieni un file README.md o Swagger per descrivere gli endpoint.
- Test:**> Scrivi test per ogni endpoint (PHPUnit, Pest).
- Sicurezza: Valida sempre l'input, usa prepared statements per il database, sanitizza l'output.
Conclusione
Seguendo questa guida hai gli strumenti per creare API PHP RESTful solide e professionali. La combinazione di:
- Principi REST (Parte 1)
- Implementazione PHP (questa guida)
- Frontend Vanilla JS
...ti permette di costruire applicazioni web scalabili e mantenibili senza dipendere da framework pesanti.
Ricorda: la semplicità e la chiarezza del codice sono la chiave della qualità.
Guida gratuita offerta da Vivacity Design.