PHP7 and MongoDb madness
Intro
This week I’ve decided to upgrade version of PHP to 7 and refactor code of my framework to use its new features. On my local machine I have php 5.5.32 which I use for all my projects so far. So I provisioned VM using Vagrant and Ansible.
The part of the role (in terms of Ansible) responsible for PHP 7 installation:
- name: PHP | Add PHP 7.0 PPA
apt_repository: repo='ppa:ondrej/php'
state=present
update_cache=yes
become: yes
become_method: sudo
- name: ensure PHP installed
apt: pkg={{ item }} state=latest
with_items:
- php7.0
- php7.0-dev
... more packages goes here
- php-pear
tags: php
for MongoDb:
---
- name: add MongoDB keyring
become: yes
become_method: sudo
shell: apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
- name: add MongoDB repo
shell: echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | tee /etc/apt/sources.list.d/mongodb.list
become: yes
become_method: sudo
- name: install MongoDB
apt: pkg=mongodb-org state=latest update_cache=yes
become: yes
become_method: sudo
notify: restart mongodb
and for MongoDB PHP driver:
- name: Install php driver for MongoDB
pear: name=pecl/mongodb state=present
become: yes
become_method: sudo
After I had VM up and running I decided to run my unit test and got the first surprise:
Fatal error: Uncaught Error: Class 'MongoDB' not found in ...
After quick search I realized that ‘MongoDB’ class is provided only by mongo.so extension (which is deprecated) but not by mongodb.so. This meant that I had to rewrite all classes related to Mongo. Fortunately I had unit tests for all of them.
Next challenge I faced with was lack of examples for new MongoDB driver. If you are going to move from old MongoDb driver to the new one — here is my mini how-to.
Refactoring
First of all you have to use new Manager class, like
$storage = new \MongoDB\Driver\Manager("mongodb://localhost:27017");
instead of
$storage = new \MongoDB(new \MongoClient(), 'mongo_db_name');
see more in official documentation
One of the classes related to MongoDb was a class responsible for adding and getting items from the queue. Lets go method after method to see how I refactored my code.
To create a queue based on MongoDb we need two collections: one for items and another one for generating auto-increment IDs. See explanation in MongoDb docs
First of all we need to prepare our collections:
New MongoDb driver uses a fully qualified namespace (e.g. “databaseName.collectionName”) in most commands, so we prepare two namespaces one per collection.
For any write operation like ‘insert’, ‘update’, ‘delete’ we need instance of MongoDB\Driver\BulkWrite. Because it is bulk you can do several operations at once:
$bulk = new MongoDB\Driver\BulkWrite();
$bulk->insert([‘_id’ => 1, ‘x’ => 1]);
$bulk->insert([‘_id’ => 2, ‘x’ => 2]);
and then execute it with:
$manager->executeBulkWrite(‘db.collection’, $bulk, $writeConcern);
Items in the queue can have two types of priority: Normal and High. Items with high priority must be extracted before items with normal priority even if former was inserted earlier then latter.
To achieve this we need to add two additional fields to each item in the queue: ‘seq’ to store position in the sequence and ‘priority’. For quick search we add indexes for these fields. You should use MongoDB\Driver\Command for such operations.
To add new item to queue we have to generate new auto-incremental id to set it as ‘seq’. Following instructions from MongoDb site I used findAndModify query against ‘counters collection’:
Once we get value for ‘seq’ we can push an item to a queue. Here I used already known bulkInsert() again:
To extract item from the queue we have to use findAndModify again but this time against main collection:
As you see from the query I sort collection by priority desc and sequence number asc and remove found item from the queue.
findAndModify as well as update are atomic so even if we have number of concurrent processes every process will get its own item.
If you have an experience of work with old MongoDb driver you can compare the old version of the class with the new one.
One thing which was not covered in this article is execution of the queries on mongo. There is a good example in official documentation for MongoDB\Driver\Manager::executeQuery which is pretty straightforward