One Extension, Three Engines

Forum PHP

Paris, France

Oct 27th, 2015

http://derickrethans.nl/talks.html

Derick Rethans
derickr

One Extension,Two Engines

Derick Rethans

derick@mongodb.com—@derickr

http://joind.in/15252

Derick Rethans

  • I'm Dutch/British living in London
  • One of the PHP/HHVM MongoDB driver maintainers
  • Author of the PHP debugger tool Xdebug, PHP's Date/Time/Timezone support, and a bunch of other PHP extensions
  • I ♥🌍 maps
  • I ♥🍺 beer
  • GridFS implementation could get some more love
  • Support for multiple engines
  • Well thought out and minimalistic API
  • Faster to write and maintain
  • Easier to maintain
  • Keep backwards compatibility
  • Build on top of existing code
  • PHP 5.4 - PHP 5.6
  • Not sure what else to say really...
  • From Facebook
  • HipHop for PHP: compiles PHP to binary via C++
  • HHVM: a JIT engine
  • Also supports Hack language, "Xdebug", etc...
  • Written in C++, and OCAML, and others
  • User land code (mostly) compatible with PHP 5
  • Internals quite a lot different than PHP 5

libbson is a new shared library written in C for developers wanting to work with the BSON serialization format.

  • It's used by libmongoc
  • It's used by the drivers directly as well
  • High performance BSON serialization and deserialization
  • Callback API for deserialization

mongo-c-driver is a client library written in C for MongoDB

  • Meant as a low level driver for applications and higher level languages
  • About two years old
  • git@github.com:mongodb/mongo-c-driver.git
  • Used by the PHP and HHVM drivers
  • New backwards breaking MongoDB extension for PHP 5.4 and higher
  • Very minimal API
  • Meant to be build upon by PHP libraries (like the 0.8.4 driver)
  • Uses PHP namespaces
  • Old extension name is "Mongo"
  • New extension name will be "MongoDB"
  • You will be able to load both "mongo.so" and "mongodb.so"

pecl site:

http://docs.php.net/manual/en/refs.database.php:

$m = new MongoClient("mongodb://localhost:27017");

$m->demo->test->drop();


$doc = [
    'string' => 'bar',
    'number_i' => 55,
    'number_l' => 12345678901234567,
    'bool' => true,
    'null' => null,
    'float' => M_PI,
];

$r = $m->demo->test->insert( $doc );

$cursor = $m->demo->test->find();


foreach ( $cursor as $key => $result )
{
    print_r( $result );
}
?>
$m = new MongoDB\Driver\Manager("mongodb://localhost:27017");

$c = new MongoDB\Driver\Command( [ 'drop' => 'test' ] );
$cursor = $m->executeCommand( 'demo', $c );

$doc = [
    'string' => 'bar',
    'number_i' => 55,
    'number_l' => 12345678901234567,
    'bool' => true,
    'null' => null,
    'float' => M_PI,
];
$bw = new MongoDB\Driver\BulkWrite();
$bw->insert( 'demo.test', $doc );
$m->executeBulkWrite( 'demo.test', $bw );
$q = new MongoDB\Driver\Query( [] );
$cursor = $m->executeQuery( 'demo.test', $q );

foreach ( $cursor as $key => $result )
{
    print_r( $result );
}
?>
  • "Same" implementation of phongo APIs as extension for HHVM
  • Drop-in replacement
  • No shared code, as this is C++
  • Parts of it can be written in Hack

ext_mongo.php (extract)

<?hh
namespace MongoDB\Driver;
    <<__Native>> 
    public function __construct(string $dsn = "localhost", array $options = array(), array $driverOptions = array());

    <<__Native>>
    public function __debugInfo() : array;
…
    <<__Native>>
    public function executeQuery(string $namespace, Query $query, ReadPreference $readPreference = null): Cursor;
…
    <<__Native>>
    public function selectServer(ReadPreference $readPreference): Server;

}

src/MongoDB/Driver/Manager.cpp

void HHVM_METHOD(MongoDBDriverManager, __construct, const String &dsn, const Array &options, const Array &driverOptions)
{
    MongoDBDriverManagerData* data = Native::data<MongoDBDriverManagerData>(this_);
    mongoc_uri_t *uri;
    mongoc_client_t *client;

    uri = hippo_mongo_driver_manager_make_uri(dsn.c_str(), options);
    client = mongoc_client_new_from_uri(uri);

    if (!client) {
        throw MongoDriver::Utils::throwRunTimeException("Failed to create Manager from URI: '" + dsn + "'");
    }

    data->m_client = client;

    hippo_mongo_driver_manager_apply_ssl_opts(data->m_client, driverOptions);

    hippo_mongo_driver_manager_apply_rp(data->m_client, options);
    hippo_mongo_driver_manager_apply_wc(data->m_client, options);
}

ext_mongo.php (extract)

<?hh
namespace MongoDB\Driver;

<<__NativeData("MongoDBDriverReadPreference")>>
final class ReadPreference {
    <<__Native>>
    private function _setReadPreference(int $readPreference): void;

    <<__Native>>
    private function _setReadPreferenceTags(array $tagSets): void;

    public function __construct(int $readPreference, mixed $tagSets = null)
    {
        if ($tagSets !== NULL && Utils::mustBeArrayOrObject('parameter 2', $tagSets)) {
            return;
        }

        switch ($readPreference) {
            case ReadPreference::RP_PRIMARY:
            case ReadPreference::RP_PRIMARY_PREFERRED:
            case ReadPreference::RP_SECONDARY:
            case ReadPreference::RP_SECONDARY_PREFERRED:
            case ReadPreference::RP_NEAREST:
                // calling into Native
                $this->_setReadPreference($readPreference);

                if ($tagSets) {
                    // calling into Native, might throw exception
                    $this->_setReadPreferenceTags($tagSets);
                }

                break;

            default:
                Utils::throwHippoException(Utils::ERROR_INVALID_ARGUMENT, "Invalid ReadPreference");
                break;
        }
    }
}

src/MongoDB/Driver/Manager (extract)

void HHVM_METHOD(MongoDBDriverReadPreference, _setReadPreference, int readPreference)
{
    MongoDBDriverReadPreferenceData* data = Native::data<MongoDBDriverReadPreferenceData>(this_);

    data->m_read_preference = mongoc_read_prefs_new((mongoc_read_mode_t) readPreference);
} 

src/MongoDB/Driver/Manager.cpp

void HHVM_METHOD(MongoDBDriverManager, __construct, const String &dsn, const Array &options, const Array &driverOptions)
{
…

src/MongoDB/Manager.c

PHP_METHOD(Manager, __construct)
{
    php_phongo_manager_t     *intern;
    zend_error_handling       error_handling;
    mongoc_uri_t             *uri;
    char                     *uri_string;
    int                       uri_string_len;
    zval                     *options = NULL;
    bson_t                    bson_options = BSON_INITIALIZER;
    zval                     *driverOptions = NULL;
    (void)return_value; (void)return_value_ptr; (void)return_value_used;

    zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling TSRMLS_CC);
    intern = (php_phongo_manager_t *)zend_object_store_get_object(getThis() TSRMLS_CC);

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a!a!", &uri_string, &uri_string_len, &options, &driverOptions) == FAILURE) {
        zend_restore_error_handling(&error_handling TSRMLS_CC);
        return;
    }
    zend_restore_error_handling(&error_handling TSRMLS_CC);

    if (options) {
        zval_to_bson(options, PHONGO_BSON_NONE, &bson_options, NULL TSRMLS_CC);
    }
…
$m = new MongoDB\Driver\Manager("mongodb://localhost:27017");

$c = new MongoDB\Driver\Command( [ 'drop' => 'test' ] );
$cursor = $m->executeCommand( 'demo', $c );

$doc = [
    'string' => 'bar',
    'number_i' => 55,
    'number_l' => 12345678901234567,
    'bool' => true,
    'null' => null,
    'float' => M_PI,
];
$bw = new MongoDB\Driver\BulkWrite();
$bw->insert( 'demo.test', $doc );
$m->executeBulkWrite( 'demo.test', $bw );
$q = new MongoDB\Driver\Query( [] );
$cursor = $m->executeQuery( 'demo.test', $q );

foreach ( $cursor as $key => $result )
{
    print_r( $result );
}
?>
Array
(
    [_id] => BSON\ObjectID Object
        (
            [oid] => 555b6be7b8c96e1a676318d1
        )

    [string] => bar
    [number_i] => 55
    [number_l] => 12345678901234567
    [bool] => 1
    [null] => 
    [float] => 3.1415926535898
)
stdClass
(
    [_id] => MongoDB\BSON\ObjectID Object
        (
            [oid] => 555b6be7b8c96e1a676318d1
        )

    [string] => bar
    [number_i] => 55
    [number_l] => 12345678901234567
    [bool] => 1
    [null] => 
    [float] => 3.1415926535898
)
  • Sigh
  • Order of elements in structs
  • char * vs. zend_string
  • zend_hash changes
  • Not coming out until: after 1.0 for hippo and phongo; PHP 7 is actually released; throrough review
  • Userland library sitting on top of Phongo/Hippo
  • Implements the convenience methods that the basic APIs lack
  • Installable by composer:
require 'vendor/autoload.php';
$m = new MongoDB\Client("mongodb://localhost:27017");
$d = $m->selectDatabase( 'demo' );
$c = $d->selectCollection( 'test' );

$c->drop();

$doc = [
    'string' => 'bar',
    'number_i' => 55,
    'number_l' => 12345678901234567,
    'bool' => true,
    'null' => null,
    'float' => M_PI,
];

$r = $c->insertOne( $doc );

$cursor = $c->find( [] );

foreach ( $cursor->toArray() as $key => $result )
{
    print_r( $result );
}
?>
  • Phongo has been release as 1.0.0
  • MongoDB\Driver\RuntimeExceptionMongoDB\Driver\Exception\RuntimeException
  • Namespace "arguments":MongoDB\BSON\Binary vs. BSON\Binary
  • Changes in result types:CommandResult and QueryResultResult
  • Proper specification on how to serialize/deserialize
  • Not a lot of the APIs are documented
  • HHVM has "issues" regarding exceptions and ctors
  • Bundling and configuring
  • Sharing connections and state
  • HHVM cookbook:https://github.com/derickr/hhvm-hni-cookbook
  • phongo has been released as 1.0.0pecl install mongodb
  • hippo has a release candidatehttps://github.com/mongodb-labs/mongo-hhvm-driver-prototype/releases/tag/1.0.0RC1
  • phplib in alpha state (1.0.0-alpha1)https://packagist.org/packages/mongodb/mongodb
  • ---Align APIs---
  • Finish Phongo, Hippo and phplib
  • Add GridFS library to phplib
  • Write documentation and tutorials

http://joind.in/15252

derick@mongodb.com—@derickr