MongoDB ODM. Part of @PHPMongoKit

Overview

PHPMongo ODM

Total Downloads Build Status Coverage Status Scrutinizer Code Quality Code Climate SensioLabsInsight Gitter

PHP ODM for MongoDB.

Why to use this ODM? You can easily work with document data through comfortable getters and setters instead of array and don't check if key exist in array. Access to sub document uses dot-syntax. You can validate data passed to document before save. We give you events, which you can handle in different moments of document's life. You can create relations, build aggregations, create versioned documents, write migrations and do more things which makes your life easier.

🔢 Tested over MongoDB v.2.4.12, v.2.6.9, v.3.0.2, v.3.2.10, v.3.3.15, v.3.4.0. See Unit tests for details.

ArmySOS - Help for Ukrainian Army

Requirements


Table of contents



Installation

Common installation

You can install library through Composer:

composer require sokil/php-mongo

Download latest release: Latest sources from GitHub

Compatibility with PHP 7

PHPMongo currently based on old ext-mongo entension. To use this ODM with PHP 7, you need to add compatibility layer, which implement API of old extension over new ext-mongodb. To start using PHPMongo with PHP7, add requirement alcaeus/mongo-php-adapter to composer. Restrictions for using ODM with compatibility layer you can read in known issues of original adapter.

Require adapter of old ext-mongo API to new ext-mongodb:

composer require alcaeus/mongo-php-adapter

Symfony bundle

If you use Symfony framework, you can use Symfony MongoDB Bundle which wraps this library

composer require sokil/php-mongo-bundle

Laravel

If you use Laravel framework, use Laravel adapter

composer require phpmongokit/laravel-mongo-odm

Yii component

If you use Yii Framework, you can use Yii Adapter which wraps this library

composer require sokil/php-mongo-yii

This package in addition to PHPMongo adapter also has data provider and log router for MongoDb.

Yii2 component

If you use Yii2 Framework, you can use Yii2 Adapter which wraps this library

composer require phpmongokit/yii2-mongo-odm

Support of migrations

If you require migrations, you can add dependency to Migrator, based on this library:

composer require sokil/php-mongo-migrator


Connecting

Single connection

Connecting to MongoDB server made through \Sokil\Mongo\Client class:

<?php
$client = new Client($dsn);

Format of DSN used to connect to server is described in PHP manual. To connect to localhost, use next DSN:

mongodb://127.0.0.1

To connect to replica set, use next DSN:

mongodb://server1.com,server2.com/?replicaSet=replicaSetName

Pool of connections

If you have few connections, you may prefer connection pool instead of managing different connections. Use \Sokil\Mongo\ClientPool instance to initialize pool object:

<?php

$pool = new ClientPool(array(
    'connect1' => array(
        'dsn' => 'mongodb://127.0.0.1',
        'defaultDatabase' => 'db2',
        'connectOptions' => array(
            'connectTimeoutMS' => 1000,
            'readPreference' => \MongoClient::RP_PRIMARY,
        ),
        'mapping' => array(
            'db1' => array(
                'col1' => '\Collection1',
                'col2' => '\Collection2',
            ),
            'db2' => array(
                'col1' => '\Collection3',
                'col2' => '\Collection4',
            )
        ),
    ),
    'connect2' => array(
        'dsn' => 'mongodb://127.0.0.1',
        'defaultDatabase' => 'db2',
        'mapping' => array(
            'db1' => array(
                'col1' => '\Collection5',
                'col2' => '\Collection6',
            ),
            'db2' => array(
                'col1' => '\Collection7',
                'col2' => '\Collection8',
            )
        ),
    ),
));

$connect1Client = $pool->get('connect1');
$connect2Client = $pool->get('connect2');


Mapping

Selecting database and collection

You can get instances of databases and collections by its name.

To get instance of database class \Sokil\Mongo\Database:

<?php
$database = $client->getDatabase('databaseName');
// or simply
$database = $client->databaseName;

To get instance of collection class \Sokil\Mongo\Collection:

<?php
$collection = $database->getCollection('collectionName');
// or simply
$collection = $database->collectionName;

Default database may be specified to get collection directly from \Sokil\Mongo\Client object:

<?php
$client->useDatabase('databaseName');
$collection = $client->getCollection('collectionName');

Custom collections

Custom collections are used to add some collection-specific features in related class. First you need to create class extended from \Sokil\Mongo\Collection:

<?php

// define class of collection
class CustomCollection extends \Sokil\Mongo\Collection
{

}

This class must be then mapped to collection name in order to return object of this class when collection requested. Custom collection referenced in standard way:

<?php
/**
 * @var \CustomCollection
 */
$collection = $client
    ->getDatabase('databaseName')
    ->getCollection('collectionName');

Collection definition

Collection name must be mapped to collection class. If you want to pass some additional options to collection, you also can configure them in mapping definition:

<?php
$client->map([
    'databaseName'  => [
        'collectionName' => [
            'class' => '\Some\Custom\Collection\Classname',
            'collectionOption1' => 'value1',
            'collectionOption2' => 'value2',
        ]
    ],
]);

All options later may be accessed by Collection::getOption() method:

<?php
// will return 'value1'
$client
    ->getDatabase('databaseName')
    ->getCollection('collectionName')
    ->getOption('collectionOption1');

Predefined options are:

Option Default value Description
class \Sokil\Mongo\Collection Fully qualified collection class
documentClass \Sokil\Mongo\Document Fully qualified document class
versioning false Using document versioning
index null Index definition
expressionClass \Sokil\Mongo\Expression Fully qualified expression class for custom query builder
behaviors null List of behaviors, attached to every document
relations null Definition of relations to documents in other collection
batchSize null Number of documents to return in each batch of response
clientCursorTimeout null A timeout can be set at any time and will affect subsequent queries on the cursor, including fetching more results from the database
serverCursorTimeout null A cumulative time limit in milliseconds to be allowed by the server for processing operations on the cursor
documentPool true Document pool, used to store already fetched documents in identity map

If class omitted, then used standart \Sokil\Mongo\Collection class.

To override default document class use documentClass option of collection:

<?php
$client->map([
    'databaseName'  => [
        'collectionName' => [
            'documentClass' => '\Some\Document\Class',
        ]
    ],
]);

// is instance of \Some\Document\Class
$document = $client
    ->getDatabase('databaseName')
    ->getCollection('collectionName')
    ->createDocument();

Mapping of collection name to collection class

If only class name of collection defined, you may simply pass it in mapping.

<?php

// map class to collection name
$client->map([
    'databaseName'  => [
        'collectionName' => [
            'class' => \Acme\MyCollection',
        ],
    ],
]);

/**
 * @var \Acme\MyCollection
 */
$collection = $client
    ->getDatabase('databaseName')
    ->getCollection('collectionName');

There is also deprecated method to specify collection's class name. Please, use array definition and option class.

<?php

// map class to collection name
$client->map([
    'databaseName'  => [
        'collectionName' => '\Acme\MyCollection'
    ],
]);

/**
 * @var \Acme\MyCollection
 */
$collection = $client
    ->getDatabase('databaseName')
    ->getCollection('collectionName');

Mapping with class prefix

Collections not configured directly, may be mapped automatically by using * in mapping keys. Any collection may be mapped to class without enumerating every collection name.

<?php
$client->map([
    'databaseName'  => [
        '*' => [
            'class' => '\Acme\Collection\Class\Prefix',
        ],
    ],
]);

/**
 * @var \Acme\Collection\Class\Prefix\CollectionName
 */
$collection = $client
    ->getDatabase('databaseName')
    ->getCollection('collectionName');

/**
 * @var \Acme\Collection\Class\Prefix\CollectionName\SubName
 */
$collection = $client
    ->getDatabase('databaseName')
    ->getCollection('collectionName.subName');

There is also deprecated method to specify class prefix. Please, use * as collection name and array definition with option class.

<?php
$client->map([
    'databaseName'  => '\Acme\Collection\Class\Prefix',
]);

/**
 * @var \Acme\Collection\Class\Prefix\CollectionName
 */
$collection = $client
    ->getDatabase('databaseName')
    ->getCollection('collectionName');

/**
 * @var \Acme\Collection\Class\Prefix\CollectionName\SubName
 */
$collection = $client
    ->getDatabase('databaseName')
    ->getCollection('collectionName.subName');

Regexp mapping

Collection name in mapping may be defined as RegExp pattern. Pattern must start from symbol /:

<?php
$database->map(array(
    '/someCollection(\d)/' => '\Some\Collection\Class',
));

Any collection with name matched to pattern will be instance of \Some\Collection\Class:

<?php
$col1 = $database->getCollection('someCollection1');
$col2 = $database->getCollection('someCollection2');
$col4 = $database->getCollection('someCollection4');

Any stored regexp values than may be get through $collection->getOption('regex');.

<?php
$database->map(array(
    '/someCollection(\d+)/' => '\Some\Collection\Class',
));
$col42 = $database->getCollection('someCollection42');
echo $col1->getOption('regexp')[0]; // someCollection42
echo $col1->getOption('regexp')[1]; // 42

Document schema and validating

Custom document class

Custom document class may be useful when required some processing of date on load, getting or save. Custom document class must extend \Sokil\Mongo\Document.

<?php
class CustomDocument extends \Sokil\Mongo\Document
{

}

Now you must configure its name in collection's class by overriding method Collection::getDocumentClassName():

<?php
class CustomCollection extends \Sokil\Mongo\Collection
{
    public function getDocumentClassName(array $documentData = null) {
        return '\CustomDocument';
    }
}

Single Collection Inheritance

Often useful to have different document classes, which store data in single collection. For example you have products in your shop Song and VideoClip, which inherit abstract Product. They have same fields like author or duration, but may also have other different fields and behaviors. This situation described in example Product Catalog.

You may flexibly configure document's class in \Sokil\Mongo\Collection::getDocumentClassName() relatively to concrete value of field (this field called discriminator), or other more complex logic:

<?php
class CustomCollection extends \Sokil\Mongo\Collection
{
    public function getDocumentClassName(array $documentData = null) {
        return '\Custom' . ucfirst(strtolower($documentData['type'])) . 'Document';
    }
}

Also document class may be defined in collection mapping:

<?php
$client->map([
    'databaseName'  => [
        'collectionName1' => [
            'documentClass' => '\CustomDocument',
        ],
        'collectionName2' => function(array $documentData = null) {
            return '\Custom' . ucfirst(strtolower($documentData['type'])) . 'Document';
        },
        'collectionName3' => [
            'documentClass' => function(array $documentData = null) {
                return '\Custom' . ucfirst(strtolower($documentData['type'])) . 'Document';
            },
        ],
    ],
]);

In example above class \CustomVideoDocument related to {"_id": "45..", "type": "video"}, and \CustomAudioDocument to {"_id": "45..", type: "audio"}

Document schema

Document's scheme is completely not required. If field is required and has default value, it can be defined in special property of document class Document::schema:

<?php
class CustomDocument extends \Sokil\Mongo\Document
{
    protected $schema = [
        'requiredField' => 'defaultValue',
        'someField'     => [
            'subDocumentField' => 'value',
        ],
    ];
}

Also supported deprecated format Document::_data:

<?php
class CustomDocument extends \Sokil\Mongo\Document
{
    protected $_data = [
        'requiredField' => 'defaultValue',
        'someField'     => [
            'subDocumentField' => 'value',
        ],
    ];
}


Document validation

Document may be validated before save. To set validation rules, you may override method \Sokil\Mongo\Document::rules() and pass validation rules here. Supported rules are:

<?php
class CustomDocument extends \Sokil\Mongo\Document
{
    public function rules()
    {
        return array(
            array('email,password', 'required'),
            array('role', 'equals', 'to' => 'admin'),
            array('role', 'not_equals', 'to' => 'guest'),
            array('role', 'in', 'range' => array('admin', 'manager', 'user')),
            array('contract_number', 'numeric', 'message' => 'Custom error message, shown by getErrors() method'),
            array('contract_number' ,'null', 'on' => 'SCENARIO_WHERE_CONTRACT_MUST_BE_NULL'),
            array('code' ,'regexp', '#[A-Z]{2}[0-9]{10}#')
        );
    }
}

Document can have validation state, based on scenario. Scenario can be specified by method Document::setScenario($scenario).

<?php
$document->setScenario('register');

If some validation rule applied only for some scenarios, this scenarios must be passed on on key, separated by comma.

<?php
public function rules()
    {
        return array(
            array('field' ,'null', 'on' => 'register,update'),
        );
    }

If some validation rule applied to all except some scenarios, this scenarios must be passed on except key, separated by comma.

<?php
public function rules()
    {
        return array(
            array('field' ,'null', 'except' => 'register,update'),
        );
    }

There are two equal cases for document validation:

try {
    $document->save();
} catch (\Sokil\Mongo\Document\InvalidDocumentException $e) {
    // get validation errors
    var_dump($document->getErrors());
    // get document instance from exception
    var_dump($e->getDocument()->getErrors());
}

or

if ($document->isValid())
    $document->save();
} else {
    var_dump($document->getErrors());
}

By default, document validates before save and \Sokil\Mongo\Document\InvalidDocumentException is thrown if it invalid. Exception \Sokil\Mongo\Document\Exception\Validate was thrown before v.1.11.6 when document was invalid. Since v.1.11.6 this exception is deprecated. Use \Sokil\Mongo\Document\InvalidDocumentException instead.

Errors may be accessed through Document::getErrors() method of document object.

Also instabce of document may be get from exception method:

<?php
try {
    $document->save();
} catch(\Sokil\Mongo\Document\InvalidDocumentException $e) {
    $e->getDocument()->getErrors();
}

If allowed to save invalid documents, disable validation on save:

$document->save(false);

Error may be triggered manually by calling method triggerError($fieldName, $rule, $message)

<?php
$document->triggerError('someField', 'email', 'E-mail must be at domain example.com');

You may add you custom validation rule just adding method to document class and defining method name as rule:

<?php
class CustomDocument extends \Sokil\Mongo\Document
{
    punlic function rules()
    {
        return array(
            array(
                'email',
                'uniqueFieldValidator',
                'message' => 'E-mail must be unique in collection'
            ),
        );
    }

    /**
     * Validator
     */
    public function uniqueFieldValidator($fieldName, $params)
    {
        // Some logic of checking unique mail.
        //
        // Before version 1.7 this method must return true if validator passes,
        // and false otherwise.
        //
        // Since version 1.7 this method return no values and must call
        // Document::addError() method to add error into stack.
    }
}

You may create your own validator class, if you want to use validator in few classes. Just extend your class from abstract validator class \Sokil\Mongo\Validator and register your own validator namespace:

<?php
namespace Vendor\Mongo\Validator;

/**
 * Validator class
 */
class MyOwnEqualsValidator extends \Sokil\Mongo\Validator
{
    public function validateField(\Sokil\Mongo\Document $document, $fieldName, array $params)
    {
        if (!$document->get($fieldName)) {
            return;
        }

        if ($document->get($fieldName) === $params['to']) {
            return;
        }

        if (!isset($params['message'])) {
            $params['message'] = 'Field "' . $fieldName . '" must be equals to "' . $params['to'] . '" in model ' . get_called_class();
        }

        $document->addError($fieldName, $this->getName(), $params['message']);
    }
}

/**
 * Registering validator in document
 */

class SomeDocument extends \Sokil\Mongo\Document
{
    public function beforeConstruct()
    {
        $this->addValidatorNamespace('Vendor\Mongo\Validator');
    }

    public function rules()
    {
        return array(
            // 'my_own_equals' converts to 'MyOwnEqualsValidator' class name
            array('field', 'my_own_equals', 'to' => 42, 'message' => 'Not equals'),
        );
    }
}


Getting documents by id

To get document from collection by its id:

<?php
$document = $collection->getDocument('5332d21b253fe54adf8a9327');

To add additional checks or query modifiers use callable:

<?php
$document = $collection->getDocument(
    '5332d21b253fe54adf8a9327',
    function(\Sokil\Mongo\Cursor $cursor) {
        // get document only if active
        $cursor->where('status', 'active');
        // slice embedded documents
        $cursor->slice('embdocs', 10, 30);
    }
);

Note that if callable specified, document always loaded directly omitting document pool.

Create new document

Create new empty document object:

<?php
$document = $collection->createDocument();

Or with pre-defined values:

<?php
$document = $collection->createDocument([
    'param1' => 'value1',
    'param2' => 'value2'
]);


Get and set data in document

Get

To get value of document's field you may use one of following ways:

<?php
$document->requiredField; // defaultValue
$document->get('requiredField'); // defaultValue
$document->getRequiredField(); // defaultValue

$document->someField; // ['subDocumentField' => 'value']
$document->get('someField'); // ['subDocumentField' => 'value']
$document->getSomeField(); // ['subDocumentField' => 'value']
$document->get('someField.subDocumentField'); // 'value'

$document->get('some.unexisted.subDocumentField'); // null

If field not exists, null value returned.

Set

To set value you may use following ways:

<?php
$document->someField = 'someValue'; // {someField: 'someValue'}
$document->set('someField', 'someValue'); // {someField: 'someValue'}
$document->set('someField.sub.document.field', 'someValue'); // {someField: {sub: {document: {field: {'someValue'}}}}}
$document->setSomeField('someValue');  // {someField: 'someValue'}

Push

Push will add value to array field:

<?php

$document->push('field', 1);
$document->push('field', 2);

$document->get('field'); // return [1, 2]

If field already exists, and not sequential list of values, then in case of scalar, scalar will be converted to array. If values pushed to field with subdocument, then triggered Sokil\Mongo\Document\InvalidOperationException.



Embedded documents

Get embedded document

Imagine that you have document, which represent User model:

{
    "login": "beebee",
    "email": "[email protected]",
    "profile": {
        "birthday": "1984-08-11",
        "gender": "female",
        "country": "Ukraine",
        "city": "Kyiv"
    }
}

You can define embedded profile document as standalone class:

<?php

/**
 * Profile class
 */
class Profile extends \Sokil\Mongo\Structure
{
    public function getBirthday() { return $this->get('birthday'); }
    public function getGender() { return $this->get('gender'); }
    public function getCountry() { return $this->get('country'); }
    public function getCity() { return $this->get('city'); }
}

/**
 * User model
 */
class User extends \Sokil\Mongo\Document
{
    public function getProfile()
    {
        return $this->getObject('profile', '\Profile');
    }
}

Now you are able to get profile params:

<?php
$birthday = $user->getProfile()->getBirthday();

Set embedded document

You can also set embedded document. If embedded document has validation rules, they will be checked before embed it to document:

/**
 * Profile class
 */
class Profile extends \Sokil\Mongo\Structure
{
    public function getBirthday() { return $this->get('birthday'); }

    public function rules()
    {
        return array(
            array('birthday', 'required', 'message' => 'REQUIRED_FIELD_EMPTY_MESSAGE'),
        );
    }
}

/**
 * User model
 */
class User extends \Sokil\Mongo\Document
{
    public function setProfile(Profile $profile)
    {
        return $this->set('profile', $profile);
    }
}

If embedded document is invalid, it will throw Sokil\Mongo\Document\InvalidDocumentException. Embedded document may be obtained from exception object:

try {
    $user->set('profile', $profile);
} catch (InvalidDocumentException $e) {
    $e->getDocument()->getErrors();
}

Get embedded list of documents

Imagine that you have stored post data in collection 'posts', and post document has embedded comment documents:

{
    "title": "Storing embedded documents",
    "text": "MongoDb allows to atomically modify embedded documents",
    "comments": [
        {
            "author": "MadMike42",
            "text": "That is really cool",
            "date": ISODate("2015-01-06T06:49:41.622Z"
        },
        {
            "author": "beebee",
            "text": "Awesome!!!11!",
            "date": ISODate("2015-01-06T06:49:48.622Z"
        },
    ]
}

So we can create Comment model, which extends \Sokil\Mongo\Structure:

<?php
class Comment extends \Sokil\Mongo\Structure
{
    public function getAuthor() { return $this->get('author'); }
    public function getText() { return $this->get('text'); }
    public function getDate() { return $this->get('date')->sec; }
}

Now we can create Post model with access to embedded Comment models:

<?php

class Post extends \Sokil\Mongo\Document
{
    public function getComments()
    {
        return $this->getObjectList('comments', '\Comment');
    }
}

Method Post::getComments() allows you to get all of embedded document. To paginate embedded documents you can use \Sokil\Mongo\Cursor::slice() functionality.

<?php
$collection->find()->slice('comments', $limit, $offset)->findAll();

If you get Document instance through Collection::getDocument() you can define additional expressions for loading it:

<?php
$document = $collection->getDocument('54ab8585c90b73d6949d4159', function(Cursor $cursor) {
    $cursor->slice('comments', $limit, $offset);
});

Set embedded list of documents

You can store embedded document to array, and validate it before pushing:

<?php
$post->push('comments', new Comment(['author' => 'John Doe']));
$post->push('comments', new Comment(['author' => 'Joan Doe']));

Validation of embedded documents

As embedded document is Structure, it has all validation functionality as Document. Currently embedded document validates only just before set to Document or manually. If embedded document is invalid, it trowns Sokil\Mongo\Document\InvalidDocumentException.

class EmbeddedDocument extends Structure()
{
    public function rules() {}
}

$embeddedDocument = new EmbeddedDocument();
// auto validation
try {
    $document->set('some', embeddedDocument);
    $document->addToSet('some', embeddedDocument);
    $document->push('some', embeddedDocument);
} catch (InvalidDocumentException $e) {
    
}

// manual validation
if ($embeddedDocument->isValid()) {
    $document->set('some', embeddedDocument);
    $document->addToSet('some', embeddedDocument);
    $document->push('some', embeddedDocument);
}


DBRefs

In most cases you should use the manual reference method for connecting two or more related documents - including one document’s _id field in another document. The application can then issue a second query to resolve the referenced fields as needed. See more info about supporting manual references in section Relations

However, if you need to reference documents from multiple collections, or use legacy database with DBrefs inside, consider using DBRefs. See more info about DBRef at https://docs.mongodb.com/manual/reference/database-references/.

If you have DBRef array, you can get document instance:

<?php
$collection->getDocumentByReference(array('$ref' => 'col', '$id' => '23ef12...ff452'));
$database->getDocumentByReference(array('$ref' => 'col', '$id' => '23ef12...ff452'));

Adding reference to one document in another:

<?php
$relatedDocument = $this->collection->createDocument(array('param' => 'value'))->save();
$document = $this->collection
    ->createDocument()
    ->setReference('related', $relatedDocument)
    ->save();

Get document from reference field:

<?php

$relatedDocument = $document->getReferencedDocument('related');

Push relation to list of relations:

<?php

$relatedDocument = $this->collection->createDocument(array('param' => 'value'))->save();
$document = $this->collection
    ->createDocument()
    ->pushReference('related', $relatedDocument)
    ->save();

Get list of related documents:

<?php

$foundRelatedDocumentList = $document->getReferencedDocumentList('related');

List of references may be from different collections of same database. Specifying of database in reference not supported.

Storing document

Storing mapped object

If you have previously loaded and modified instance of \Sokil\Mongo\Document, just save it. Document will automatically be inserted or updated if it already stored.

<?php

// create new document and save
$document = $collection
    ->createDocument(['param' => 'value'])
    ->save();

// load existed document, modify and save
$document = $collection
    ->getDocument('23a4...')
    ->set('param', 'value')
    ->save();

Insert and update documents without ODM

If required quick insert of document without validating, events just insert it as array:

<?php
$collection->insert(['param' => 'value']);

To update existed documents, use:

<?php
$collection->update($expression, $data, $options);

Expression may be defined as array, \Sokil\Mongo\Expressin object or callable, which configure expression. Operator may be defined as array, \Sokil\Mongo\Operator object or callable which configure operator. Options is array of all allowed options, described in http://php.net/manual/ru/mongocollection.update.php.

For example:

<?php
$collection->update(
    function(\Sokil\Mongo\Expression $expression) {
        $expression->where('status', 'active');
    },
    function(\Sokil\Mongo\Operator $operator) {
        $operator->increment('counter');
    },
    array(
        'multiple' => true,
    )
);

Batch insert

To insert many documents at once with validation of inserted document:

<?php
$collection->batchInsert(array(
    array('i' => 1),
    array('i' => 2),
));

Also supported \MongoInsertBatch through interface:

<?php
$collection
    ->createBatchInsert()
    ->insert(array('i' => 1))
    ->insert(array('i' => 2))
    ->execute('majority');

Batch update

Making changes in few documents:

<?php

$collection->batchUpdate(
    function(\Sokil\Mongo\Expression $expression) {
        return $expression->where('field', 'value');
    },
    ['field' => 'new value']
);

To update all documents use:

<?php

$collection->batchUpdate([], array('field' => 'new value'));

Rename fields of documents:

<?php
$collection->batchUpdate(
    [],
    function(Operator $operator) {
        $operator->renameField('param', 'renamedParam');
    }
);

Also supported \MongoUpdateBatch through interface:

<?php
$collection
    ->createBatchUpdate()
    ->update(
        array('a' => 1),
        array('$set' => array('b' => 'updated1')),
        $multiple,
        $upsert
    )
    ->update(
        $collection->expression()->where('a', 2),
        $collection->operator()->set('b', 'updated2'),
        $multiple,
        $upsert
    )
    ->update(
        function(Expression $e) { $e->where('a', 3); },
        function(Operator $o) { $o->set('b', 'updated3'); },
        $multiple,
        $upsert
    )
    ->execute('majority');

Moving data between collections

To copy documents from one collection to another according to expression:

<?php
// to new collection of same database
$collection
    ->find()
    ->where('condition', 1)
    ->copyToCollection('newCollection');

// to new collection in new database
$collection
    ->find()
    ->where('condition', 1)
    ->copyToCollection('newCollection', 'newDatabase');

To move documents from one collection to another according to expression:

<?php
// to new collection of same database
$collection
    ->find()
    ->where('condition', 1)
    ->moveToCollection('newCollection');

// to new collection in new database
$collection
    ->find()
    ->where('condition', 1)
    ->moveToCollection('newCollection', 'newDatabase');

Important to note that there is no transactions so if error will occur during process, no changes will rollback.

Querying documents

Query Builder

To query documents, which satisfy some conditions you need to use query builder:

<?php
$cursor = $collection
    ->find()
    ->fields(['name', 'age'])
    ->where('name', 'Michael')
    ->whereGreater('age', 29)
    ->whereIn('interests', ['php', 'snowboard', 'traveling'])
    ->skip(20)
    ->limit(10)
    ->sort([
        'name'  => 1,
        'age'   => -1,
    ]);

All "where" conditions added with logical AND. To add condition with logical OR:

<?php
$cursor = $collection
    ->find()
    ->whereOr(
        $collection->expression()->where('field1', 50),
        $collection->expression()->where('field2', 50),
    );

Result of the query is iterator \Sokil\Mongo\Cursor, which you can then iterate:

<?php
foreach($cursor as $documentId => $document) {
    echo $document->get('name');
}

Or you can get result array:

<?php
$result = $cursor->findAll();

To get only one result:

<?php
$document = $cursor->findOne();

To get only one random result:

<?php
$document = $cursor->findRandom();

To get values from a single field in the result set of documents:

<?php
$columnValues = $cursor->pluck('some.field.name');

To map found documents:

<?php
$result = $collection->find()->map(function(Document $document) {
    return $document->param;
});

To filter found documents:

<?php
$result = $collection->find()->filter(function(Document $document) {
    return $document->param % 2;
});

To apply chain of functions to result, use ResultSet:

<?php
$collection->find()
    ->getResultSet()
    ->filter(function($doc) { return $doc->param % 2 })
    ->filter(function($doc) { return $doc->param > 6 })
    ->map(function($item) {
        $item->param = 'update' . $item->param;
        return $item;
    });

When iterating through cursor client retrieve some amount of documents from the server in one round trip. To define this numner of documents:

<?php

$cursor->setBatchSize(20);

Query timeouts

Client timeout defined to stop waiting for a response and throw a \MongoCursorTimeoutException after a set time. A timeout can be set at any time and will affect subsequent queries on the cursor, including fetching more results from the database.

$collection->find()->where('name', 'Michael')->setClientTimeout(4200);

Server-side timeout for a query specifies a cumulative time limit in milliseconds to be allowed by the server for processing operations on the cursor.

$collection->find()->where('name', 'Michael')->setServerTimeout(4200);

Distinct values

To get distinct values of field:

<?php
// return all distinct values
$values = $collection->getDistinct('country');

Values may be filtered by expression specified as array, callable or Expression object:

<?php
// by array
$collection->getDistinct('country', array('age' => array('$gte' => 25)));
// by object
$collection->getDistinct('country', $collection->expression()->whereGreater('age', 25));
// by callable
$collection->getDistinct('country', function($expression) { return $expression->whereGreater('age', 25); });

Extending Query Builder

For extending standart query builder class with custom condition methods you need to create expression class which extends \Sokil\Mongo\Expression:

<?php

// define expression
class UserExpression extends \Sokil\Mongo\Expression
{
    public function whereAgeGreaterThan($age)
    {
        $this->whereGreater('age', (int) $age);
    }
}

And then specify it in collection mapping:

<?php

$client->map([
    'myDb' => [
        'user' => [
            'class' => '\UserCollection',
            'expressionClass' => '\UserExpression',
        ],
    ],
]);

Also there is deprecated feature to override property Collection::$_queryExpressionClass:

<?php

// define expression in collection
class UserCollection extends \Sokil\Mongo\Collection
{
    protected $_queryExpressionClass = 'UserExpression';
}

Now new expression methods available in the query buiilder:

<?php
// use custom method for searching
$collection = $db->getCollection('user'); // instance of UserCollection
$queryBuilder = $collection->find(); // instance of UserExpression

// now methods available in query buider
$queryBuilder->whereAgeGreaterThan(18)->fetchRandom();

// since v.1.3.2 also supported query builder configuration through callable:
$collection
    ->find(function(UserExpression $e) {
        $e->whereAgeGreaterThan(18);
    })
    ->fetchRandom();

Identity Map

Imagine that you have two different query builders and they are both return same document. Identity map helps us to get same instance of object from different queries, so if we made changes to document from first query, that changes will be in document from second query:

<?php

$document1 = $collection->find()->whereGreater('age' > 18)->findOne();
$document2 = $collection->find()->where('gender', 'male')->findOne();

$document1->name = 'Mary';
echo $document2->name; // Mary

This two documents referenced same object. Collection by default store all requested documents to identity map. If we obtain document directly by id using Collection::getDocument() and document was previously loaded to identity map, it will be fetched from identity map without requesing database. Even document present in identity map, it can be fetched direcly from db by using Collection::getDocumentDirectly() with same syntax as Collection::getDocument().

If serial requests fetch same document, this document not replaced in identity mav, but content of this document will be renewed. So different requests works with same document stored in identity map.

If we know that documents never be reused, we can disable storing documents to identity map:

Document pool may be disabled or enabled in mapping. By default it is enabled:

<?php
$collection->map([
    'someDb' => [
        'someCollection', array(
            'documentPool' => false,
        ),
    ],
]);
<?php

$collection->disableDocumentPool();

To enable identity mapping:

<?php

$collection->enableDocumentPool();

To check if identity mapping enabled:

<?php

$collection->isDocumentPoolEnabled();

To clear pool identity map from previously stored documents:

<?php

$collection->clearDocumentPool();

To check if there are documents in map already:

<?php

$collection->isDocumentPoolEmpty();

If document already loaded, but it may be changed from another proces in db, then your copy is not fresh. You can manually refresh document state syncing it with db:

<?php

$document->refresh();

Comparing queries

If you want to cache your search results or want to compare two queries, you need some identifier which unambiguously identify query. You can use Cursor::getHash() for that reason. This hash uniquely identify just query parameners rather than result set of documents, because it calculated from all query parameters:

<?php

$queryBuilder = $this->collection
    ->find()
    ->field('_id')
    ->field('interests')
    ->sort(array(
        'age' => 1,
        'gender' => -1,
    ))
    ->limit(10, 20)
    ->whereAll('interests', ['php', 'snowboard']);

$hash = $queryBuilder->getHash(); // will return 508cc93b371c222c53ae90989d95caae

if($cache->has($hash)) {
    return $cache->get($hash);
}

$result = $queryBuilder->findAll();

$cache->set($hash, $result);


Geospatial queries

Before querying geospatial coordinates we need to create geospatial index and add some data.

Index 2dsphere available since MongoDB version 2.4 and may be created in few ways:

<?php
// creates index on location field
$collection->ensure2dSphereIndex('location');
// cerate compound index
$collection->ensureIndex(array(
    'location' => '2dsphere',
    'name'  => -1,
));

Geo data can be added as array in GeoJson format or using GeoJson objects of library GeoJson:

Add data as GeoJson object

<?php

$document->setGeometry(
    'location',
    new \GeoJson\Geometry\Point(array(30.523400000000038, 50.4501))
);

$document->setGeometry(
    'location',
    new \GeoJson\Geometry\Polygon(array(
        array(24.012228, 49.831485), // Lviv
        array(36.230376, 49.993499), // Harkiv
        array(34.174927, 45.035993), // Simferopol
        array(24.012228, 49.831485), // Lviv
    ))
);

Data may be set througn array:

<?php

// Point
$document->setPoint('location', 30.523400000000038, 50.4501);
// LineString
$document->setLineString('location', array(
    array(30.523400000000038, 50.4501),
    array(36.230376, 49.993499),
));
// Polygon
$document->setPolygon('location', array(
    array(
        array(24.012228, 49.831485), // Lviv
        array(36.230376, 49.993499), // Harkiv
        array(34.174927, 45.035993), // Simferopol
        array(24.012228, 49.831485), // Lviv
    ),
));
// MultiPoint
$document->setMultiPoint('location', array(
    array(24.012228, 49.831485), // Lviv
    array(36.230376, 49.993499), // Harkiv
    array(34.174927, 45.035993), // Simferopol
));
// MultiLineString
$document->setMultiLineString('location', array(
    // line string 1
    array(
        array(34.551416, 49.588264), // Poltava
        array(35.139561, 47.838796), // Zaporizhia
    ),
    // line string 2
    array(
        array(24.012228, 49.831485), // Lviv
        array(34.174927, 45.035993), // Simferopol
    )
));
// MultiPolygon
$document->setMultyPolygon('location', array(
    // polygon 1
    array(
        array(
            array(24.012228, 49.831485), // Lviv
            array(36.230376, 49.993499), // Harkiv
            array(34.174927, 45.035993), // Simferopol
            array(24.012228, 49.831485), // Lviv
        ),
    ),
    // polygon 2
    array(
        array(
            array(24.012228, 49.831485), // Lviv
            array(36.230376, 49.993499), // Harkiv
            array(34.174927, 45.035993), // Simferopol
            array(24.012228, 49.831485), // Lviv
        ),
    ),
));
// GeometryCollection
$document->setGeometryCollection('location', array(
    // point
    new \GeoJson\Geometry\Point(array(30.523400000000038, 50.4501)),
    // line string
    new \GeoJson\Geometry\LineString(array(
        array(30.523400000000038, 50.4501),
        array(24.012228, 49.831485),
        array(36.230376, 49.993499),
    )),
    // polygon
    new \GeoJson\Geometry\Polygon(array(
        // line ring 1
        array(
            array(24.012228, 49.831485), // Lviv
            array(36.230376, 49.993499), // Harkiv
            array(34.174927, 45.035993), // Simferopol
            array(24.012228, 49.831485), // Lviv
        ),
        // line ring 2
        array(
            array(34.551416, 49.588264), // Poltava
            array(32.049226, 49.431181), // Cherkasy
            array(35.139561, 47.838796), // Zaporizhia
            array(34.551416, 49.588264), // Poltava
        ),
    )),
));

Query documents near point on flat surface, defined by latitude 49.588264 and longitude 34.551416 and distance 1000 meters from this point:

<?php
$collection->find()->nearPoint('location', 34.551416, 49.588264, 1000);

This query require 2dsphere or 2d indexes.

Distance may be specified as array [minDistance, maxDistance]. This feature allowed for MongoDB version 2.6 and greater. If some value empty, only existed value applied.

<?php
// serch distance less 100 meters
$collection->find()->nearPoint('location', 34.551416, 49.588264, array(null, 1000));
// search distabce between 100 and 1000 meters
$collection->find()->nearPoint('location', 34.551416, 49.588264, array(100, 1000));
// search distabce greater than 1000 meters
$collection->find()->nearPoint('location', 34.551416, 49.588264, array(1000, null));

To search on spherical surface:

<?php
$collection->find()->nearPointSpherical('location', 34.551416, 49.588264, 1000);

To find geometries, which intersect specified:

<?php
$this->collection
    ->find()
    ->intersects('link', new \GeoJson\Geometry\LineString(array(
        array(30.5326905, 50.4020355),
        array(34.1092134, 44.946798),
    )))
    ->findOne();

To select documents with geospatial data that exists entirely within a specified shape:

<?php
$point = $this->collection
    ->find()
    ->within('point', new \GeoJson\Geometry\Polygon(array(
        array(
            array(24.0122356, 49.8326891), // Lviv
            array(24.717129, 48.9117731), // Ivano-Frankivsk
            array(34.1092134, 44.946798), // Simferopol
            array(34.5572385, 49.6020445), // Poltava
            array(24.0122356, 49.8326891), // Lviv
        )
    )))
    ->findOne();

Search documents within flat circle:

<?php
$this->collection
    ->find()
    ->withinCircle('point', 28.46963, 49.2347, 0.001)
    ->findOne();

Search document within spherical circle:

<?php
$point = $this->collection
    ->find()
    ->withinCircleSpherical('point', 28.46963, 49.2347, 0.001)
    ->findOne();

Search documents with points (stored as legacy coordinates) within box:

<?php
$point = $this->collection
    ->find()
    ->withinBox('point', array(0, 0), array(10, 10))
    ->findOne();

Search documents with points (stored as legacy coordinates) within polygon:

<?php
$point = $this->collection
    ->find()
    ->withinPolygon(
        'point',
        array(
            array(0, 0),
            array(0, 10),
            array(10, 10),
            array(10, 0),
        )
    )
    ->findOne();


Fulltext search

Before search field must be previously indexed as fulltext search field:

<?php

// one field
$collection->ensureFulltextIndex('somefield');

// couple of fields
$collection->ensureFulltextIndex(['somefield1', 'somefield2']);

Searching on fulltext field:

<?php

$collection->find()->whereText('string searched in all fulltext fields')->findAll();


Pagination

Query builder allows you to create pagination.

<?php
$paginator = $collection->find()->where('field', 'value')->paginate(3, 20);
$totalDocumentNumber = $paginator->getTotalRowsCount();
$totalPageNumber = $paginator->getTotalPagesCount();

// iterate through documents
foreach($paginator as $document) {
    echo $document->getId();
}


Persistence (Unit of Work)

Instead of saving and removing objects right now, we can queue this job and execute all changes at once. This may be done through well-known pattern Unit of Work. If installed PHP driver above v. 1.5.0 and version of MongoDB above, persistence will use MongoWriteBatch classes, which can execute all operations of same type and in same collection at once.

Lets create persistance manager

<?php
$persistence = $client->createPersistence();

Now we can add some documents to be saved or removed later

<?php
$persistence->persist($document1);
$persistence->persist($document2);

$persistence->remove($document3);
$persistence->remove($document4);

If later we decice do not save or remove document, we may detach it from persistence manager

<?php
$persistence->detach($document1);
$persistence->detach($document3);

Or we even may remove them all:

<?php
$persistence->clear();

Note that after detaching document from persistence manager, it's changes do not removed and document still may be saved directly or by adding to persistence manager.

If we decide to store changes to databasae we may flush this changes:

<?php
$persistence->flush();

Note that persisted documents do not deleted from persistence manager after flush, but removed will be deleted.

Deleting collections and documents

Deleting of collection:

<?php
$collection->delete();

Deleting of document:

<?php
$document->delete();

Deleting of few documents by expression:

<?php

$collection->batchDelete($collection->expression()->where('param', 'value'));
// deprecated since 1.13
$collection->deleteDocuments($collection->expression()->where('param', 'value'));

Also supported \MongoDeleteBatch through interface:

<?php
$batch = $collection->createBatchDelete();
$batch
    ->delete(array('a' => 2))
    ->delete($collection->expression()->where('a', 4))
    ->delete(function(Expression $e) { $e->where('a', 6); })
    ->execute();


Aggregation framework

Create aggregator:

<?php
$aggregator = $collection->createAggregator();

Than you need to configure aggregator by pipelines.

<?php
// through array
$aggregator->match(array(
    'field' => 'value'
));
// through callable
$aggregator->match(function($expression) {
    $expression->whereLess('date', new \MongoDate);
});

To get results of aggregation after configuring pipelines:

<?php
/**
 * @var array list of aggregation results
 */
$result = $aggregator->aggregate();
// or
$result = $collection->aggregate($aggregator);

You can execute aggregation without previously created aggregator:

<?php
// by array
$collection->aggregate(array(
    array(
        '$match' => array(
            'field' => 'value',
        ),
    ),
));
// or callable
$collection->aggregate(function($aggregator) {
    $aggregator->match(function($expression) {
        $expression->whereLess('date', new \MongoDate);
    });
});

Options

Available aggregation options may be found at https://docs.mongodb.org/manual/reference/command/aggregate/#dbcmd.aggregate.

Options may be passed as argument of aggregate method:

<?php

// as argument of Pipeline::aggregate
$collection->createAggregator()->match()->group()->aggregate($options);

// as argument of Collection::aggregate
$collection->aggregate($pipelines, $options);

// as calling of Pipeline methods
$collection
    ->createAggregator()
    ->explain()
    ->allowDiskUse()
    ->setBatchSize(100);

Debug

If client in debug mode and logger configured, pipelines will be logged. There is ability to get explanation of aggregation:

<?php

// set explain option
$collection->aggregate($pipelines, ['explain' => true]);

// or configure pipeline
$collection->createAggregator()->match(...)->group(...)->explain()->aggregate();

Aggregation cursor

Collection::aggregate return array as result, but also iterator may be optained: Read more at http://php.net/manual/ru/mongocollection.aggregatecursor.php.

<?php

// set as argument
$asCursor = true;
$collection->aggregate($pipelines, $options, $asCursor);

// or call method
$cursor = $collection->createAggregator()->match()->group()->aggregateCursor();


Events

Event support based on Symfony's Event Dispatcher component. You can attach and trigger any event you want, but there are some already defined events:

Event name Description
afterConstruct Already after construct executed
beforeValidate Before document validation
afterValidate After document validation
validateError After document validation when document is invalid
beforeInsert Before document will insert to collection
afterInsert After successfull insert
beforeUpdate Before document will be updated
afterUpdate After successfull update of document
beforeSave Before insert or update of document
afterSave After insert or update of document
beforeDelete Before delete of document
afterDelete After delete of document

Event listener is a function that calls when event triggered:

<?php
$listener = function(
    \Sokil\Mongo\Event $event, // instance of event
    string $eventName, // event name
    \Symfony\Component\EventDispatcher\EventDispatcher $eventDispatcher // instance of dispatcher
) {}

Event listener may be attached by method Document::attachEvent():

<?php
$document->attachEvent('myOwnEvent', $listener, $priority);

It also may be attached through helper methods:

<?php
$document->onMyOwnEvent($listener, $priority);
// which is equals to
$this->attachEvent('myOwnEvent', $listener, $priority);

Event may be attached in runtime or in Document class by override Document::beforeConstruct() method:

<?php
class CustomDocument extends \Sokil\Mongo\Document
{
    public function beforeConstruct()
    {
        $this->onBeforeSave(function() {
            $this->set('date' => new \MongoDate);
        });
    }
}

Event may be triggered to call all attached event listeners:

<?php
$this->triggerEvent('myOwnEvent');

You can create your own event class, which extends `\Sokil\Mongo\Event' and pass it to listeners. This allows you to pass some data to listener:

<?php
// create class
class OwnEvent extends \Sokil\Mongo\Event {
    public $status;
}

// define listener
$document->attachEvent('someEvent', function(\OwnEvent $event) {
    echo $event->status;
});

// configure event
$event = new \OwnEvent;
$event->status = 'ok';

// trigger event
$this->triggerEvent('someEvent', $event);

To cancel operation execution on some condition use event handling cancel:

<?php
$document->onBeforeSave(function(\Sokil\Mongo\Event $event) {
    if($this->get('field') === 42) {
        $event->cancel();
    }
})
->save();


Behaviors

Behavior is a posibility to extend functionality of document object and reuse code among documents of different class.

Behavior is a class extended from \Sokil\Mongo\Behavior. Any public method may be accessed through document, where behavior is attached.

<?php
class SomeBehavior extends \Sokil\Mongo\Behavior
{
    public function return42()
    {
        return 42;
    }
}

To get instance of object, to which behavior is attached, call Behavior::getOwner() method:

<?php
class SomeBehavior extends \Sokil\Mongo\Behavior
{
    public function getOwnerParam($selector)
    {
        return $this->getOwner()->get($selector);
    }
}

You can add behavior in document class:

<?php
class CustomDocument extends \Sokil\Mongo\Document
{
    public function behaviors()
    {
        return [
            '42behavior' => '\SomeBehavior',
        ];
    }
}

You can attach behavior in runtime too:

<?php
// single behavior
$document->attachBehavior('42behavior', '\SomeBehavior');
// set of behaviors
$document->attachBehaviors([
    '42behavior' => '\SomeBehavior',
]);

Behaviors may be defined as fully qualified class names, arrays, or Behavior instances:

<?php
// class name
$document->attachBehavior('42behavior', '\SomeBehavior');

// array with parameters
$document->attachBehavior('42behavior', [
    'class'     => '\SomeBehavior',
    'param1'    => 1,
    'param2'    => 2,
]);

// Behavior instance
$document->attachBehavior('42behavior', new \SomeBehavior([
    'param1'    => 1,
    'param2'    => 2,
]);

Then you can call any methods of behaviors. This methods searches in order of atraching behaviors:

<?php
echo $document->return42();


Relations

You can define relations between different documents, which helps you to load related documents.

To define relation to other document you need to override Document::relations() method and return array of relations in format [relationName => [relationType, targetCollection, reference], ...].

Also you can define relations in mapping:

<?php

$collection->map([
    'someDb' => [
        'someCollection', array(
            'relations'     => array(
                'someRelation'   => array(self::RELATION_HAS_ONE, 'profile', 'user_id'),
            ),
        ),
    ],
]);

If relation specified both in mapping and document class, then mapping relation merged into document's relations, so mapping relations has more priority.

One-to-one relation

We have to classes User and Profile. User has one profile, and profile belongs to User.

<?php
class User extends \Sokil\Mongo\Document
{
    protected $schema = [
        'email'     => null,
        'password'  => null,
    ];

    public function relations()
    {
        return [
            'profileRelation' => [self::RELATION_HAS_ONE, 'profile', 'user_id'],
        ];
    }
}

class Profile extends \Sokil\Mongo\Document
{
    protected $schema = [
        'name' => [
            'last'  => null,
            'first' => null,
        ],
        'age'   => null,
    ];

    public function relations()
    {
        return [
            'userRelation' => [self::RELATION_BELONGS, 'user', 'user_id'],
        ];
    }
}

Now we can lazy load related documnts just calling relation name:

<?php
$user = $userColletion->getDocument('234...');
echo $user->profileRelation->get('age');

$profile = $profileCollection->getDocument('234...');
echo $pfofile->userRelation->get('email');

One-to-many relation

One-to-many relation helps you to load all related documents. Class User has few posts of class Post:

<?php
class User extends \Sokil\Mongo\Document
{
    protected $schema = [
        'email'     => null,
        'password'  => null,
    ];

    public function relations()
    {
        return [
            'postsRelation' => [self::RELATION_HAS_MANY, 'posts', 'user_id'],
        ];
    }
}

class Posts extends \Sokil\Mongo\Document
{
    protected $schema = [
        'user_id' => null,
        'message'   => null,
    ];

    public function relations()
    {
        return [
            'userRelation' => [self::RELATION_BELONGS, 'user', 'user_id'],
        ];
    }

    public function getMessage()
    {
        return $this->get('message');
    }
}

Now you can load related posts of document:

<?php
foreach($user->postsRelation as $post) {
    echo $post->getMessage();
}

Many-to-many relation

Many-to-many relation in relational databases uses intermediate table with stored ids of related rows from both tables. In mongo this table equivalent embeds to one of two related documents. Element of relation definition at position 3 must be set to true in this document.

<?php

// this document contains field 'driver_id' where array of ids stored
class CarDocument extends \Sokil\Mongo\Document
{
    protected $schema = [
        'brand' => null,
    ];

    public function relations()
    {
        return array(
            'drivers'   => array(self::RELATION_MANY_MANY, 'drivers', 'driver_id', true),
        );
    }
}

class DriverDocument extends \Sokil\Mongo\Document
{
    protected $schema = [
        'name' => null,
    ];

    public function relations()
    {
        return array(
            'cars'    => array(self::RELATION_MANY_MANY, 'cars', 'driver_id'),
        );
    }
}

Now you can load related documents:

<?php
foreach($car->drivers as $driver) {
    echo $driver->name;
}

Add relation

There is helper to add related document, if you don't want modify relation field directly:

<?php
$car->addRelation('drivers', $driver);

This helper automatically resolves collection and field where to store relation data.

Remove relation

There is helper to remove related document, if you don't want modify relation field directly:

<?php
$car->removeRelation('drivers', $driver);

This helper automatically resolves collection and field where to remove relation data. If relation type is HAS_MANY or BELONS_TO, second parameter wich defined related object may be omitted.

Concurency

Optimistic locking

To enable optimistic locking, specify lock mode in mapping:

use Sokil\Mongo\Collection\Definition;
use Sokil\Mongo\Document\OptimisticLockFailureException;

$client->map([
    'db' => [
        'col' => [
            'lock' => Definition::LOCK_OPTIMISTIC,
        ],
    ]
]);

Now when some process try to update already updated document, exception \Sokil\Mongo\Document\OptimisticLockFailureException will be thrown.

Read preferences

Read preference describes how MongoDB clients route read operations to members of a replica set. You can configure read preferences at any level:

<?php
// in constructor
$client = new Client($dsn, array(
    'readPreference' => 'nearest',
));

// by passing to \Sokil\Mongo\Client instance
$client->readNearest();

// by passing to database
$database = $client->getDatabase('databaseName')->readPrimaryOnly();

// by passing to collection
$collection = $database->getCollection('collectionName')->readSecondaryOnly();


Write concern

Write concern describes the guarantee that MongoDB provides when reporting on the success of a write operation. You can configure write concern at any level:

<?php

// by passing to \Sokil\Mongo\Client instance
$client->setMajorityWriteConcern(10000);

// by passing to database
$database = $client->getDatabase('databaseName')->setMajorityWriteConcern(10000);

// by passing to collection
$collection = $database->getCollection('collectionName')->setWriteConcern(4, 1000);


Capped collections

To use capped collection you need previously to create it:

<?php
$numOfElements = 10;
$sizeOfCollection = 10*1024;
$collection = $database->createCappedCollection('capped_col_name', $numOfElements, $sizeOfCollection);

Now you can add only 10 documents to collection. All old documents will ve rewritted ny new elements.

Executing commands

Command is universal way to do anything with mongo. Let's get stats of collection:

<?php
$collection = $database->createCappedCollection('capped_col_name', $numOfElements, $sizeOfCollection);
$stats = $database->executeCommand(['collstat' => 'capped_col_name']);

Result in $stats:

array(13) {
  'ns' =>  string(29) "test.capped_col_name"
  'count' =>  int(0)
  'size' =>  int(0)
  'storageSize' =>  int(8192)
  'numExtents' =>  int(1)
  'nindexes' =>  int(1)
  'lastExtentSize' =>  int(8192)
  'paddingFactor' =>  double(1)
  'systemFlags' =>  int(1)
  'userFlags' =>  int(1)
  'totalIndexSize' =>  int(8176)
  'indexSizes' =>  array(1) {
    '_id_' =>    int(8176)
  }
  'ok' =>  double(1)
}


Queue

Queue gives functionality to send messages from one process and get them in another process. Messages can be send to different channels.

Sending message to queue with default priority:

<?php
$queue = $database->getQueue('channel_name');
$queue->enqueue('world');
$queue->enqueue(['param' => 'value']);

Send message with priority

<?php
$queue->enqueue('hello', 10);

Reading messages from channel:

<?php
$queue = $database->getQueue('channel_name');
echo $queue->dequeue(); // hello
echo $queue->dequeue(); // world
echo $queue->dequeue()->get('param'); // value

Number of messages in queue

<?php
$queue = $database->getQueue('channel_name');
echo count($queue);


Migrations

Migrations allows you easily change schema and data versions. This functionality implemented in packet https://github.com/sokil/php-mongo-migrator and can be installed through composer:

composer require sokil/php-mongo-migrator


GridFS

GridFS allows you to store binary data in mongo database. Details at http://docs.mongodb.org/manual/core/gridfs/.

First get instance of GridFS. You can specify prefix for partitioning filesystem:

<?php
$imagesFS = $database->getGridFS('image');
$cssFS = $database->getGridFS('css');

Now you can store file, located on disk:

<?php
$id = $imagesFS->storeFile('/home/sokil/images/flower.jpg');

You can store file from binary data:

<?php
$id1 = $imagesFS->storeBytes('some text content');
$id2 = $imagesFS->storeBytes(file_get_contents('/home/sokil/images/flower.jpg'));

You are able to store some metadata with every file:

<?php
$id1 = $imagesFS->storeFile('/home/sokil/images/flower.jpg', [
    'category'  => 'flower',
    'tags'      => ['flower', 'static', 'page'],
]);

$id2 = $imagesFS->storeBytes('some text content', [
    'category' => 'books',
]);

Get file by id:

<?php
$imagesFS->getFileById('6b5a4f53...42ha54e');

Find file by metadata:

<?php
foreach($imagesFS->find()->where('category', 'books') as $file) {
    echo $file->getFilename();
}

Deleting files by id:

<?php
$imagesFS->deleteFileById('6b5a4f53...42ha54e');
Reading of file content
<?php
// dump binary data to file
$file->dump($filename);

// get binary data
$file->getBytes();

// get resource
$file->getResource();

If you want to use your own GridFSFile classes, you need to define mapping, as it does with collections:

<?php
// define mapping of prefix to GridFS class
$database->map([
    'GridFSPrefix' => '\GridFSClass',
]);

// define GridFSFile class
class GridFSClass extends \Sokil\Mongo\GridFS
{
    public function getFileClassName(\MongoGridFSFile $fileData = null)
    {
        return '\GridFSFileClass';
    }
}

// define file class
class GridFSFileClass extends \Sokil\Mongo\GridFSFile
{
    public function getMetaParam()
    {
        return $this->get('meta.param');
    }
}

// get file as instance of class \GridFSFileClass
$database->getGridFS('GridFSPrefix')->getFileById($id)->getMetaParam();


Versioning

Versioninbg allows you to have history of all document changes. To enable versioning of documents in collection, you can set protected property Collection::$versioning to true, call Collection::enableVersioning() method or define versioning in mapping.

<?php
// througn protected property
class MyCollection extends \Sokil\Mongo\Collection
{
    protected $versioning = true;
}

// througn method
$collection = $database->getCollection('my');
$collection->enableVersioning();

// through mapping
$database->map('someCollectionName', [
    'versioning' => true,
]);

To check if documents in collections is versioned call:

<?php
if($collection->isVersioningEnabled()) {}

Revision is an instance of class \Sokil\Mongo\Revision and inherits \Sokil\Mongo\Document, so any methods of document may be applied to revision. Revisions may be accessed:

<?php
// get all revisions
$document->getRevisions();

// get slice of revisions
$limit = 10;
$offset = 15;
$document->getRevisions($limit, $offset);

To get one revision by id use:

<?php
$revision = $document->getRevision($revisionKey);

To get count of revisions:

<?php
$count = $document->getRevisionsCount();

To clear all revisions:

<?php
$document->clearRevisions();

Revisions stored in separate collection, named {COLLECTION_NAME}.revisions To obtain original document of collection {COLLECTION_NAME} from revision, which is document of collection {COLLECTION_NAME}.revisions, use Revision::getDocument() method:

<?php
$document->getRevision($revisionKey)->getDocument();

Properties of document from revision may be accessed directly:

echo $document->property;
echo $document->getRevision($revisionKey)->property;

Also date of creating revison may be obtained from document:

<?php
// return timestamp
echo $document->getRevision($revisionKey)->getDate();
// return formatted date string
echo $document->getRevision($revisionKey)->getDate('d.m.Y H:i:s');


Indexes

Create index with custom options (see options in http://php.net/manual/en/mongocollection.ensureindex.php):

<?php
$collection->ensureIndex('field', [ 'unique' => true ]);

Create unique index:

<?php
$collection->ensureUniqueIndex('field');

Create sparse index (see http://docs.mongodb.org/manual/core/index-sparse/ for details about sparse indexes):

<?php
$collection->ensureSparseIndex('field');

Create TTL index (see http://docs.mongodb.org/manual/tutorial/expire-data/ for details about TTL indexes):

<?php
$collection->ensureTTLIndex('field');

You may define field as array where key is field name and value is direction:

<?php
$collection->ensureIndex(['field' => 1]);

Also you may define compound indexes:

<?php
$collection->ensureIndex(['field1' => 1, 'field2' => -1]);

You may define all collection indexes in property Collection::$_index as array, where each item is an index definition. Every index definition must contain key keys with list of fields and orders, and optional options, as described in http://php.net/manual/en/mongocollection.createindex.php.

<?php
class MyCollection extends \Sokil\Mongo\Collection
{
    protected $_index = array(
        array(
            'keys' => array('field1' => 1, 'field2' => -1),
            'unique' => true
        ),
    );
}

Then you must create this indexes by call of Collection::initIndexes():

<?php
$collection = $database->getCollection('myCollection')->initIndexes();

You may use Mongo Migrator package to ensure indexes in collections from migration scripts.

Query optimiser automatically choose which index to use, but you can manuallty define it:

<?php
$collection->find()->where('field', 1)->hind(array('field' => 1));


Caching and documents with TTL

If you want to get collection where documents will expire after some specified time, just add special index to this collection.

<?php
$collection->ensureTTLIndex('createDate', 1000);

You can do this also in migration script, using Mongo Migrator. For details see related documentation.

You also can use \Sokil\Mongo\Cache class, which already implement this functionality and compatible with PSR-16 interface.

<?php

// Get cache instance
$cache = $database->getCache('some_namespace');

Namespace is a name of collection to be created in database.

Before use cache must be initialised by calling method Cache:init():

<?php
$cahce->init();

This operation creates index with expireAfterSecond key in collection some_namespace.

This operation may be done in some console command or migration script, e.g. by using migration tool sokil/php-mongo-migrator, or you can create manually in mongo console:

db.some_namespace.ensureIndex('e', {expireAfterSeconds: 0});

Now you can store new value with:

<?php
// this store value for 10 seconds
// expiration defined relatively to current time
$cache->set('key', 'value', 10);

You can define value which never expired and must be deleted manually:

<?php
$cache->set('key', 'value', null);

You can define some tags defined with key:

<?php
$cache->set('key', 'value', 10, ['php', 'c', 'java']);

To get value

<?php
$value = $cache->get('key');

To delete cached value by key:

<?php
$cache->delete('key');

Delete few values by tags:

<?php
// delete all values with tag 'php'
$cache->deleteMatchingTag('php');
// delete all values without tag 'php'
$cache->deleteNotMatchingTag('php');
// delete all values with tags 'php' and 'java'
$cache->deleteMatchingAllTags(['php', 'java']);
// delete all values which don't have tags 'php' and 'java'
$cache->deleteMatchingNoneOfTags(['php', 'java']);
// Document deletes if it contains any of passed tags
$cache->deleteMatchingAnyTag(['php', 'elephant']);
// Document deletes if it contains any of passed tags
$cache->deleteNotMatchingAnyTag(['php', 'elephant']);


Debugging

In debug mode client may log some activity to pre-configured logger or show extended errors.

<?php

// start debugging
$client->debug();

// stop debugging
$client->debug(false);

// check debug state
$client->isDebugEnabled();

Logging

Library suports logging of queries. To configure logging, you need to pass logger object to instance of \Sokil\Mongo\Client and enable debug of client. Logger must implement \Psr\Log\LoggerInterface due to PSR-3:

<?php
$client = new Client($dsn);
$client->setLogger($logger);

Profiling

More details about profiling at Analyze Performance of Database Operations profiler data stores to system.profile collection, which you can query through query builder:

<?php

$qb = $database
    ->findProfilerRows()
    ->where('ns', 'social.users')
    ->where('op', 'update');

Structure of document described in article Database Profiler Output

There is three levels of profiling, described in article Profile command. Switching between then may be done by calling methods:

<?php

// disable profiles
$database->disableProfiler();

// profile slow queries slower than 100 milliseconds
$database->profileSlowQueries(100);

// profile all queries
$database->profileAllQueries();

To get current level of profiling, call:

<?php
$params = $database->getProfilerParams();
echo $params['was'];
echo $params['slowms'];

// or directly
$level = $database->getProfilerLevel();
$slowms = $database->getProfilerSlowMs();


Unit tests

Build Status Coverage Status Scrutinizer Code Quality Code Climate SensioLabsInsight

Local PHPUnit tests

To start unit tests, run:

./vendor/bin/phpunit -c tests/phpunit.xml tests

Docker PHPUnit tests

Also available Docker containers. They start with xdebug enabled, so you can sign in to any container and debug code there.

To start tests on all supported PHP and MongoDB versions, run

./run-docker-tests.sh

To run test on concrete platforms, specify them:

./run-docker-tests.sh -p 56 -p 70 -m 30 -m 32

To run concrete test, pass it:

./run-docker-tests.sh -t DocumentTest.php

To run concrete method of test, pass it:

./run-docker-tests.sh -t DocumentTest.php -f ::testElemMatch

Tests may be found at ./share/phpunit dir after finishing tests.

Contributing

Pull requests, bug reports and feature requests is welcome. Please see CONTRIBUTING for details.

Change log

Please see CHANGELOG for more information on what has changed recently.

License

The MIT License (MIT). Please see License File for more information.

Comments
  • Relations - document object

    Relations - document object

    I don't know if this a problem but i cant get this right.

    Im doing a token collection "RELATION_BELONGS to UserCollection" but the object i'm getting back "$token->userRelations" returns \Sokil\Mongo\Document Object and not \Models\UserDocument Object So this results that i only can get _data but not any relation or relay the UserDocument object. "resolvedRelationIds" returns of course from Document Object and thats logic because it dosen't go inside the document object with relation().

    opened by bobbygrossmann 13
  • Use PHPUnit\Framework\TestCase instead of PHPUnit_Framework_TestCase

    Use PHPUnit\Framework\TestCase instead of PHPUnit_Framework_TestCase

    I use the PHPUnit\Framework\TestCase notation instead of PHPUnit_Framework_TestCase while extending our TestCases. This will help us migrate to PHPUnit 6, that no longer support snake case class names.

    I just need to bump PHPUnit version to >=4.8.35, that support this namespace.

    opened by carusogabriel 9
  • DBRefs support

    DBRefs support

    opened by ramzes13 8
  • Support Phongo (pecl/mongodb)

    Support Phongo (pecl/mongodb)

    As mentioned here and here, starting with PHP7, due to be released on November 12th the old pecl/mongo extension will not be supported. It's replacement will likely be the pecl/mongodb driver, codenamed Phongo.

    Seeing as PHP7 is dramatically faster than PHP5(.6), I think supporting the new driver as soon as possible would be a good idea. Not sure if it's better to create two versions/branches or to abstract all the driver API calls somewhere. In any case, the "php-mongo" API shouldn't need to change.

    If I have any spare time, I might start with implementation myself.

    enhancement 
    opened by sraka1 8
  • Storing embedded documents

    Storing embedded documents

    validation and setting embedded documents as Structure instances

    • [ ] check old _data style
    • [ ] Subdocument is a separate class with own getters and setters, extended from Structure or maybe new class Subdocument
    • [ ] subdocument may be assigned to key of document or other subdocument
    • [ ] subdocument data may be validated in its class
    • [ ] subdocument may be get from document if mapping of field to document specified. Used Structure::getObject and Structure::getObjectList
    • [ ] Subdocument transparently stored to db
    enhancement 
    opened by sokil 8
  • Wrong argument type passed to Structure::_mergeUnmodified()

    Wrong argument type passed to Structure::_mergeUnmodified()

    Argument 1 passed to Sokil\Mongo\Structure::_mergeUnmodified() must be of the type array, string given.
    
    Called in /fakepath/vendor/sokil/php-mongo/src/Structure.php on line 345 and defined...
    

    So the offending line is https://github.com/sokil/php-mongo/blob/master/src/Structure.php#L345 Don't know why you can just assume that $target[$key] will be an array? Seems like a bug to me.

    Using latest version.

    I'm testing with quite a big bunch of data, will try to isolate a specific example, so that you can easily reproduce it. Might even make a patch myself, if I figure out what's wrong.

    Any help would be greatly appreciated.

    opened by sraka1 7
  • support empty plain object as value

    support empty plain object as value

    more information you can find here https://jira.mongodb.org/browse/PHP-172

    sometimes you need save empty plain object {} as value

    $document->set('myValue', new stdClass);
    

    result:

    {
      myValue: {}
    }
    

    but now it always saved as ↓, because value casts to array

    {
      myValue: []
    }
    
    opened by svsool 6
  • Identity Map Document pool not work

    Identity Map Document pool not work

    When I make two selects, with same mongoId, with document pool enabled, I expect that one call to mongo server is made.

    $carCollection = $client->getCollection('Car');
    $car1 = $carCollection->find()->where('_id', new \MongoId("52388891b3d55c1181b9548f"))->current();
    $car2 = $carCollection->find()->where('_id', new \MongoId("52388891b3d55c1181b9548f"))->current();
    

    At the moment 2 queries are made to db

     2016-07-21 12:06:58] performance.DEBUG: Sokil\Mongo\Cursor: {"collection":"Car","query":{"_id":{"$id":"52388891b3d55c1181b9548f"}},"project":[],"sort":[]} [] []
     2016-07-21 12:06:58] performance.DEBUG: Sokil\Mongo\Cursor: {"collection":"Car","query":{"_id":{"$id":"52388891b3d55c1181b9548f"}},"project":[],"sort":[]} [] []
    
    opened by ramzes13 6
  • for manual document upserting

    for manual document upserting

    $sMongoID = md5('555' . 'www.apple.com');

    $doc = new Document($collection); $doc->setId($sMongoID); $doc->set('manufacturer', 'apple.com'); $doc->set('name', 'iPad'); $doc->set('cap', '32GB');

    // document created $doc->save();

    unset($doc);

    $sMongoID = md5('555' . 'www.apple.com');

    $doc = new Document($collection); // creating document wit SAME id $doc->setId($sMongoID); // set color $doc->set('color','white');

    $doc->save() ; // it will update document, set color

    opened by horrower 6
  • many-to-many

    many-to-many

    • mark field of source entity as list of id's of external entity
    • describe relation to source entity in external entity
    • implement removing relations is source entity if external entity deleted

    entityACollection: {_id: 'entityA1', 'entityBRel': ['entityB1', 'entityB2']} {_id: 'entityA2', 'entityBRel': ['entityB1', 'entityB3']}

    entityBCollection: {_id: 'entityB1'} {_id: 'entityB2'} {_id: 'entityB3'}

    $entityA1->someEntityBRelField gets list of all related entityB documents $entityB1->someEntityARelField gets list of all related entityA documents

    $entityB1->delete() will delete all links to it from entityACollection.entityBRel field

    Algo of how to add or remove field

    opened by sokil 6
  • Performing Projection  to return matched embedded documents

    Performing Projection to return matched embedded documents

    I am having trouble in returning only matched embedded document. I am new to ODM concept and following is a document structure that is inserted in collection called Project:

    { 
        "_id" : ObjectId("59f889e46803fa3713454b5d"), 
        "projectName" : "usecase-updated", 
        "classes" : [
            {
                "_id" : ObjectId("59f9d7776803faea30b895dd"), 
                "className" : "OLA"
            }, 
            {
                "_id" : ObjectId("59f9d8ad6803fa4012b895df"), 
                "className" : "HELP"
            }, 
            {
                "_id" : ObjectId("59f9d9086803fa4112b895de"), 
                "className" : "DOC"
            }, 
            {
                "_id" : ObjectId("59f9d9186803fa4212b895de"), 
                "className" : "INVOC"
            }
        ]
    }
    

    Now lets suppose that fromProject collection, i wanted to return a Project with specific _id and within that selected Project document, i wanted to get only the matched embedded Classes documents (with a given _id). I tried creating this query like this:

    $collection = $PHPMongoDBInstance->getCollection("Project"); //get the Project Collection
    $cursor = $collection->findAsArray()->whereAnd(
                $collection->expression()->where('_id', new MongoId("59f889e46803fa3713454b5d")),
                $collection->expression()->whereElemMatch("classes", $collection->expression()->where("_id", 
                new MongoId("59f9d7776803faea30b895dd"))));
    

    I was expecting the result would be something like this:

    { 
       "_id" : ObjectId("59f889e46803fa3713454b5d"), 
        "projectName" : "usecase-updated", 
        "classes" : [
              {
                "_id" : ObjectId("59f9d7776803faea30b895dd"), 
                "className" : "OLA"
              }
         ]
    }
    

    But its not like this. Thing is i am able to achieve the desired field level projection with native collection instance of mongo driver as follows:

    $result = $collection->getMongoCollection("Project")
                                    ->findOne(array("_id" => new MongoId("59f889e46803fa3713454b5d")), 
                                                     array("classes" => array('$elemMatch' => array( "_id" => new MongoId("59f9d7776803faea30b895dd")))));
    

    So i wonder if we have similar thing in the library where i can pass the fields of the results to return within the chaining of getCollection method.

    enhancement 
    opened by fahimmahmoodmir 5
  • "Undefined index: ok" when i try to aggregate

    These error cause when i try to run following code:

    $collection = $this->getClient()->getDatabase($this->db)->getCollection('messages');
    $pipeline = $collection->createAggregator()->setBatchSize(100)->group(array('_id' => '$channelId', 'sum' => array('$sum' => 1)));
    $result = $collection->aggregate($pipeline, array(), false);
    

    I think that error on 1056 line in Sokil\Mongo\Collection.php:

    if ($status['ok'] != 1) {
        throw new Exception('Aggregate error: ' . $status['errmsg']);
    }
    

    If i fetch data as cursor (pass true in third aggregate function parameter) - works fine.

    bug 
    opened by SarasovMatvey 0
Releases(1.23.1)
  • 1.23.1(Jul 8, 2019)

  • 1.23.0(Nov 4, 2018)

  • 1.22.4(Dec 5, 2017)

  • 1.22.3(Nov 22, 2017)

  • 1.22.2(Nov 10, 2017)

    • Add $elemMatch to projection argument of Cursor
    • Cursor methods findOne, findAll, findRandom deprecated, use one, all and random respectively;
    • Allow pass filter to docker tests
    Source code(tar.gz)
    Source code(zip)
  • 1.22.1(Nov 2, 2017)

  • 1.22(Aug 12, 2017)

    • Cache now compatible with PSR-16;
    • Cache setters now return bool instead of exceptions to be compatible with PSR-16;
    • Cache::setNeverExpired and Cache::setDueDate now deprecated. Use Cache::set instead;
    Source code(tar.gz)
    Source code(zip)
  • 1.21.5(Jun 20, 2017)

  • 1.21.4(Jun 15, 2017)

  • 1.21.2(Jun 14, 2017)

  • 1.21.1(Mar 24, 2017)

  • 1.21(Feb 9, 2017)

    • Document::beforeConstruct moved to Structure::beforeConstruct so embedded documents may configure some logic there #142
    • Collection::batchDelete() now has required argument
    • Now may be configured batch limit in Cursor::copyToCollection and Cursor::moveToCollection
    • Methods of \Iterator interface currently not recommended to use directly in Cursor and Paginator. But if used, now rewind MUST be calls before current.
    • Remove debug logger calls
    Source code(tar.gz)
    Source code(zip)
  • 1.20(Jan 16, 2017)

    • Implemented support of new ext-mongodb and, as a result, PHP7 and HHVM through compatibility layer "alcaeus/mongo-php-adapter", which implement API from old ext-mongo extension;
    • Cursor::findOne() throws internal CursorException exception instead of related to mongo extension. Exception from extension may be obtained from internal exception;
    • Document::save() throws internal WriteException exception instead of related to mongo extension. Exception from extension may be obtained from internal exception;
    • Docker tests now check PHP 7 code
    • Structure::apply() now protected
    Source code(tar.gz)
    Source code(zip)
  • 1.19.2(Dec 18, 2016)

  • 1.19.1(Oct 7, 2016)

  • 1.19(Sep 14, 2016)

    • Configure document pool status in collection's mapping;
    • Collection::_mongoCollection is deprecated. Use Collection::getMongoCollection() instead;
    • Collection::ensureIndex() is deprecated, use Collection::createIndex();
    • Cursor::toArray() removed, use Cursor::getMongoQuery();
    • Document::belongsToCollection() removed, use Collection::hasDocument();
    • Document::FIELD_TYPE_* constants removed, use FieldType enum
    • Collection::_database removed, use Collection::getDatabase() instead;
    Source code(tar.gz)
    Source code(zip)
  • 1.18.2(Sep 13, 2016)

  • 1.18.1(Aug 18, 2016)

  • 1.17(Aug 16, 2016)

  • 1.16.1(Jun 27, 2016)

  • 1.16(Jun 23, 2016)

    • Add validation of structure, set as embedded document
    • \Sokil\Mongo\Structure\Arrayable moves to \Sokil\Mongo\ArrayableInterface
    • Structure::$_modifiedFields and Structure::$_originalData set private
    • Document::_data is not deprecated, and replaced with propected property Document::schema.
    • Documents not allowed to be cloned
    • Removed 'validator' suffix from names of validation errors in array of validation errors Document::getErrors()
    Source code(tar.gz)
    Source code(zip)
  • 1.15.2(Apr 27, 2016)

  • 1.15.1(Mar 10, 2016)

  • 1.15(Mar 1, 2016)

    • Removed Collection::createPipeline(). Use Collection::createAggregator;
    • Aggregator options may be passed as arguments of Collection::aggregate($pipelines, $options) or configured through methods of Pipeline;
    • Experimental feature: aggregation returns Cursor, if third parameter passed Collection::aggregate($pipelines, $options, $asCursor);
    • Collection::explainAggregate() is deprecated. Use Pipeline::explain();
    • Added debug mode to Client;
    Source code(tar.gz)
    Source code(zip)
  • 1.14(Jan 30, 2016)

  • 1.13.9(Jan 13, 2016)

  • 1.13.8(Jan 3, 2016)

  • 1.13.7(Sep 28, 2015)

  • 1.13.6(Aug 25, 2015)

  • 1.13.5(Aug 7, 2015)

Owner
Sokil
Kyiv, Ukraine
Sokil
Psalm Stubs for doctrine/mongodb-odm library

doctrine-mongodb-psalm-plugin A Doctrine plugin for Psalm (requires Psalm v4). Installation: $ composer require --dev runtothefather/doctrine-mongodb-

Evgeny 6 Jun 15, 2022
Migrations for MongoDB based on PHPMongo ODM

PHPMongo Migrator Migrations for MongoDB based on PHPMongo ODM Schema not required in MongoDb, so we dont need to create databases, collections or alt

Sokil 29 Jul 13, 2022
Php mongodb admin, use new mongodb extension.

EasyMongo EasyMongo is a Mongodb web management application. This project is based on iwind/RockMongo, uses the latest mongodb-extension + mongo-php-l

Meng Wang 8 Oct 31, 2022
Plastic is an Elasticsearch ODM and mapper for Laravel. It renders the developer experience more enjoyable while using Elasticsearch, by providing a fluent syntax for mapping, querying, and storing eloquent models.

Plastic is an Elasticsearch ODM and mapper for Laravel. It renders the developer experience more enjoyable while using Elasticsearch, by providing a f

Sleiman Sleiman 511 Dec 31, 2022
ODM with inheritance and OOP composition for Laravel 5+

ODM with inheritance and OOP composition for Laravel 5+ Full Documentation | CHANGELOG LODM module is intended to bring the Spiral ODM component funct

Anton Titov 21 Aug 17, 2022
Emoncms is an open-source web application for processing, logging and visualising energy, temperature and other environmental data and is part of the OpenEnergyMonitor project.

Emoncms is an open-source web application for processing, logging and visualising energy, temperature and other environmental data and is part of the OpenEnergyMonitor project.

Emoncms 1.1k Dec 22, 2022
Group of projects completed by me as a part of Intern at LGM

LGMVIP-Projects Group of projects completed by me as a part of Intern at LGM Author Details: Name : MAINAK CHAUDHURI Position : Web Developer Intern,

MAINAK CHAUDHURI 25 Dec 17, 2022
A lightweight framework-agnostic library in pure PHP for part-of-speech tagging

N-ai php pos tagger A lightweight framework-agnostic library in pure PHP for part-of-speech tagging. Can be used for chatbots, personal assistants, ke

Giorgio Rey 8 Nov 8, 2022
Exploiting and fixing security vulnerabilities of an old version of E-Class. Project implemented as part of the class YS13 Cyber-Security.

Open eClass 2.3 Development of XSS, CSRF, SQLi, RFI attacks/defences of an older,vulnerable version of eclass. Project implemented as part of the clas

Aristi_Papastavrou 11 Apr 23, 2022
A Virtualmin API designed to run standalone or as part of a Laravel Application

Virtualmin API A Virtualmin API designed to run standalone or as part of a Laravel Application Requirements: PHP 8.0 A running Virtualmin server Featu

Fintech Systems 6 Jan 26, 2022
Part of the Vagento framework. Can be installed independently.

Vagento - Enable Patching This package adds { 'extra': { 'enable-patching': true } } to the composer.json file. Needed for the cweagans/composer-patch

Vagento 1 Dec 11, 2021
A CLI tool to check whether a specific composer package uses imported symbols that aren't part of its direct composer dependencies

A CLI tool to analyze composer dependencies and verify that no unknown symbols are used in the sources of a package. This will prevent you from using "soft" dependencies that are not defined within your composer.json require section.

Matthias Glaub 722 Dec 30, 2022
Multipurpose Laravel and Livewire Application. This is a part of tutorial series on Youtube.

Multipurpose Laravel and Livewire Application This is a part of YouTube tutorial series on building application using Laravel and Livewire. Here is th

Clovon 88 Dec 23, 2022
DataLoaderPhp is a generic utility to be used as part of your application's data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching.

DataLoaderPHP is a generic utility to be used as part of your application's data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching.

Webedia - Overblog 185 Nov 3, 2022
Applies a patch from a local or remote file to any package that is part of a given composer project.

Applies a patch from a local or remote file to any package that is part of a given composer project. Patches can be defined both on project and on package level in package config or separate JSON file. Declaration-free mode (using embedded info within patch files) is available as well.

Vaimo 245 Dec 15, 2022
Stepup Middleware - This component is part of "Step-up Authentication as-a Service".

Step-up Middleware This component is part of "Step-up Authentication as-a Service". See Stepup-Deploy for an overview and installation instructions fo

OpenConext 4 Nov 2, 2022
Staged Payloads from Kali Linux - Part 1,2 of 3

PT Phone Home As penetration testers, we often come up with creative methods to deliver and execute our payloads, such as staged payloads. A staged pa

Tristram 14 Dec 19, 2022
Simple and swift MongoDB abstraction.

Monga A simple and swift MongoDB abstraction layer for PHP 5.4+ What's this all about? An easy API to get connections, databases and collections. A fi

The League of Extraordinary Packages 330 Nov 28, 2022
MongoDB PHP library

MongoDB PHP Library This library provides a high-level abstraction around the lower-level PHP driver (mongodb extension). While the extension provides

mongodb 1.5k Dec 31, 2022
A MongoDB based Eloquent model and Query builder for Laravel (Moloquent)

Laravel MongoDB This package adds functionalities to the Eloquent model and Query builder for MongoDB, using the original Laravel API. This library ex

Jens Segers 6.3k Jan 5, 2023