Précision : ce billet ne parle pas de l’installation de mongo. Il suppose que vous utilisez l’installation sur les machines de l’université (voir TP correspondant), ou une installation que vous aurez faite par vos propres moyens.
Un système de gestion de base de données NoSQL
Qu’est-ce qu’un système de gestion de base de données (SGBD) ?
Une base de données sert à stocker des informations, les consulter et les mettre à jour. Dans un programme sur un serveur web, ces informations seront stockées sur le serveur.
On parle de SGBD (système de gestion de base de données). Un fichier texte, dans lequel des informations sont enregistrées ligne après lignes, constitue un SGBD, même rudimentaire. L’intérêt d’un SGBD élaboré (et donc qui nécessite l’utilisation de programmes/modules spéficiques) est de garantir des propriétés comme par exemple la performance des opérations, la cohérence, la consistance des données enregistrées, etc.
NoSQL
Le type de SGBD le plus courant utilise un langage de requêtes de la famille SQL (Structured Query Language). Mongo n’en fait pas partie.
Mongo s’inscrit dans un mouvement qui gagne en ampleur depuis plusieurs années, dit NoSQL (pour Not only SQL), d’utilisation de SGBD moins contraignants que ceux de la famille SQL.
Les avantages souvent mis en avant :
- plus souples
- plus faciles d’utilisation
- conçus pour être utilisés sur des architectures massivement parallèles, sur des très grandes quantités de données
Inconvénient majeur :
- moins de garanties de cohérence et de consistance des données. Ces contraintes sont laissées à la charge du programmeur.
Pour un site web, les opérations de base données suivent à peu près toujours le même schéma. Il n’est souvent pas utile de s’embarrasser de SQL, dont la syntaxe et les concepts sous-jacents sont très différents de ce qu’on manipule par ailleurs (dans la programmation en langage serveur et en javascript).
Principes de base de Mongo
Une installation de mongo peut contenir plusieurs bases de données (sur les machines de l’université, vous n’aurez qu’une seule base, qui porte le même nom que votre login).
Une entrée dans une base mongo est appelée un document. On stocke des documents dans des collections. Une base contient donc des collections de documents.
Les données comme clefs / valeurs
Un document peut être vu comme un ensemble de couples clef / valeur.
Exemples de documents très simples :
{ 'prenom': 'Christophe', 'nom': 'Prieur' }
{ 'prenom': 'Toto' }
{
'code': 'IO2',
'intitulé': 'Internet et outils',
}
On utilise ici la syntaxe JSON, qui est utilisée par l’interpréteur mongo (voir ci-dessous)
Rien n’oblige les documents d’une collection à posséder les mêmes champs, ni à ce que les champs de même nom aient des valeurs de même type (la cohérence de la base est laissée à la charge du programmeur). Une collection n’est qu’un ensemble de documents, sans contrainte pour ceux-ci.
Les valeurs peuvent être complexes (listes, dictionnaires), mais nous nous limiterons pour le moment à un exemple :
{
'code': 'IO2',
'intitulé': 'Internet et outils',
'semestre': 2,
'outils': ['html', 'css', 'php', 'mongo', 'javascript']
}
Identifiants
Chaque document est pourvu d’un identifiant supposé être unique (et si on utilise le système d’affection d’identifiant par défaut, il sera bien unique). C’est la clef _id
(noter l’underscore). Elle n’est pas affichée dans les exemples ci-dessus.
Les opérations comme clefs / valeurs
Toutes les opérations sont effectuées au moyen de commandes prenant en paramètre des tableaux clef / valeur.
- Les documents sont naturellement fournis comme des tableaux clef / valeur
- Les requêtes sont fournies sous la forme de tableaux clef / valeur
- Les options des commandes sont spécifiées sous la forme de tableaux clef / valeur
La syntaxe variera donc selon le langage utilisé :
- dans l’interpréteur mongo (qui utilise javascript), on utilisera JSON (Javascript Object Notation)
- en php, on utilisera la syntaxe php de description de tableaux associatifs (clef / valeur)
Mais les opérations seront les mêmes : l’interpréteur mongo et l’objet MongoClient
de php sont deux implémentations différentes d’une même interface de programmation. Détails ci-dessous, d’abord pour l’interpréteur (donc en javascript), ensuite pour php.
Cinq commandes de base (insert, find, findOne, update, remove)
Référence : opérations sur les collections.
Avant tout, l’interpréteur
Dans cette section, les commandes sont décrites avec la syntaxe de l’interpréteur mongo (donc javascript).
La version php sera fournie dans la section suivante.
Pour lancer l’interpréteur sur les machines de l’université, utilisez la commande ci-dessous :
mongo --host pams -p -u login base
pams
est le nom du serveur-p
indique que la connexion à la base requiert un mot de passe (qui vous sera demandé interactivement)-u
permet de fournir votre loginbase
est le nom de votre base, c’est en fait égalementlogin
Sur une installation en local, sur votre ordinateur par exemple, où aucune restriction n’est imposée (c’est le cas dans l’installation par défaut), la commande mongo
sans arguments suffit.
mongo
Contrairement à l’interpréteur du shell (bash), l’interpréteur mongo n’exécute pas des commandes à proprement parler mais évalue des expressions et affiche leur valeur (et lorsque ces expressions comportent des appels de méthodes, cela revient à exécuter des commandes, même si on n’appelle pas ça comme ça). Ce qui est très utile pour obtenir le résultat de certaines opérations (et quand on a un doute sur la syntaxe).
> 1+2
3
> 'bon'+'jour'
bonjour
> var n=10
> n+3
13
L’interpréteur mongo fournit une auto-complétion des noms de méthodes au moyen de la touche Tab (une fois s’il n’y a pas ambiguïté, deux fois pour afficher la liste des suites possibles).
Il est possible d’exécuter des commandes écrites dans un fichier au moyen de la fonction load
:
load('test.js')
insert
La référence : insert
Pour insérer un document dans une collection (que la collection existe ou non) :
db.collection.insert( document )
db.collection.insert( documents )
document
est un tableau clefs / valeursdocuments
est une liste de tableaux clefs / valeurscollection
est le nom de la collection à laquelle on souhaite ajouter le(s) document(s). Si la collection n’existait pas au préalable, elle est créée (c’est de cette manière qu’on crée les collections)
db.etudiants.insert({
'prenom': 'Camille',
'nom': 'Simon'
})
db.etudiants.insert( { 'prenom': 'Thomas' } )
db.etudiants.insert( [ { 'prenom': 'Jordan' }, { 'prenom': 'Mélanie'} ] )
db.etudiants.insert(
[
{
'prenom': 'Camille',
'nom': 'Alberti'
},
{
'prenom': 'Laura',
'nom': 'Seban'
}
]
)
db.cours.insert([
{ 'code': 'IO2', 'intitulé': 'Internet et outils' },
{ 'code': 'TO2', 'intitulé': 'Types de données et objets' }
])
Deux remarques de syntaxe :
- les espaces et sauts de ligne n’importent pas
- attention à fermer tout ce qui est ouvert, dans le bon ordre (parenthèses, crochets, parenthèses)
find
La référence : find
Pour consulter des documents dans une collection :
db.collection.find()
db.collection.find(requete)
db.collection.find(requete, projection)
- sans paramètre, tous les documents sont renvoyés
requete
est un tableau clefs / valeurs spécifiant des opérateurs sur les champs des documents recherchésprojection
est un tableau permettant de limiter les champs que l’on souhaite consulter dans les documents recherchés (cette option sera traitée ultérieurement)
db.etudiants.find()
db.etudiants.find( { ‘prenom’: ‘Camille’ } )
Exemple de résultat obtenu :
> db.etudiants.find( { 'prenom': 'Camille' } )
{ "_id" : ObjectId("532d40c72d150b4635b8cfc9"), "prenom" : "Camille", "nom" : "Simon" }
{ "_id" : ObjectId("532d40c72d150b4635b8cfcd"), "prenom" : "Camille", "nom" : "Alberti" }
Le champ _id
a été ajouté par le système au moment de l’opération insert
. On peut ensuite l’utiliser pour désigner un document précis :
> db.etudiants.find({ _id: ObjectId("532d40c72d150b4635b8cfc9") })
{ "_id" : ObjectId("532d40c72d150b4635b8cfc9"), "prenom" : "Camille", "nom" : "Simon" }
Le résultat renvoyé par find
est un curseur, un objet qui permet d’itérer sur les documents. Voir plus bas l’utilisation de ce curseur avec php. La version Javascript de cette itération n’est pas présentée ici, considérons juste un exemple d’utilisation simple, comme un tableau :
> var c = db.etudiants.find({'prenom': 'Camille'})
> c[0]
{
"_id" : ObjectId("532d40c72d150b4635b8cfc9"),
"prenom" : "Camille",
"nom" : "Simon"
}
> c[0].nom
Simon
findOne
La référence : findOne
.
La fonction findOne
fait la même chose que find
mais sans s’embarrasser d’un curseur, lorsqu’on souhaite récupérer un document unique (par son identifiant, par exemple).
> var camille = db.etudiants.findOne({'_id': ObjectId('532d40c72d150b4635b8cfc9')})
> camille
{
"_id" : ObjectId("532d40c72d150b4635b8cfc9"),
"prenom" : "Camille",
"nom" : "Simon"
}
> camille.nom
Simon
Si la requête désigne plusieurs documents, le premier trouvé est renvoyé.
update
La référence : update
.
Pour modifier un ou des documents existant :
db.collection.update(requete, modifications)
requete
est de la même forme que pourfind
modification
est un tableau clefs / valeurs définissant des opérations sur les champs. On n’utilisera pour le moment que l’opérateur$set
pour ajouter un champ, comme dans l’exemple ci-dessous.
db.users.update({'prenom': 'Thomas'}, { '$set': {nom: 'Hummel'} })
remove
La référence : remove
.
Pour supprimer un ou plusieurs documents :
db.collection.remove()
db.collection.remove(requete)
- sans paramètre, tous les documents sont supprimés (attention, donc)
requete
est de la même forme que pourfind
, elle désigne les documents qui seront supprimés.
> db.etudiants.find( { 'prenom': 'Camille' } )
{ "_id" : ObjectId("532d40c72d150b4635b8cfc9"), "prenom" : "Camille", "nom" : "Simon" }
{ "_id" : ObjectId("532d40c72d150b4635b8cfcd"), "prenom" : "Camille", "nom" : "Alberti" }
> db.etudiants.remove({ _id: ObjectId("532d40c72d150b4635b8cfc9") })
> db.etudiants.find({prenom: 'Camille'})
{ "_id" : ObjectId("532d40c72d150b4635b8cfcd"), "prenom" : "Camille", "nom" : "Alberti" }
Fonctionnement avec php
L’objet MongoClient
La référence : MongoClient
Connexion à la base
On utilise la syntaxe suivante :
new MongoClient("mongodb://username:password@server/database");
On peut par exemple définir les paramètres de la connexion dans un fichier séparé, ce qui permet d’écrire des scripts indépendants de ces paramètres. C’est aussi particulièrement important pour le stockage du mot de passe.
require_once('parametres-base.php');
$url = 'mongodb://'.DBUSER.':'.DBPASSWD.'@'.DBSERVER.'/'.DBNAME;
$m = new MongoClient($url);
$db = $m->{DBNAME};
Note sur la syntaxe : dans l’expression $m->{DBNAME}
, les accolades permettent de remplacer DBNAME
par sa valeur, et ainsi récupérer l’attribut qui porte ce nom dans l’objet stocké dans $m
.
Un exemple de fichier parametres-base.php
:
<?php
define('DBSERVER', 'pams');
define('DBUSER', 'cprieur');
define('DBNAME', 'cprieur');
require_once('18aa28061571fa8cced19dc62e7a2fbc/p.php')
?>
On a utilisé la fonction php define
, qui permet en php de définir des constantes.
Dans cet exemple, le mot de passe est stocké dans un fichier p.php
, rangé dans un répertoire dont le nom ne peut être trouvé par hasard, par une personne mal intentionnée.
Le fichier p.php
pourra contenir ce qui suit :
<?php
define('DBPASSWD', 'mon mot de passe');
?>
Attention, votre mot de passe doit être stocké dans un fichier et un répertoire qui sont accessibles au serveur web mais pas aux autres étudiants. Les étudiants sont dans votre groupe, le serveur web non, ce qui donne les droits suivants :
# pour le répertoire
chmod 701 rep
# pour le fichier
chmod 604 p.php
# ou bien
chmod g-rwx,o+x rep
chmod g-rwx,o+r p.php
Exemple d’ajout d’un document
Le fonctionnement est le même qu’avec l’interpréteur, mais en utilisant la syntaxe php :
$etudiant = array('prenom' => 'Camille', 'nom' => 'Simon' );
$db->etudiants->insert($etudiant);
Références :
Récupérer l’identifiant lors de l’insertion
Après un appel à insert
, un champ _id
est ajouté au tableau qui a été passé en paramètre. Dans l’exemple ci-dessus, on peut récupérer l’identifiant par :
$id = $etudiant['_id'];
Parcourir les résultats de find
$cursor = $db->etudiants->find();
L’objet renvoyé permet d’itérer sur les documents trouvés, indexés par leur identifiant :
foreach($cursor as $id => $document) {
traite($id, $document);
}
Dans le corps de la boucle, $id
contient l’identifiant du document (le champ _id
), $document
est un tableau associatif clefs / valeurs comprenant tous les champs du document renvoyés par find
(y compris l’identifiant lui-même).
traite($id, $document)
{
echo '<dt>nom</dt><dd>'.$document['nom'].'</dd>';
echo '<dt>prenom</dt><dd>'.$document['prenom'].'</dd>';
}
Comme avec l’interpréteur, on peut définir une requête :
$requete = array( 'prenom' => 'Camille' );
$cursor = $db->etudiants->find($requete);
Références :
Recherche d’un document par son identifiant
$requete = array('_id' => new MongoId('532d40c72d150b4635b8cfc9'));
$etudiant = $db->etudiants->findOne($requete);
Références
Généralités
- NoSQL (Wikipédia fr)
- JSON (Wikipédia fr)
Mongo
- Tutorial
- Manuel
- Opérations sur les collections
insert
find
findOne
update
remove
- Les types (pas évoqué ici)
Trois admins de base de données entrent dans un bar NoSQL…
Ils en ressortent un peu plus tard, sans avoir pu trouver de table.
Excellent !
Merci pour cette anecdote d’admin SGBDR et ce petit tuto…
Excellent instruction. Moi, j’ai fait mes premiers pas avec MongoDB en suivant un tutoriel vidéo sur http://www.alphorm.com/tutoriel/formation-en-ligne-mongodb-administration. En tout cas, votre article est très réussi.