Introduzione
In un’API RESTful, il metodo HTTP DELETE viene utilizzato per rimuovere una risorsa. Tuttavia, cancellare definitivamente un record dal database non è sempre la scelta migliore: potremmo aver bisogno di conservarlo per motivi di audit, recupero dati o conformità normativa.
Una soluzione molto usata è il soft delete. Invece di eliminare fisicamente la riga, l’applicazione valorizza una colonna come deleted_at con la data e l’ora della cancellazione. Le query normali escluderanno automaticamente i record marcati come eliminati.
In questo tutorial realizzeremo un endpoint PHP che gestisce richieste come:
DELETE /api/articles/15 L’endpoint controllerà il metodo HTTP, verificherà l’esistenza dell’articolo e applicherà una cancellazione logica tramite PDO e MySQL.
Codice completo
Per prima cosa, immaginiamo una tabella articles definita in questo modo:
CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(150) NOT NULL,
content TEXT NOT NULL,
deleted_at DATETIME NULL DEFAULT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
); Il file delete-article.php può contenere il seguente codice:
<?php
declare(strict_types=1);
// Impostiamo il tipo di risposta dell´API.
header(´Content-Type: application/json; charset=utf-8´);
// In un´API reale, la connessione dovrebbe essere caricata da una configurazione sicura.
$dsn = ´mysql:host=localhost;dbname=blog;charset=utf8mb4´;
$username = ´api_user´;
$password = ´password_sicura´;
try {
$pdo = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
} catch (PDOException $exception) {
http_response_code(500);
echo json_encode([
´error´ => ´database_unavailable´,
´message´ => ´Impossibile connettersi al database.´
], JSON_UNESCAPED_UNICODE);
exit;
}
// Controlliamo che il client utilizzi il metodo DELETE.
if ($_SERVER[´REQUEST_METHOD´] !== ´DELETE´) {
header(´Allow: DELETE´);
http_response_code(405);
echo json_encode([
´error´ => ´method_not_allowed´,
´message´ => ´È consentito soltanto il metodo DELETE.´
], JSON_UNESCAPED_UNICODE);
exit;
}
// Recuperiamo l´identificativo dalla query string.
// Esempio: delete-article.php?id=15
$id = filter_input(INPUT_GET, ´id´, FILTER_VALIDATE_INT);
if ($id === false || $id === null || $id <= 0) {
http_response_code(400);
echo json_encode([
´error´ => ´invalid_id´,
´message´ => ´L identificativo deve essere un intero positivo.´
], JSON_UNESCAPED_UNICODE);
exit;
}
try {
// Cerchiamo solo articoli non ancora eliminati.
$select = $pdo->prepare(
´SELECT id FROM articles WHERE id = :id AND deleted_at IS NULL´
);
$select->execute([´id´ => $id]);
if ($select->fetch() === false) {
http_response_code(404);
echo json_encode([
´error´ => ´article_not_found´,
´message´ => ´Articolo non trovato o già eliminato.´
], JSON_UNESCAPED_UNICODE);
exit;
}
// Cancellazione logica: il record resta nel database.
$delete = $pdo->prepare(
´UPDATE articles
SET deleted_at = CURRENT_TIMESTAMP
WHERE id = :id AND deleted_at IS NULL´
);
$delete->execute([´id´ => $id]);
// 204 indica successo senza contenuto nel corpo della risposta.
http_response_code(204);
} catch (PDOException $exception) {
http_response_code(500);
echo json_encode([
´error´ => ´delete_failed´,
´message´ => ´Si è verificato un errore durante la cancellazione.´
], JSON_UNESCAPED_UNICODE);
} Una richiesta di prova con cURL è la seguente:
curl -X DELETE "https://example.test/api/delete-article.php?id=15" Spiegazione
Controllo del metodo HTTP
Il codice verifica $_SERVER[´REQUEST_METHOD´]. Se il client invia una richiesta GET, POST o PATCH, l’endpoint risponde con 405 Method Not Allowed. L’header Allow: DELETE comunica esplicitamente quale metodo è supportato.
Validazione dell’identificativo
filter_input() consente di accettare soltanto un intero. Il controllo aggiuntivo su $id <= 0 impedisce valori come zero o numeri negativi. Non bisogna inserire direttamente l’identificativo nella query SQL: l’uso dei parametri nominati evita SQL injection.
Soft delete e idempotenza
La query aggiorna deleted_at invece di eseguire DELETE FROM articles. L’operazione è inoltre idempotente: ripetere la stessa richiesta non dovrebbe produrre effetti ulteriori. Dopo la prima cancellazione, la risorsa non viene più considerata attiva.
Il codice restituisce 404 se l’articolo non esiste oppure è già stato eliminato. In alternativa, un’API potrebbe rispondere sempre con 204 quando lo stato finale desiderato è “risorsa non presente”. La scelta deve essere documentata e mantenuta coerente.
Codice di stato 204
La risposta 204 No Content indica che l’operazione è riuscita, ma non contiene un documento JSON. Per questo motivo non bisogna stampare un corpo dopo aver impostato questo status code.
Best practice
- Usare sempre query preparate tramite PDO e disabilitare le prepared statement emulate.
- Applicare il controllo di autorizzazione prima della cancellazione: non ogni utente dovrebbe poter eliminare qualsiasi risorsa.
- Registrare chi ha eseguito l’operazione, ad esempio con colonne
deleted_byedeleted_at. - Escludere i record eliminati nelle query normali usando
WHERE deleted_at IS NULL. - Creare un endpoint amministrativo separato per il ripristino, ad esempio
PATCH /api/articles/15/restore. - Non mostrare dettagli dell’eccezione PDO al client, perché potrebbero rivelare nomi di tabelle o informazioni sull’infrastruttura.
- Proteggere l’endpoint con autenticazione, autorizzazione, HTTPS e logging.
- Scrivere test per i casi di successo, ID non valido, risorsa inesistente, metodo errato e database non disponibile.
Riepilogo
Un endpoint DELETE ben progettato deve verificare il metodo HTTP, validare l’identificativo, utilizzare query parametrizzate e restituire codici di stato coerenti. Il soft delete rappresenta una soluzione sicura quando la rimozione definitiva non è appropriata, perché conserva lo storico e permette eventuali procedure di recupero.
La combinazione di deleted_at, risposta 204, controllo degli articoli già eliminati e gestione centralizzata degli errori costituisce una base solida per API PHP manutenibili e prevedibili.
