MongoDB seconde partie

By | 14 mai 2011

Introduction

Ce tutoriel est la suite de l’article Mongo DB (Php) Installation et premières impressions. Dans le premier article, j’ai voulu faire rapidement le tour des possibilités qu’avait à nous offrir MongoDB. Dans cet article, nous allons rentrer plus en détail dans l’utilisation des différentes méthodes. Nous verrons donc dans un premier temps la création de la base et de ses collections, l’insertion de documents et pour finir l’interrogation de la base.

Création de la base MongoDB

Je vais commencer par créer une base « forum », qui contiendra une collection « message ». La structure de notre objet ne sera donc pas compliqué, un unique id, une date, un statut de modération, un titre et son contenu texte.

// Connection sans authentification
$oMongo = new Mongo('mongodb://localhost:27017');

// Creation de la base
$oMgDB = $oMongo->selectDB('forum');

// Creation de la collection
$oMgCollection = $oMgDB->createCollection('message');

// Creation de l'index unique sur ID
$aContextIndex = array('unique' => true,
                       'safe'   => true,
                       'name'   => 'UID');
try {
	$oMgCollection->ensureIndex('uid', $aContextIndex);
	echo "Creation de l'index OK\n";
}
catch(MongoCursorException $e) {
	echo $e->getMessage() . "\n";
}

// Affichage des index
$aListIndexes = $oMgCollection->getIndexInfo();
print_r($aListIndexes);
/*
Array
(
    [0] => Array
        (
            [name] => _id_
            [ns] => forum.message
            [key] => Array
                (
                    [_id] => 1
                )

            [v] => 0
        )

    [1] => Array
        (
            [_id] => MongoId Object
                (
                    [$id] => 4dcec67466a1954f56000000
                )

            [ns] => forum.message
            [key] => Array
                (
                    [uid] => 1
                )

            [unique] => 1
            [name] => UID
            [v] => 0
        )

)
 */

Nous avons donc commencé par créer une connexion avec la base, en instanciant un objet Mongo. Comme vous pouvez le constater je n’ai pas précisé d’utilisateur, ni de mot de passe pour me connecter. La gestion des utilisateurs fera l’objet d’un autre tutoriel.

L’appel à la méthode selectDB() va créer une base « forum » si elle n’existe pas, ou la sélectionnera. L’appel à cette méthode renvoi un objet MongoDB, avec lequel nous allons créer notre collection « message » en appelant la méthode createCollection().

Nous sommes donc maintenant en possession d’un objet MongoCollection. Afin d’assurer l’unicité de nos messages, nous allons créer un index unique sur l’attribut « uid ». J’ai commencé par créer un contexte pour la création de l’index. Il devra donc assurer l’unicité de mes documents via l’attribut « uid » (unique => true). J’ai aussi activé le mode safe, qui me lévera une MongoCursorException si la création de l’index échoue (safe => true). La dernière valeur de ce tableau contient le nom de l’index, si vous ne précisez pas de nom, MongoDB lui en affectera un automatiquement (attributName_1).

Après la création de l’index, j’ai listé les index existants pour la collection « message ». Comme vous pouvez vous le constater, il y a deux index pour la collection « message ».  Le deuxième index, on le connait, c’est nous qui l’avons créé. Le premier quant à lui, c’est un index unique (ce n’est pas précisé mais il l’est) créé automatiquement par MongoDB.

!mportant: Le nom d’un attribut ne doit pas commencer par « $ », ni contenir le caractère « . ». L’attribut « _id » est réservé par MongoDB, il correspond à l’id unique attribué automatiquement lors d’une insertion en base.

Insertion de documents

Je vais donc insérer 100 000 documents, de manière à avoir une base assez conséquente pour nos tests en lecture.

$oMongo = new Mongo('mongodb://localhost:27017');

$oMgCollection = $oMongo->forum->message;

$aOptions = array('fsync' => true);

$iTimeMin = time() - 365*24*60*60;
$iTimeMax = time();
$aComment = array();
for($i = 0; $i < 100000; $i++) {

	$aComment['uid']    = $i;
	$aComment['date']   = rand($iTimeMin, $iTimeMax);
	$aComment['statut'] = rand(1, 3)%2;
	$aComment['titre']  = '';
	$aComment['texte']  = '';

    $iNbCharTitre = rand(10, 40);
    for($nbChar = 0; $nbChar < $iNbCharTitre; $nbChar++)
      $aComment['titre'].= $nbChar%10 == 0 ? ' ' : chr(rand(97, 122));

    $iNbCharTexte = rand(50, 140);
    for($nbChar = 0; $nbChar < $iNbCharTexte; $nbChar++)
      $aComment['texte'].= $nbChar%10 == 0 ? ' ' : chr(rand(97, 122));

	$oMgCollection->insert($aComment, $aOptions);
	unset($aComment);
}
echo $oMgCollection->count() . "\n";

L’insertion des 100 000 documents a durée environ 5min avec le fsync activé, contre 9sec sans. Ce qui est tout à fait normal. Avec le fsync activé, MongoDB synchronisera à chaque insertion les données sur le disque dur.

Comme dit précédemment j’ai volontairement activé le fsync, ce qui a pour effet que chaque insertion nécessitera une écriture sur le disque dur. Réellement ce genre de code est à éviter en production, car il est très couteux. Ce qui serai plus judicieux c’est de ne synchroniser que lors de la dernière insertion de la série.

Interrogation de la base

Les requêtes s’exécutent à partir d’un objet MongoCursor (méthode protégée doQuery()), ce denier est retourné par la méthode find() de MongoCollection. Nous allons donc commencer par sélectionner la collection sur laquelle nous voulons travailler.

$oMongo = new Mongo();
$oMgCollection = $oMongo->forum->message;

Pour interroger notre collection, nous allons donc utiliser la méthode find() de MongoCollection. Toutes les conditions de requête sont à passer en paramètre à cette méthode.

Sélectionner tout (0.00052)

$oMgCursor = $oMgCollection->find();

foreach($oMgCursor as $oDoc) {
	print_r($oDoc);
}

Dans la requête ci-dessus, nous appelons la méthode find() sans paramètre, ce qui a pour effet de sélectionner tous les documents présent dans la collection « message ».

!mportant: La requête n’est pas exécutée à l’appel de la méthode find(), mais à l’appel du premier résultat de MongoCursor (getNext(), hasNext() …).

Sélectionner les attributs à retourner

$oMgCursor = $oMgCollection->find(array(), array('titre' => 1));

foreach($oMgCursor as $oDoc) {
	echo $oDoc['titre'] . "\n";
}

MongoDB nous offre la possibilité de choisir les attributs à retourner. Pour ce faire il suffit de passer en second paramètre de la méthode find(), un tableau associatif. Ce tableau sera composé du nom de l’attribut en clé, et en valeur 1 ou -1. Dans l’exemple ci-dessus, nous souhaitons que seul l’attribut « titre » nous soit retourné. En contre partie, nous pouvons demander à MongoDB d’exclure un attribut du résultat. Pour cela il suffit de lui passer en valeur -1.

Condition d’égalité (0.00097)

$aListCond = array('statut' => 0);
$oMgCursor = $oMgCollection->find($aListCond);

foreach($oMgCursor as $oDoc) {
	echo $oDoc['statut'] . "\n";
}

Les conditions d’une requête sont donc à passer sous la forme d’un tableau à la méthode find(). La clé étant le nom de l’attribut, et la valeur les conditions à appliquer sur cet attribut. Dans l’exemple ci-dessus, nous recherchons donc tous les documents dont la valeur de l’attribut « statut » est égale à 0.

Supérieur à | $gt (0.00534)

$iTimeMin = time() - 10*24*60*60;
$aListCond = array('date' => array('$gt' => $iTimeMin));
$oMgCursor = $oMgCollection->find($aListCond);

foreach($oMgCursor as $oDoc) {
     echo date('d/m/Y', $oDoc['date']) . "\n";
}

Dans l’exemple ci-dessus, nous souhaitons obtenir la liste des messages publiés depuis les 10 derniers jours. L’attribut sur lequel appliquer la condition est donc « date ». Mais cette fois sa valeur est un tableau. Ce tableau est composé de l’opérateur en clé, et de sa valeur.  L’opérateur sera toujours préfixé d’un $ (peut être redéfini).

Inférieur à | $lt (0.00534)

$iTimeMax = time() - 10*24*60*60;
$aListCond = array('date' => array('$lt' => $iTimeMax));
$oMgCursor = $oMgCollection->find($aListCond);

foreach($oMgCursor as $oDoc) {
     echo date('d/m/Y', $oDoc['date']) . "\n";
}

Dans l’exemple ci-dessus, nous souhaitons obtenir la liste des messages publiés il y a plus de 10 jours.

Supérieur ou égal, et inférieur ou égal  | $gte/$lte (0.00645)

$iTimeMin = time() - 10*24*60*60;
$iTimeMax = time();
$aListCond = array('date' => array('$gte' => $iTimeMin,
                                   '$lte' => $iTimeMax));
$oMgCursor = $oMgCollection->find($aListCond);

foreach($oMgCursor as $oDoc) {
	echo date('d/m/Y', $oDoc['date']) . "\n";
}

Dans la requête ci-dessus, nous avons combiné supérieur ou égale ($gte) et inférieur ou égale ($lte).

Tester l’existence d’un attribut | $exists

$aListCond = array('date' => array('$exists' => false));
$oMgCursor = $oMgCollection->find($aListCond);

echo $oMgCursor->count() . "\n";

L’opérateur $exists permet de ne retourner que les documents possédant ou pas un attribut. Dans l’exemple ci-dessus, nous souhaitons obtenir la liste des documents ne possédant pas l’attribut « date ». Pour obtenir les documents possédant l’attribut « date », il suffit de modifier la valeur de $exists à true.

Limiter le nombre de résultats | MongoCursor::limit()

$oMgCursor = $oMgCollection->find();
$oMgCursor->limit(10);

foreach($oMgCursor as $oDoc) {
	echo date('d/m/Y', $oDoc['date']) . "\n";
}

Pour limiter le nombre de résultats, il faut utiliser la méthode limit() de MongoCursor.

Décalage des résultats | MongoCursor::skip()

$oMgCursor = $oMgCollection->find();
$oMgCursor->skip(10)->limit(10);

foreach($oMgCursor as $oDoc) {
	echo date('d/m/Y', $oDoc['date']) . "\n";
}

La méthode skip() permet de commencer la liste des résultats à partir du n-ième document. Utilisé par exemple pour la pagination d’une liste de résultats.

Trier les résultats | MongoCursor::sort()

$oMgCursor = $oMgCollection->find();
$oMgCursor->sort(array('titre' => 1))
          ->limit(10);

foreach($oMgCursor as $oDoc) {
	echo $oDoc['titre'] . "\n";
}

La méthode sort() permet de trier les résultats sur un ou plusieurs attributs. Elle attend en paramètre un tableau associatif, dont la clé correspond à l’attribut sur lequel trier et sa valeur l’ordre dans lequel trier. Il est donc possible de trier de manière croissante (1) ou décroissante (-1).

Exemple de trie sur l’attribut « date » de manière décroissante et « titre » de manière croissante:

$oMgCursor = $oMgCollection->find();
$oMgCursor->sort(array('date' => -1,'titre' => 1))
          ->limit(10);

foreach($oMgCursor as $oDoc) {
	echo $oDoc['date'] . ' ' . $oDoc['titre'] . "\n";
}

Conclusion

Comme vous avez pu le constater, MongoDB nous offre un système de requêtage très complet.  Je n’ai pas mentionné dans cet article tous les opérateurs existant, tel que $in (équivalent au IN de MySQL), $ne (différent) ou $regex. Il est aussi possible d’exécuter des requêtes à travers des scripts JS, je reviendrai dessus.

Je vais continuer d’enrichir cet article au fur et à mesure de mon avancé sur MongoDB, mais déjà de quoi s’amuser 🙂

  • mlemarrec

    Bravo, super article!
    Pour compléter:
    http://devzone.zend.com/article/12132

  • Jeremmm

    Super article, j’étais justement en train de regarder comment cette db fonctionnait.
    Article -> Favoris 😀