Vai al contenuto

MongoDB e PHP: un’accoppiata insolita?

A ogni linguaggio il suo database. Chi lavora con il linguaggio PHP sa benissimo che il database di riferimento è MySql. PHP infatti possiede delle librerie native che permettono di interfacciarsi in maniera semplice ed immediata ad un database MySql. Esistono in verità anche librerie che permettono di connettersi ad altri database concorrenti (Postgres, Sql Server, etc), ma la quasi totalità dei siti presenti sul web, anche per “colpa” dei CMS quali WordPress e Joomla, utilizza un database MySql.

In un articolo passato, ho analizzato le differenze tra i database DBMS e NOSQL, indicando tra le possibili scelte l’ormai celeberrimo MongoDB. Sebbene l’accoppiata MongoDB/PHP sia poco diffusa, esistono librerie apposite che permettono di gestire completamente il database. A differenza di MySql, le cui librerie sono già presenti all’interno di PHP, con MongoDb la cosa si fa leggermente più complicata, poichè bisogna innanzitutto reperire le librerie di connessione e configurarle correttamente.

I driver di MongoDB sono disponibili sul sito del progetto al link: https://docs.mongodb.com/drivers/

Sarà quindi necessario scaricare l’estensione adatta al nostro sistema (php_mongo.dll per Windows, disponibile qui http://pecl.php.net/package/mongodb), copiarla nella cartella delle estensioni di PHP (ext) e modificare il file php.ini aggiungendo la direttiva extension=php_mongodb.dll. Nel mio caso ho scaricato la versione 1.10 (7.4 Thread Safe (TS) x64) per PHP 7.4 64bit. Tutte le istruzioni sono riportate in dettaglio nella documentazione ufficiale di MongoDB. Se lavoriamo in locale la procedura è abbastanza banale, poiché abbiamo accesso a tutte le risorse e ai file di configurazione. Su un hosting condiviso, sarà invece necessario verificare se il driver è installato e se pienamente supportato.

Utilizzeremo per le nostre prove, un servizio in Cloud anziché installare MongoDB in locale. Il servizio offerto gratuitamente (pur con una serie di limitazioni) è disponibile qui: https://www.mongodb.com/atlas/database. Basta registrarsi, creare un Cluster scegliendo il profilo gratuito e iniziare.

La struttura di MongoDB

Diamo un’occhiata velocemente alla tabella riepilogativa sotto per capire quali sono le differenze tra un database relazionale e MongoDB. Innanzitutto non esistono tabelle, ma collection (che le sostituiscono), mentre la riga della tabella in MongoDB è un documento. Va da se che le colonne vengono sostituite dai campi e che per interrogare il database non si usa SQL ma direttamente JSON o Javascript. L’unica caratteristica in comune è l’utilizzo di un indice univoco, che assicura l’univocità dei dati.

RDBMS MongoDB
database database
tabella collection
record (tupla o riga della tabella) documento
colonna della tabella campo del documento
indice indice
SQL JSON, Javascipt

Ciascun campo del documento è definito come una coppia di chiave->valore dove la chiave è una stringa che identifica il nome del campo, mentre il valore può assumere un valore di qualsiasi tipo (testo, numero, bool, oggetto, un intero documento…)

Atlas MongoDB

Se abbiamo già creato un cluster gratuito su Atlas, dovrebbe apparire qualcosa del genere come in figura sotto. Attraverso i tre pulsanti sarà possibile generare la stringa di connessione al database oppure “sfogliare” le collezioni, nonchè monitorare in realtime le attività e i processi che vengono eseguiti.

Cliccando su Connect, apparirà una finestra dove bisognerà scegliere “Connect your application”. A quel punto bisognerà scegliere il tipo di linguaggio preferito e la versione del driver di connessione. Nel nostro caso sceglieremo ovviamente PHP e PHPLIB 1.9 + mongodb-1.10 or later

require 'vendor/autoload.php';

$client = new MongoDB\Client('mongodb+srv://northwind01:<password>@cluster0.2hfkn.mongodb.net');

Ovviamente Dopo aver salvato, possiamo già testare il corretto funzionamento dello script dal browser attraverso l’URL http://localhost/mongotest/test.php. Non stupitevi se vi apparirà una finestra bianca. Se non vi appare nessun errore significa in realtà che la connessione al database è avvenuta correttamente. Adesso possiamo proseguire con il nostro esperimento.

Lista dei database presenti nel cluster

Nello stesso file test.php, dopo le prime due righe di connessione al database, possiamo scrivere quanto segue per chiedere a MongoDB la lista dei database del cluster:

try
{
    $dbs = $mongo->listDatabases();
    print_r($dbs);
}
catch (MongoDB\Driver\Exception\ConnectionTimeoutException $e)
{
    // script per gestire l'eccesione
}

Ovviamente la funzione print_r() potrebbe essere sostituita da un foreach per scansionare l’array ritornato dalla funzione listDatabases() e mostrare in maniera leggibile i nomi dei database contenuti nel cluster.

Interrogare il database

Uno degli aspetti più importanti, se non il più importante, dell’interazione con un database è il recupero dei dati.

Iniziamo con la ricerca più semplice, quella senza filtri, finalizzata alla lettura di tutti i documenti presenti nella collection. Se abbiamo inserito di dataset di esempio, possiamo iniziare a fare le prime prove con la collection listingsAndReviews del database sample_airbnb. Tale collezione contiene un totale di ben 5.555 documenti. 

<?php 
require 'vendor/autoload.php'; 

$client = new MongoDB\Client('mongodb+srv://northwind01:<password>@cluster0.2hfkn.mongodb.net');
$collection = $client->sample_airbnb->listingsAndReviews;
$result = $collection->find();

foreach ($result as $entry) {
    echo $entry['listing_url'], ': ', $entry['name'], "<br>";
}

Molto probabilmente l’esecuzione di questo script ha richiesto qualche secondo poichè la variabile $result (che si tratta di un cursore) contiene tutti i documenti della collection e mostrare 5.555 righe sul browser richiede sempre un po’ di tempo. Possiamo ottimizzare l’interrogazione, aggiungendo dei filtri specifici, da passare come array alla funzione find(). Supponiamo di voler estrarre tutti i documenti (appartamenti) il cui campo property_type = “House”. Il codice che abbiamo eseguito sopra, andrà modificato leggermente, come sotto:

<?php
require 'vendor/autoload.php';

$client = new MongoDB\Client('mongodb+srv://northwind01:<password>@cluster0.2hfkn.mongodb.net');
$collection = $client->sample_airbnb->listingsAndReviews;
$result = $collection->find(["property_type" => "House"]);

foreach ($result as $entry) {
    echo $entry['listing_url'], ': ', $entry['name'], "<br>";
}

Quello che abbiamo fatto è passare un array con una condizione da verificare durante l’interrogazione. Se vogliamo, possiamo inserire una seconda o più condizioni, in modo tale da raffinare ancor più la ricerca e ottimizzare i tempi di visualizzazione. In tal caso basterà inserire un secondo item all’array (campo room_type = “Private room”), come nell’esempio sotto:

$result = $collection->find(["property_type" => "House", "room_type" => "Private room"]);

Possiamo scrivere anche la seguente istruzione in maniera più leggibile. Il risultato sarebbe il medesimo sia in termini di dati che di prestazioni.

$query = array(
    "property_type" => "House", 
    "room_type" => "Private room"
);

$result = $collection->find($query);

La condizione nella forma KEY=>VALUE è estremamente flessibile, perchè ci permette di definire in VALUE oltre le stringhe, anche una struttura più articolata di condizioni. Ad esempio, se volessi cercare, sempre nella collection listingsAndReviews, tutti gli appartamenti che hanno più di 3 letti, allora dovrei fare cosi:

$query = array(
    'beds' => array('$gt' => 3)
);

$result = $collection->find($query);

Appare chiaro quindi in VALUE possiamo inserire un subarray che definisce regole più complesse e articolate utilizzando degli operatori di confronto specifici di MongoDB. Piccola nota: occorre specificare tali operatori tra gli apici ‘ e non le “, poichè utilizzando il simbolo del dollaro come prefisso, potrebbero essere scambiate per delle variabili di PHP. Alcuni di questi operatori sono i seguenti:

Name
Description
Matches values that are equal to a specified value.
Matches values that are greater than a specified value.
Matches values that are greater than or equal to a specified value.
Matches any of the values specified in an array.
Matches values that are less than a specified value.
Matches values that are less than or equal to a specified value.
Matches all values that are not equal to a specified value.
Matches none of the values specified in an array.

Si rimanda alla documentazione ufficiale di MongoDB per ulteriori approfondimenti in merito a tutti gli operatori che possono essere utilizzati.

Inserire un documento

L’inserimento di un nuovo documento (record) all’interno di una collezione è molto semplice. Il concetto si basa sull’utilizzo di array associativi che verranno poi, attraverso la libreria mongodb di PHP, trasformate in notazione BSON. In questo caso specifico, utilizzeremo la funzione insertOne(), che effettua un singolo inserimento nella collezione. Ecco il codice per intero:

<?php
require 'vendor/autoload.php';

$client = new MongoDB\Client('mongodb+srv://northwind01:<password>@cluster0.2hfkn.mongodb.net');

$db = $client->test_db;
$collection = $db->test_coll;
$document = array (
     'nome' => "Gianluca",
     'cognome' => "Tramontana",
     'PHP' => 10,
     'Java' => 10,
     'JS' => 9
);
$result = $collection->insertOne($document);

echo "Object ID '{$result->getInsertedId()}'";

L’ultima riga ritorna l’ID univoco che viene assegnato in maniera causale e arbitraria da MongoDB. Ricordiamo che è anche possibile assegnare un ID specifico, specificando nell’array un item nella forma “_id” => 1, tuttavia per garantire l’univocità, bisognerebbe prima recuperare l’ultimo ID inserito, incrementarlo di una unità, etc…

Il risultato su Atlas è visibile nella figura in basso. Se il database test_db non è presente nel cluster, viene innanzitutto creato. Medesima cosa avviene per la collezione (se non esiste, viene creata).

Modificare un documento

La modifica di un documento esistente prevede la chiamata alla funzione updateOne(). Precisiamo che la funzione richiede il passaggio di due parametri, il primo è la condizione da verificare per effettuare l’update e il secondo rappresenta un subarray con i campi del documento da modificare. Come già visto per insertOne() proveremo a modificare un documento partendo dal suo ID.

Supponiamo di voler modificare il documento che abbiamo aggiunto con l’insert sopra. L’ID univoco del documento è rappresentato da 61e2f6a8e6570000bf007935. Vediamo come fare:

<?php
require 'vendor/autoload.php';

$client = new MongoDB\Client('mongodb+srv://northwind01:<password>@cluster0.2hfkn.mongodb.net');

$collection = $client->test_db->test_coll;

$condition = array("_id" => new MongoDB\BSON\ObjectID("61e2f6a8e6570000bf007935"));
$set = array(
    "nome" => "Mario",
    "cognome" => "Rossi"
);

$updateResult = $collection->updateOne($condition, ['$set' => $set]);

printf("Documenti trovati: %d\n", $updateResult->getMatchedCount());
printf("<br>Documenti modificati: %d\n", $updateResult->getModifiedCount());

Dopo aver recuperato la collection di riferimento, a differenza dell’insertOne, la modifica richiede due variabili di tipo array: la prima ($condition) contiene la condizione (o le condizioni) e la seconda ($set) contiene i campi da modificare con i nuovi valori. Si tratta poi di chiamare updateOne, specificando appunto i due array cosi come nel listato.

Una attenzione particolare richiede l’array $condition: abbiamo deciso di modificare un documento specifico (tramite il suo ID), quindi siamo costretti ad usare un oggetto di tipo MongoDB\BSON\ObjectID poichè il campo _id è considerato da MongoDB un vero e proprio oggetto. Se avessimo usato la semplice condizione  array(“_id” => 61e2f6a8e6570000bf007935) sarebbe di certo fallita.

Le ultime due righe ritornano rispettivamente il numero di documenti trovati (getMatchedCount), cioè che corrispondono alla condizione specificata e il numero di documenti modificati (getModifiedCount): in questo caso solo uno, poichè abbiamo usato l’ID univoco del documento.

E’ interessante notare che nel caso più documenti corrispondessero alla condizione specificata, verrebbe modificato esclusivamente il primo trovato, poiché la funzione updateOne permette la modifica di un solo documento. Inoltre, c’è da dire che un eventuale seconda ripetizione dello script di updateOne, non produrrebbe alcun effetto poichè MongoDB verifica i campi da modificare abbiano effettivamente un contenuto diverso da quello attuale.

Eliminare un documento

Va da sè che la cancellazione di un documento è possibile farlo con deleteOne() che prende come parametro di ingresso un array che rappresenta la condizione (o le condizioni) da verificare per cancellare il documento. L’ID univoco del documento è rappresentato, in questo caso, da 61e2f6a8e6570000bf007935.

<?php
require 'vendor/autoload.php';

$client = new MongoDB\Client('mongodb+srv://northwind01:<password>@cluster0.2hfkn.mongodb.net');

$collection = $client->test_db->test_coll;

$condition = array("_id" => new MongoDB\BSON\ObjectID("61e2f6a8e6570000bf007935"));

$deleteResult = $collection->deleteOne($condition);

printf("<br>Documenti eliminati: %d\n", $deleteResult->getDeletedCount());

Anche in questo caso, nel caso non si utilizzi nella condizione un valore univoco, verrà cancellato il primo documento trovato.

Agire su più documenti

Le funzioni di inserimento, modifica e cancellazione che abbiamo visto agiscono solo ed esclusivamente sul primo documento che viene trovato (nel caso di modifica e cancellazione). Ma se volessimo effettuare delle modifiche o cancellazioni massive? MongoDB mette a disposizione delle funzioni proprio per fare questo. In tal caso avremo insertMany(), updateMany() e deleteMany(), il cui funzionamento è esattamente speculare a quello già visto sopra, con la sola differenza che il risultato interesserà non uno ma due o più documenti.

Riepilogo

In questo articolo, abbiamo esaminato le funzioni di base per effettuare le operazioni CRUD con PHP e MongoDB. Metterò di seguito i vari link alla documentazione ufficiale di MongoDB per maggiori approfondimenti.

La creazione o l’inserimento di documenti avviene attraverso l’uso di:

La lettura o la ricerca di documenti si ottiene utilizzando:

L’aggiornamento dei documenti avviene mediante l’utilizzo di:

L’eliminazione o la rimozione di documenti viene eseguita utilizzando: