⭐ 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.
- PHP-Mongo v.1
- PHP 5.3 not supported starting from 2018-10-19
- PHP 5.4 - 5.5 not supported starting from 2019-10-19
- PHP 5.6
- PHP Mongo Extension 0.9 or above (Some features require >= 1.5)
- PHP 7
- PHP MongoDB Extension 1.0 or above
- Compatibility layer. Please, note some restriontions
- HHVM Driver not supported.
- PHP-Mongo v.2 (Currentry is under development and can not be used in production)
- PHP 7
- PHP MongoDB Extension natively without legacy driver adapters.
- PHP 5 not supported
- HHVM Driver not supported.
- PHP 7
- Installation
- Connecting
- Mapping
- Document validation
- Getting documents by id
- Create new document
- Get and set data in document
- Embedded documents
- DBRefs
- Storing document
- Querying documents
- Geospatial queries
- Fulltext search
- Pagination
- Persistence (Unit of Work)
- Deleting collections and documents
- Aggregation framework
- Events
- Behaviors
- Relations
- Concurency
- Read preferences
- Write concern
- Capped collections
- Executing commands
- Queue
- Migrations
- GridFS
- Versioning
- Indexes
- Caching and documents with TTL
- Debugging
- Unit tests
You can install library through Composer:
composer require sokil/php-mongo
Download latest release: Latest sources from GitHub
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
If you use Symfony framework, you can use Symfony MongoDB Bundle which wraps this library
composer require sokil/php-mongo-bundle
If you use Laravel framework, use Laravel adapter
composer require phpmongokit/laravel-mongo-odm
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.
If you use Yii2 Framework, you can use Yii2 Adapter which wraps this library
composer require phpmongokit/yii2-mongo-odm
If you require migrations, you can add dependency to Migrator, based on this library:
composer require sokil/php-mongo-migrator
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
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');
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 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 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();
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');
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');
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
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';
}
}
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'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 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'),
);
}
}
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 empty document object:
<?php
$document = $collection->createDocument();
Or with pre-defined values:
<?php
$document = $collection->createDocument([
'param1' => 'value1',
'param2' => 'value2'
]);
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.
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 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
.
Imagine that you have document, which represent User
model:
{
"login": "beebee",
"email": "beebee@gmail.com",
"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();
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();
}
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);
});
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']));
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);
}
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.
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();
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,
)
);
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');
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');
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.
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);
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);
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); });
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();
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();
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);
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.