Creazione di API PHP RESTful per il Frontend

Parte 2: Implementazione pratica di endpoint PHP per il tuo frontend Vanilla

Prerequisito: Questa è la seconda parte della guida REST. Se non l'hai ancora letta, ti consigliamo di iniziare da Guida completa alle API REST per comprendere i principi, la struttura degli endpoint e i metodi HTTP.

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 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']);
}
?>
CORS: I header 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

Importante: Usa sempre HTTPS in produzione. Il HTTP è insicuro per dati sensibili.

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.