Parliamo di REST API e PHP 8. REST ha acquisito una posizione di rilievo come lo standard de facto per l'esposizione dei dati attraverso API e lo sviluppo di servizi web. La sua diffusione è tale che la maggior parte delle applicazioni web moderne si basa su API REST per l'accesso e la condivisione dei dati. Questo approccio è supportato dalla disponibilità di numerosi framework front-end che semplificano l'interazione con le API REST, offrendo una serie di vantaggi per gli sviluppatori e le applicazioni stesse.
Nel contesto della creazione di servizi web, MySQL è uno dei database più utilizzati e integrare la sua gestione dei dati con API REST può portare benefici in termini di flessibilità e scalabilità. Oggi ci concentreremo su come creare una semplice applicazione demo per il recupero di un elenco di utenti dal database MySQL attraverso un endpoint REST.
Per iniziare, è necessario avere una conoscenza di base di SQL per interagire con il database e comprendere i concetti fondamentali di API REST per la gestione delle richieste HTTP e delle risposte JSON. Seguiremo una procedura step-by-step per mostrare come collegare un'applicazione web con un database MySQL utilizzando API REST. Consentendo così il recupero dei dati degli utenti in modo strutturato e scalabile.
Il processo includerà la creazione di una tabella per gli utenti nel database, la configurazione di un server web per gestire le richieste HTTP, la definizione di endpoint REST per accedere ai dati e infine la creazione di un'interfaccia web per visualizzare gli utenti recuperati tramite le API REST. Seguendo questo percorso potrai acquisire una comprensione pratica su come integrare tecnologie come MySQL e API REST per creare servizi web orientati ai dati.
Realizzare una REST API in PHP8: lo Skeleton
Il progetto assumerà tale struttura:
Esaminatela per avere una visione dei componenti principali e del loro ruolo nell'applicazione:
- index.php: funge da punto di ingresso per l'applicazione. Si comporta come un front controller, indirizzando le richieste agli handler e gestendo il flusso generale dell'applicazione.
- inc/config.php: qui troviamo le informazioni di configurazione per il funzionamento dell'applicazione. Questo file contiene principalmente le credenziali di accesso al database per stabilire una connessione.
- inc/bootstrap.php: è responsabile per l'avvio dell'applicazione, inclusi i file e le risorse per il suo funzionamento. Può contenere inizializzazioni di librerie, impostazioni globali e altre configurazioni di avvio.
- Model/Database.php: rappresenta il livello di accesso al database (Data Access Layer) dell'applicazione. Qui vengono definite le funzioni e le query per interagire con il database.
- Model/UserModel.php: rappresenta il modello di dati per gli utenti nell'applicazione. Implementa i metodi per accedere e manipolare i dati della tabella degli utenti nel database. Ciò include operazioni come la creazione di utenti, il recupero delle informazioni degli utenti e la modifica dei lo dettagli.
- Controller/Api/BaseController.php: è un controller di base per le chiamate API. Contiene metodi che possono essere ereditati da altri controller API. Questo aiuta a mantenere un codice ben organizzato, evitando le duplicazioni.
- Controller/Api/UserController.php: è il controller per la gestione delle chiamate API relative agli utenti. Contiene il codice dell'applicazione per gestire le richieste REST relative agli utenti, come il recupero dell'elenco degli utenti, l'aggiornamento dei dati e così via.
Questo schema organizzativo fornisce una base solida per lo sviluppo dell'applicazione, garantendo una separazione delle responsabilità tra i componenti e seguendo le best practice di progettazione software.
Creare il database e i Models
Per realizzare il database relativo all'API aprite un terminale e loggatevi con MySQL:
CREATE DATABASE rest_api_php;
volendo potreste usare anche strumenti quali PhpMyAdmin o Mysql Workbench ma è bene abituarsi a scrivere query a mano. Una volta creato il database realizzate la tabella degli Utenti, quindi sempre da terminale digitate:
$use rest_api_php;
$CREATE TABLE `users` (
`user_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`user_email` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`user_status` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Nella prima riga viene indicato di utilizzare il database creato in precedenza e nelle altre viene creata la tabella users
. Ora popolate la tabella con la seguente query:
INSERT INTO `users`(`user_id`, `username`, `user_email`, `user_status`) VALUES (1,'Gina','ginaverdi@gmail.com',0);
Fatelo tante volte in base alla quantità di righe che volete aggiungere, cambiando chiaramente il nome, la e-mail ed icrementandone l'id:
I Models
Aprite il file Model/Database.php
ed aggiungete le seguenti righe:
<?php
class Database
{
protected $connection = null;
public function __construct()
{
try {
$this->connection = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_DATABASE_NAME);
if (mysqli_connect_errno()) {
throw new Exception("Could not connect to database.");
}
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
}
public function select($query = "", $params = [])
{
try {
$stmt = $this->executeStatement($query, $params);
$result = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
$stmt->close();
return $result;
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
return false;
}
private function executeStatement($query = "", $params = [])
{
try {
$stmt = $this->connection->prepare($query);
if ($stmt === false) {
throw new Exception("Unable to do prepared statement: " . $query);
}
if ($params) {
$stmt->bind_param($params[0], $params[1]);
}
$stmt->execute();
return $stmt;
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
}
}
Questa classe rappresenta il livello di accesso al database, fornendo la capacità di stabilire una connessione con il database MySQL. Oltre alla configurazione di base della connessione, offre metodi generici come select
e executeStatement
che facilitano l'estrazione di dati. È importante notare che non utilizzeremo la classe Database
. Al contrario, creeremo classi modello specifiche che la estendono per interagire con il database in modo più mirato.
Questo approccio di estensione della classe per creare classi modello ci consente di implementare le operazioni di accesso ai dati, mantenendo una separazione tra logica di accesso al database e logica di business dell'applicazione. Potete così garantire una migliore modularità e manutenibilità del codice nel lungo termine, nonché più flessibilità nel caso in cui sia necessario modificare o espandere le funzionalità di accesso ai dati.
Il model Model/UserModel.php
Ora create il model Model/UserModel.php
come di seguito:
<?php
require_once PROJECT_ROOT_PATH . "/Model/Database.php";
class UserModel extends Database
{
public function getUsers($limit)
{
return $this->select("SELECT * FROM users ORDER BY user_id ASC LIMIT ?", ["i", $limit]);
}
}
La classe UserModel
estende la classe Database
per sfruttarne le funzionalità di accesso al database.
All'interno di UserModel
, vi è il metodo getUsers
, progettato per recuperare gli utenti dal database. Esso richiede il parametro $limit
che limita il numero di record selezionati alla volta. Si evita così il recupero di un numero eccessivo di record in una sola operazione, garantendo prestazioni ottimali.
Nella classe UserModel
è possibile definire ulteriori metodi in base alle necessità dell'applicazione. Tuttavia in questo tutorial ci concentreremo su un approccio semplice e chiaro per illustrare i concetti fondamentali.
La directory inc
All'interno della directory inc
create il file di configurazione config.php
con il seguente contenuto:
<?php
define("DB_HOST", "localhost");
define("DB_USERNAME", "root");
define("DB_PASSWORD", "");
define("DB_DATABASE_NAME", "rest_api_php");
Le costanti sono da definire con le proprie credenziali. Successivamente, sempre in inc create il file bootstrap.php
con il seguente contenuto:
<?php
define("PROJECT_ROOT_PATH", __DIR__ . "/../");
// include main configuration file
require_once PROJECT_ROOT_PATH . "/inc/config.php";
// include the base controller file
require_once PROJECT_ROOT_PATH . "/Controller/Api/BaseController.php";
// include the use model file
require_once PROJECT_ROOT_PATH . "/Model/UserModel.php";
?>
La costante PROJECT_ROOT_PATH
viene inizializzata con il percorso radice dell'applicazione. Questa scelta consente di creare percorsi assoluti in modo uniforme, facilitando la gestione di file e risorse.
Il file config.php
che contiene le informazioni per stabilire la connessione al database. Questo passaggio assicura che le credenziali e le impostazioni di accesso al database siano correttamente caricate e utilizzate nel contesto dell'applicazione.
I file relativi ai controller e ai modelli per il funzionamento dell'applicazione sono stati inclusi per ultimi. Essi svolgono ruoli chiave nel gestire la logica di business, l'interazione con il database e la gestione delle richieste utente.
I Controllers
All'interno di Controller/Api create la classe BaseController.php
con il seguente contenuto:
<?php
class BaseController
{
/**
* __call magic method.
*/
public function __call($name, $arguments)
{
$this->sendOutput('', array('HTTP/1.1 404 Not Found'));
}
/**
* Get URI elements.
*
* @return array
*/
protected function getUriSegments()
{
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$uri = explode('/', $uri);
return $uri;
}
/**
* Get querystring params.
*
* @return array
*/
protected function getQueryStringParams()
{
parse_str($_SERVER['QUERY_STRING'], $query);
return $query;
}
/**
* Send API output.
*
* @param mixed $data
* @param string $httpHeader
*/
protected function sendOutput($data, $httpHeaders = array())
{
header_remove('Set-Cookie');
if (is_array($httpHeaders) && count($httpHeaders)) {
foreach ($httpHeaders as $httpHeader) {
header($httpHeader);
}
}
echo $data;
exit;
}
}
I metodi della classe BaseController
Esaminate in dettaglio i metodi presenti nella classe BaseController
.
__call
: è un metodo magico che entra in gioco quando viene chiamato un metodo inesistente nella classe. In questo caso gestisce dinamicamente le chiamate a metodi non implementati, generando un errore HTTP 404 Not Found. Questo approccio è cruciale per mantenere la gestione delle richieste e delle risposte, garantendo una migliore esperienza utente. Se inizialmente può sembrare complesso, diventerà più chiaro quando metteremo alla prova la nostra applicazione nella fase successiva.getUriSegments
: restituisce un array contenente i segmenti dell'URI richiesto. È particolarmente utile per validare l'endpoint REST chiamato dall'utente, consentendo di eseguire controlli e operazioni in base alla struttura dell'URL richiesto.getQueryStringParams
: restituisce un array contenente le variabili della stringa di query passate insieme alla richiesta HTTP in arrivo. Le stringhe di query sono spesso utilizzate per passare parametri o filtri aggiuntivi alle richieste API. Questo metodo ci permette di accedere a tali dati in modo strutturato e di manipolarli secondo necessità.sendOutput
: metodo fondamentale per inviare la risposta generata dalle API agli utenti. Quando vogliamo comunicare i risultati delle operazioni richieste tramite le API lo utilizzeremo per formattare e inviare i dati in risposta alle richieste HTTP.
Continuando con l'analisi potete apprezzare come questi metodi si integrano nella logica complessiva dell'applicazione e ne potenziano l'esperienza utente.
Il file UserController.php
Create poi UserController.php
, sempre all'interno della solita directory:
<?php
class UserController extends BaseController
{
/**
* "/user/list" Endpoint - Get list of users
*/
public function listAction()
{
$strErrorDesc = '';
$requestMethod = $_SERVER["REQUEST_METHOD"];
$arrQueryStringParams = $this->getQueryStringParams();
if (strtoupper($requestMethod) == 'GET') {
try {
$userModel = new UserModel();
$intLimit = 10;
if (isset($arrQueryStringParams['limit']) && $arrQueryStringParams['limit']) {
$intLimit = $arrQueryStringParams['limit'];
}
$arrUsers = $userModel->getUsers($intLimit);
$responseData = json_encode($arrUsers);
} catch (Error $e) {
$strErrorDesc = $e->getMessage().'Something went wrong! Please contact support.';
$strErrorHeader = 'HTTP/1.1 500 Internal Server Error';
}
} else {
$strErrorDesc = 'Method not supported';
$strErrorHeader = 'HTTP/1.1 422 Unprocessable Entity';
}
// send output
if (!$strErrorDesc) {
$this->sendOutput(
$responseData,
array('Content-Type: application/json', 'HTTP/1.1 200 OK')
);
} else {
$this->sendOutput(
json_encode(array('error' => $strErrorDesc)),
array('Content-Type: application/json', $strErrorHeader)
);
}
}
}
Analizzare il codice di UserController.php
Da notare che la classe UserController
estende BaseController
, seguendo una pratica comune nell'organizzazione gerarchica dei controller. Questa struttura consente di separare logicamente le azioni e le funzionalità associate agli endpoint REST specifici per l'entità utente.
All'interno della classe UserController
, uno dei metodi fondamentali è listAction
. Esso gestisce l'endpoint /user/list
corrispondente al recupero dell'elenco degli utenti dal database MySQL. Questo metodo rappresenta la logica completa per gestire la richiesta e generare la risposta corrispondente.
Inizialmente, nel metodo listAction
, vengono inizializzate alcune variabili come $requestMethod
e $arrQueryStringParams
. Successivamente, si verifica se la richiesta è stata effettuata utilizzando il metodo GET per l'endpoint /user/list
. In caso contrario, l'esecuzione viene interrotta. Successivamente viene istanziato un oggetto UserModel
e chiamato il metodo getUsers
per recuperare l'elenco degli utenti dal database. Infine, vengono formattati i dati ottenuti utilizzando json_encode
per convertirli in un formato JSON prima di inviarli come risposta all'utente.
È importante evidenziare che l'utilizzo di sendOutput
è cruciale per inviare la risposta JSON all'utente. Inoltre, l'intestazione del tipo di contenuto della risposta viene impostata su application/json
poiché stiamo restituendo una risposta in JSON.
Seguendo questo approccio, è possibile definire altri metodi all'interno della classe UserController
per gestire diversi endpoint e operazioni legate all'entità utente o ad altre entità. Mantenendo così una struttura chiara e organizzata nel codice. Questo approccio modulare e orientato agli endpoint REST contribuisce a una migliore gestione e scalabilità del sistema.
Il file index.php
index.php
è il file più importante in quanto rappresenta il punto di ingresso dell'applicazione:
<?php
require __DIR__ . "/inc/bootstrap.php";
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$uri = explode('/', $uri);
if ((isset($uri[2]) && $uri[2] != 'user') || !isset($uri[3])) {
header("HTTP/1.1 404 Not Found");
exit();
}
require PROJECT_ROOT_PATH . "/Controller/Api/UserController.php";
$objFeedController = new UserController();
$strMethodName = $uri[3] . 'Action';
$objFeedController->{$strMethodName}();
?>
Nella gestione delle richieste HTTP in entrata si è utilizzato parse_url()
e explode()
per estrarre e inizializzare i segmenti dell'URI nella variabile $uri
come un array. Questo permette di ottenere una rappresentazione strutturata dell'URI richiesto essenziale per valutare l'endpoint richiesto dall'utente.
Successivamente, procedete con la convalida dei segmenti dell'URI per determinare l'endpoint richiesto e l'azione da eseguire. Questa fase consente di indirizzare correttamente la richiesta utente al controller e al metodo di azione appropriati.
Una volta identificato l'endpoint richiesto e l'azione da intraprendere, istanziate il controller corrispondente, nel nostro caso UserController
, e chiamate il metodo di azione corrispondente. Questo passaggio permette di eseguire la logica associata all'endpoint richiesto, come ad esempio il recupero dell'elenco degli utenti dal database nel caso dell'endpoint /user/list
.
Con l'implementazione e l'integrazione di tutti i file necessari per gestire le richieste REST, avete creato una base solida per l'applicazione demo. La prossima fase prevede di testare e chiamare l'applicazione dal punto di vista dell'utente finale, per verificare il funzionamento delle API esposte e la risposta adeguata alle richieste inviate.
Chiamare la API REST
Semplicemente da Postman o nella barra degli indirizzi del vostro Browser digitate quanto segue:
// https://localhost/index.php/{MODULE_NAME}/{METHOD_NAME}?limit={LIMIT_VALUE}
http://localhost/index.php/user/list?limit=20
index.php
gioca un ruolo fondamentale nella gestione delle richieste. Con esso stiamo analizzando la variabile $uri
per determinare l'endpoint richiesto e l'azione da intraprendere.
In questo contesto, state controllando se il valore della variabile $uri[2]
è impostato su user
. Questo indica che state gestendo una richiesta relativa all'entità utente
. Esaminate poi il valore della variabile $uri[3]
che rappresenta il nome dell'azione da eseguire. Nel caso specifico il valore di $uri[3]
è impostato su list
. Di conseguenza ciò comporterebbe l'esecuzione di listAction
nella classe UserController
.
Quando listAction
viene chiamato, esegue la logica necessaria per recuperare e restituire un elenco di utenti. L'output generato da questa azione dovrebbe essere strutturato in conformità con il formato JSON, poiché l'intestazione del tipo di contenuto è impostata sulla risposta come application/json
.
È importante sottolineare che la gestione delle richieste e delle azioni è cruciale per garantire un comportamento coerente e prevedibile dell'applicazione. Questo approccio modulare e basato sugli endpoint vi consente di estendere e aggiungere nuove funzionalità in modo ordinato e scalabile. L'output dovrebbe assomigliare a questo:
Ricevete quindi un elenco di utenti, quelli inseriti in precedenza nella tabella, sotto forma di oggetto JSON. Anche l'errore viene gestito allo stesso modo.
In conclusione
Hai esplorato una prospettiva nello sviluppo di applicazioni web utilizzando PHP e MySQL, concentrandoti sulla creazione di un'applicazione RESTful. Per dimostrare i concetti chiave, ho sviluppato una demo che espone un'API REST per recuperare un elenco di utenti da un database. Questo scenario riflette un caso d'uso comune in cui le applicazioni necessitano di accedere e manipolare dati degli utenti.
Durante lo sviluppo, ho adottato una metodologia guidata dalle best practice, organizzando il codice in moduli logici distinti, come controller e modello. Ho inoltre utilizzato concetti come l'ereditarietà delle classi per creare una struttura gerarchica coerente e manutenibile nel codice.
Attraverso l'implementazione di endpoint REST chiaramente definiti, come ad esempio l'endpoint /user/list, hai consentito agli sviluppatori e agli utenti di interagire con l'applicazione in modo intuitivo e conforme agli standard REST.
L'applicazione demo non solo ti ha permesso di mettere in pratica i concetti teorici relativi alle API REST, ma ti ha anche fornito un'esperienza pratica nel gestire richieste, elaborare dati e inviare risposte nel formato JSON, ampiamente utilizzato nelle comunicazioni API moderne.
Questo progetto non è solo un esercizio, ma anche un'opportunità per comprendere l'importanza di adottare approcci architetturali come REST per garantire robustezza, scalabilità ed evolvibilità nel tempo delle applicazioni.