PHP e API RESTful: eliminare risorse con DELETE e soft delete

by theArchitect
SHARE
PHP e API RESTful: eliminare risorse con DELETE e soft delete
© Guida-HTML5.it

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_by e deleted_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.

Approfondisci con risorse ufficiali

SHARE