{"id":13480769,"url":"https://github.com/sokil/php-mongo","last_synced_at":"2025-04-12T18:38:34.525Z","repository":{"id":13050478,"uuid":"15730561","full_name":"sokil/php-mongo","owner":"sokil","description":"MongoDB ODM. Part of @PHPMongoKit","archived":false,"fork":false,"pushed_at":"2023-02-07T10:34:34.000Z","size":2899,"stargazers_count":241,"open_issues_count":42,"forks_count":46,"subscribers_count":14,"default_branch":"1.0","last_synced_at":"2025-04-03T20:11:33.213Z","etag":null,"topics":["document","mongo","mongodb","odm","php","php-mongo","phpmongokit"],"latest_commit_sha":null,"homepage":"http://phpmongokit.github.io/","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sokil.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-01-08T09:06:52.000Z","updated_at":"2025-03-30T13:50:58.000Z","dependencies_parsed_at":"2023-02-19T16:45:29.354Z","dependency_job_id":null,"html_url":"https://github.com/sokil/php-mongo","commit_stats":null,"previous_names":[],"tags_count":63,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sokil%2Fphp-mongo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sokil%2Fphp-mongo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sokil%2Fphp-mongo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sokil%2Fphp-mongo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sokil","download_url":"https://codeload.github.com/sokil/php-mongo/tar.gz/refs/heads/1.0","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248615969,"owners_count":21133983,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["document","mongo","mongodb","odm","php","php-mongo","phpmongokit"],"created_at":"2024-07-31T17:00:44.773Z","updated_at":"2025-04-12T18:38:34.475Z","avatar_url":"https://github.com/sokil.png","language":"PHP","readme":"# Stand With Ukraine\n\n[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)\n\n----\n\nPHPMongo ODM\n============\n\n[![Total Downloads][badge-totalDownloads-img]][badge-totalDownloads-url]\n[![Build Status](https://travis-ci.org/sokil/php-mongo.png?branch=master\u00262)](https://travis-ci.org/sokil/php-mongo)\n[![Coverage Status](https://coveralls.io/repos/sokil/php-mongo/badge.png)](https://coveralls.io/r/sokil/php-mongo)\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/sokil/php-mongo/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/sokil/php-mongo/?branch=master)\n[![Code Climate](https://codeclimate.com/github/sokil/php-mongo/badges/gpa.svg)](https://codeclimate.com/github/sokil/php-mongo)\n[![Gitter](https://badges.gitter.im/Join_Chat.svg)](https://gitter.im/sokil/php-mongo?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\n#### PHP ODM for MongoDB.\n\n:star: 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](https://github.com/sokil/php-mongo-migrator) and do more things which makes your life easier.\n\n:1234: 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](#unit-tests) for details.\n\n[![ArmySOS - Help for Ukrainian Army](http://armysos.com.ua/wp-content/uploads/2014/09/728_90.jpg)](http://armysos.com.ua/en/help-the-army)\n\n#### Requirements\n\n* PHP-Mongo v.1\n  * __PHP 5.3 not supported starting from 2018-10-19__\n  * __PHP 5.4 - 5.5 not supported starting from 2019-10-19__\n  * PHP 5.6\n      * [PHP Mongo Extension](https://pecl.php.net/package/mongo) 0.9 or above (Some features require \u003e= 1.5)\n  * PHP 7\n    * [PHP MongoDB Extension](https://pecl.php.net/package/mongodb) 1.0 or above\n    * [Compatibility layer](https://github.com/alcaeus/mongo-php-adapter). Please, note some [restriontions](#compatibility-with-php-7)\n   * HHVM Driver [not supported](https://derickrethans.nl/mongodb-hhvm.html).\n* PHP-Mongo v.2 (Currentry is under development and can not be used in production)\n  * PHP 7\n    * [PHP MongoDB Extension](https://pecl.php.net/package/mongodb) natively without legacy driver adapters. \n  * PHP 5 not supported\n  * HHVM Driver [not supported](https://derickrethans.nl/mongodb-hhvm.html).\n\u003cbr/\u003e\n\n#### Table of contents\n\n* [Installation](#installation)\n  * [Common installation](#common-installation)\n  * [Symfony bundle](#symfony-bundle)\n  * [Laravel](#laravel)\n  * [Yii component](#yii-component)\n  * [Yii2 component](#yii2-component)\n  * [Support of migrations](#support-of-migrations)\n* [Connecting](#connecting)\n* [Mapping](#mapping)\n  * [Selecting database and collection](#selecting-database-and-collection)\n  * [Custom collections](#custom-collections)\n  * [Document schema](#document-schema)\n* [Document validation](#document-validation)\n* [Getting documents by id](#getting-documents-by-id)\n* [Create new document](#create-new-document)\n* [Get and set data in document](#get-and-set-data-in-document)\n* [Embedded documents](#embedded-documents)\n  * [Get embedded document](#get-embedded-document)\n  * [Set embedded document](#set-embedded-document)\n  * [Get embedded list of documents](#get-embedded-list-of-documents)\n  * [Set embedded list of documents](#set-embedded-list-of-documents)\n  * [Validation of embedded documents](#validation-of-embedded-documents)\n* [DBRefs](#dbrefs)\n* [Storing document](#storing-document)\n  * [Storing mapped object](#storing-mapped-object)\n  * [Insert and update documents without ODM](#insert-and-update-documents-without-odm)\n  * [Batch insert](#batch-insert)\n  * [Batch update](#batch-update)\n  * [Moving data between collections](#moving-data-between-collections)\n* [Querying documents](#querying-documents)\n  * [Query Builder](#query-builder)\n  * [Extending Query Builder](#extending-query-builder)\n  * [Identity Map](#identity-map)\n  * [Comparing queries](#comparing-queries)\n* [Geospatial queries](#geospatial-queries)\n* [Fulltext search](#fulltext-search)\n* [Pagination](#pagination)\n* [Persistence (Unit of Work)](#persistence-unit-of-work)\n* [Deleting collections and documents](#deleting-collections-and-documents)\n* [Aggregation framework](#aggregation-framework)\n* [Events](#events)\n* [Behaviors](#behaviors)\n* [Relations](#relations)\n  * [One-to-one relation](#one-to-one-relation)\n  * [One-to-many relation](#one-to-many-relation)\n  * [Many-to-many relation](#many-to-many-relation)\n  * [Add relation](#add-relation)\n  * [Remove relation](#remove-relation)\n* [Concurency](#concurency)\n  * [Optimistic locking](#optimistic-locking)\n* [Read preferences](#read-preferences)\n* [Write concern](#write-concern)\n* [Capped collections](#capped-collections)\n* [Executing commands](#executing-commands)\n* [Queue](#queue) \n* [Migrations](#migrations)\n* [GridFS](#gridfs)\n* [Versioning](#versioning)\n* [Indexes](#indexes)\n* [Caching and documents with TTL](#caching-and-documents-with-ttl)\n* [Debugging](#debugging)\n  * [Logging](#logging)\n  * [Profiling](#profiling)\n* [Unit tests](#unit-tests)\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nInstallation\n------------\n\n#### Common installation\n\nYou can install library through Composer:\n```\ncomposer require sokil/php-mongo\n```\n\nDownload latest release:\n[Latest sources from GitHub](https://github.com/sokil/php-mongo/releases/latest)\n\n#### Compatibility with PHP 7\n\n\u003e PHPMongo currently based on old [ext-mongo](https://pecl.php.net/package/mongo) entension.\n\u003e To use this ODM with PHP 7, you need to add [compatibility layer](https://github.com/alcaeus/mongo-php-adapter), which implement API of old extension over new [ext-mongodb](https://pecl.php.net/package/mongodb).\n\u003e To start using PHPMongo with PHP7, add requirement [alcaeus/mongo-php-adapter](https://github.com/alcaeus/mongo-php-adapter) to composer.\n\u003e Restrictions for using ODM with compatibility layer you can read in [known issues](https://github.com/alcaeus/mongo-php-adapter#known-issues) of original adapter.\n\nRequire adapter of old `ext-mongo` API to new `ext-mongodb`:\n```\ncomposer require alcaeus/mongo-php-adapter\n```\n\n#### Symfony bundle\nIf you use Symfony framework, you can use [Symfony MongoDB Bundle](https://github.com/sokil/php-mongo-bundle) which wraps this library\n\n```\ncomposer require sokil/php-mongo-bundle\n```\n\n#### Laravel\n\nIf you use Laravel framework, use [Laravel adapter](https://github.com/sokil/laravel-mongo-odm )\n```\ncomposer require phpmongokit/laravel-mongo-odm\n```\n\n#### Yii component\nIf you use Yii Framework, you can use [Yii Adapter](https://github.com/sokil/php-mongo-yii) which wraps this library\n\n```\ncomposer require sokil/php-mongo-yii\n```\n\nThis package in addition to PHPMongo adapter also has data provider and log router for MongoDb.\n\n#### Yii2 component\nIf you use Yii2 Framework, you can use [Yii2 Adapter](https://github.com/PHPMongoKit/yii2-mongo-odm) which wraps this library\n\n```\ncomposer require phpmongokit/yii2-mongo-odm\n```\n\n#### Support of migrations\nIf you require migrations, you can add dependency to [Migrator](https://github.com/sokil/php-mongo-migrator), based on this library:\n\n```\ncomposer require sokil/php-mongo-migrator\n```\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nConnecting\n----------\n\n#### Single connection\n\nConnecting to MongoDB server made through `\\Sokil\\Mongo\\Client` class:\n\n```php\n\u003c?php\n$client = new Client($dsn);\n```\n\nFormat of DSN used to connect to server is described in [PHP manual](http://www.php.net/manual/en/mongo.connecting.php).\nTo connect to localhost, use next DSN:\n```\nmongodb://127.0.0.1\n```\nTo connect to replica set, use next DSN:\n```\nmongodb://server1.com,server2.com/?replicaSet=replicaSetName\n```\n\n#### Pool of connections\n\nIf you have few connections, you may prefer connection pool instead of managing different connections. Use `\\Sokil\\Mongo\\ClientPool` instance to initialize pool object:\n\n```php\n\u003c?php\n\n$pool = new ClientPool(array(\n    'connect1' =\u003e array(\n        'dsn' =\u003e 'mongodb://127.0.0.1',\n        'defaultDatabase' =\u003e 'db2',\n        'connectOptions' =\u003e array(\n            'connectTimeoutMS' =\u003e 1000,\n            'readPreference' =\u003e \\MongoClient::RP_PRIMARY,\n        ),\n        'mapping' =\u003e array(\n            'db1' =\u003e array(\n                'col1' =\u003e '\\Collection1',\n                'col2' =\u003e '\\Collection2',\n            ),\n            'db2' =\u003e array(\n                'col1' =\u003e '\\Collection3',\n                'col2' =\u003e '\\Collection4',\n            )\n        ),\n    ),\n    'connect2' =\u003e array(\n        'dsn' =\u003e 'mongodb://127.0.0.1',\n        'defaultDatabase' =\u003e 'db2',\n        'mapping' =\u003e array(\n            'db1' =\u003e array(\n                'col1' =\u003e '\\Collection5',\n                'col2' =\u003e '\\Collection6',\n            ),\n            'db2' =\u003e array(\n                'col1' =\u003e '\\Collection7',\n                'col2' =\u003e '\\Collection8',\n            )\n        ),\n    ),\n));\n\n$connect1Client = $pool-\u003eget('connect1');\n$connect2Client = $pool-\u003eget('connect2');\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nMapping\n-------\n\n### Selecting database and collection\n\nYou can get instances of databases and collections by its name.\n\nTo get instance of database class `\\Sokil\\Mongo\\Database`:\n```php\n\u003c?php\n$database = $client-\u003egetDatabase('databaseName');\n// or simply\n$database = $client-\u003edatabaseName;\n```\n\nTo get instance of collection class `\\Sokil\\Mongo\\Collection`:\n```php\n\u003c?php\n$collection = $database-\u003egetCollection('collectionName');\n// or simply\n$collection = $database-\u003ecollectionName;\n```\n\nDefault database may be specified to get collection directly from `\\Sokil\\Mongo\\Client` object:\n```php\n\u003c?php\n$client-\u003euseDatabase('databaseName');\n$collection = $client-\u003egetCollection('collectionName');\n```\n\n### Custom collections\n\nCustom collections are used to add some collection-specific features in related class. \nFirst you need to create class extended from `\\Sokil\\Mongo\\Collection`:\n```php\n\u003c?php\n\n// define class of collection\nclass CustomCollection extends \\Sokil\\Mongo\\Collection\n{\n\n}\n```\n\nThis class must be then mapped to collection name in order to return object of this class when collection requested.\nCustom collection referenced in standard way:\n\n```php\n\u003c?php\n/**\n * @var \\CustomCollection\n */\n$collection = $client\n    -\u003egetDatabase('databaseName')\n    -\u003egetCollection('collectionName');\n```\n\n#### Collection definition\n\nCollection name must be mapped to collection class. \nIf you want to pass some additional options to collection, you also can\nconfigure them in mapping definition:\n\n```php\n\u003c?php\n$client-\u003emap([\n    'databaseName'  =\u003e [\n        'collectionName' =\u003e [\n            'class' =\u003e '\\Some\\Custom\\Collection\\Classname',\n            'collectionOption1' =\u003e 'value1',\n            'collectionOption2' =\u003e 'value2',\n        ]\n    ],\n]);\n```\n\nAll options later may be accessed by `Collection::getOption()` method:\n\n```php\n\u003c?php\n// will return 'value1'\n$client\n    -\u003egetDatabase('databaseName')\n    -\u003egetCollection('collectionName')\n    -\u003egetOption('collectionOption1');\n```\n\nPredefined options are:\n\n| Option              | Default value            | Description                                                |\n| ------------------- | ------------------------ | ---------------------------------------------------------- |\n| class               | \\Sokil\\Mongo\\Collection  | Fully qualified collection class                           |\n| documentClass       | \\Sokil\\Mongo\\Document    | Fully qualified document class                             |\n| versioning          | false                    | Using document versioning                                  |\n| index               | null                     | Index definition                                           |\n| expressionClass     | \\Sokil\\Mongo\\Expression  | Fully qualified expression class for custom query builder  |\n| behaviors           | null                     | List of behaviors, attached to every document              |\n| relations           | null                     | Definition of relations to documents in other collection   |\n| batchSize           | null                     | Number of documents to return in each batch of response    |\n| 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    |\n| serverCursorTimeout | null                     | A cumulative time limit in milliseconds to be allowed by the server for processing operations on the cursor |\n| documentPool        | true                     | Document pool, used to store already fetched documents in identity map |\n\nIf `class` omitted, then used standart `\\Sokil\\Mongo\\Collection` class.\n\nTo override default document class use `documentClass` option of collection:\n```php\n\u003c?php\n$client-\u003emap([\n    'databaseName'  =\u003e [\n        'collectionName' =\u003e [\n            'documentClass' =\u003e '\\Some\\Document\\Class',\n        ]\n    ],\n]);\n\n// is instance of \\Some\\Document\\Class\n$document = $client\n    -\u003egetDatabase('databaseName')\n    -\u003egetCollection('collectionName')\n    -\u003ecreateDocument();\n```\n\n#### Mapping of collection name to collection class\n\nIf only class name of collection defined, you may simply pass it in mapping. \n\n\n```php\n\u003c?php\n\n// map class to collection name\n$client-\u003emap([\n    'databaseName'  =\u003e [\n        'collectionName' =\u003e [\n            'class' =\u003e \\Acme\\MyCollection',\n        ],\n    ],\n]);\n\n/**\n * @var \\Acme\\MyCollection\n */\n$collection = $client\n    -\u003egetDatabase('databaseName')\n    -\u003egetCollection('collectionName');\n```\n\n\n_There is also deprecated method to specify collection's class name. Please, use array definition and option `class`._\n\n```php\n\u003c?php\n\n// map class to collection name\n$client-\u003emap([\n    'databaseName'  =\u003e [\n        'collectionName' =\u003e '\\Acme\\MyCollection'\n    ],\n]);\n\n/**\n * @var \\Acme\\MyCollection\n */\n$collection = $client\n    -\u003egetDatabase('databaseName')\n    -\u003egetCollection('collectionName');\n```\n\n#### Mapping with class prefix\n\nCollections not configured directly, may be mapped automatically by using `*` in mapping keys. \nAny collection may be mapped to class without enumerating every collection name.\n\n```php\n\u003c?php\n$client-\u003emap([\n    'databaseName'  =\u003e [\n        '*' =\u003e [\n            'class' =\u003e '\\Acme\\Collection\\Class\\Prefix',\n        ],\n    ],\n]);\n\n/**\n * @var \\Acme\\Collection\\Class\\Prefix\\CollectionName\n */\n$collection = $client\n    -\u003egetDatabase('databaseName')\n    -\u003egetCollection('collectionName');\n\n/**\n * @var \\Acme\\Collection\\Class\\Prefix\\CollectionName\\SubName\n */\n$collection = $client\n    -\u003egetDatabase('databaseName')\n    -\u003egetCollection('collectionName.subName');\n```\n\n_There is also deprecated method to specify class prefix. Please, use `*` as collection name and array definition with option `class`._\n\n```php\n\u003c?php\n$client-\u003emap([\n    'databaseName'  =\u003e '\\Acme\\Collection\\Class\\Prefix',\n]);\n\n/**\n * @var \\Acme\\Collection\\Class\\Prefix\\CollectionName\n */\n$collection = $client\n    -\u003egetDatabase('databaseName')\n    -\u003egetCollection('collectionName');\n\n/**\n * @var \\Acme\\Collection\\Class\\Prefix\\CollectionName\\SubName\n */\n$collection = $client\n    -\u003egetDatabase('databaseName')\n    -\u003egetCollection('collectionName.subName');\n```\n\n#### Regexp mapping\n\nCollection name in mapping may be defined as RegExp pattern. Pattern must start from symbol `/`:\n\n```php\n\u003c?php\n$database-\u003emap(array(\n    '/someCollection(\\d)/' =\u003e '\\Some\\Collection\\Class',\n));\n```\n\nAny collection with name matched to pattern will be instance of `\\Some\\Collection\\Class`:\n```php\n\u003c?php\n$col1 = $database-\u003egetCollection('someCollection1');\n$col2 = $database-\u003egetCollection('someCollection2');\n$col4 = $database-\u003egetCollection('someCollection4');\n```\n\nAny stored regexp values than may be get through `$collection-\u003egetOption('regex');`.\n\n```php\n\u003c?php\n$database-\u003emap(array(\n    '/someCollection(\\d+)/' =\u003e '\\Some\\Collection\\Class',\n));\n$col42 = $database-\u003egetCollection('someCollection42');\necho $col1-\u003egetOption('regexp')[0]; // someCollection42\necho $col1-\u003egetOption('regexp')[1]; // 42\n```\n\n### Document schema and validating\n\n#### Custom document class\n\nCustom document class may be useful when required some processing of date on load, getting or save. Custom document class must extend `\\Sokil\\Mongo\\Document`.\n\n```php\n\u003c?php\nclass CustomDocument extends \\Sokil\\Mongo\\Document\n{\n\n}\n```\n\nNow you must configure its name in collection's class by overriding method `Collection::getDocumentClassName()`:\n\n```php\n\u003c?php\nclass CustomCollection extends \\Sokil\\Mongo\\Collection\n{\n    public function getDocumentClassName(array $documentData = null) {\n        return '\\CustomDocument';\n    }\n}\n```\n\n#### Single Collection Inheritance\n\nOften useful to have different document classes, which store data in single collection.\nFor example you have products in your shop `Song` and `VideoClip`, which inherit abstract `Product`.\nThey have same fields like author or duration, but may also have other different fields and\nbehaviors. This situation described in example [Product Catalog](http://docs.mongodb.org/ecosystem/use-cases/product-catalog/).\n\nYou 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:\n\n```php\n\u003c?php\nclass CustomCollection extends \\Sokil\\Mongo\\Collection\n{\n    public function getDocumentClassName(array $documentData = null) {\n        return '\\Custom' . ucfirst(strtolower($documentData['type'])) . 'Document';\n    }\n}\n```\n\nAlso document class may be defined in collection mapping:\n\n```php\n\u003c?php\n$client-\u003emap([\n    'databaseName'  =\u003e [\n        'collectionName1' =\u003e [\n            'documentClass' =\u003e '\\CustomDocument',\n        ],\n        'collectionName2' =\u003e function(array $documentData = null) {\n            return '\\Custom' . ucfirst(strtolower($documentData['type'])) . 'Document';\n        },\n        'collectionName3' =\u003e [\n            'documentClass' =\u003e function(array $documentData = null) {\n                return '\\Custom' . ucfirst(strtolower($documentData['type'])) . 'Document';\n            },\n        ],\n    ],\n]);\n```\n\nIn example above class `\\CustomVideoDocument` related to `{\"_id\": \"45..\", \"type\": \"video\"}`, and `\\CustomAudioDocument` to `{\"_id\": \"45..\", type: \"audio\"}`\n\n#### Document schema\n\nDocument's scheme is completely not required. \nIf field is required and has default value, it can be defined in special property of document class `Document::schema`:\n\n```php\n\u003c?php\nclass CustomDocument extends \\Sokil\\Mongo\\Document\n{\n    protected $schema = [\n        'requiredField' =\u003e 'defaultValue',\n        'someField'     =\u003e [\n            'subDocumentField' =\u003e 'value',\n        ],\n    ];\n}\n```\n\nAlso supported deprecated format `Document::_data`:\n\n```php\n\u003c?php\nclass CustomDocument extends \\Sokil\\Mongo\\Document\n{\n    protected $_data = [\n        'requiredField' =\u003e 'defaultValue',\n        'someField'     =\u003e [\n            'subDocumentField' =\u003e 'value',\n        ],\n    ];\n}\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nDocument validation\n-------------------\n\nDocument 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:\n\n```php\n\u003c?php\nclass CustomDocument extends \\Sokil\\Mongo\\Document\n{\n    public function rules()\n    {\n        return array(\n            array('email,password', 'required'),\n            array('role', 'equals', 'to' =\u003e 'admin'),\n            array('role', 'not_equals', 'to' =\u003e 'guest'),\n            array('role', 'in', 'range' =\u003e array('admin', 'manager', 'user')),\n            array('contract_number', 'numeric', 'message' =\u003e 'Custom error message, shown by getErrors() method'),\n            array('contract_number' ,'null', 'on' =\u003e 'SCENARIO_WHERE_CONTRACT_MUST_BE_NULL'),\n            array('code' ,'regexp', '#[A-Z]{2}[0-9]{10}#')\n        );\n    }\n}\n```\n\nDocument can have validation state, based on scenario. Scenario can be specified by method `Document::setScenario($scenario)`.\n\n```php\n\u003c?php\n$document-\u003esetScenario('register');\n```\n\nIf some validation rule applied only for some scenarios, this scenarios must be passed on `on` key, separated by comma.\n```php\n\u003c?php\npublic function rules()\n    {\n        return array(\n            array('field' ,'null', 'on' =\u003e 'register,update'),\n        );\n    }\n```\n\nIf some validation rule applied to all except some scenarios, this scenarios must be passed on `except` key, separated by comma.\n\n```php\n\u003c?php\npublic function rules()\n    {\n        return array(\n            array('field' ,'null', 'except' =\u003e 'register,update'),\n        );\n    }\n```\n\nThere are two equal cases for document validation:\n```php\ntry {\n    $document-\u003esave();\n} catch (\\Sokil\\Mongo\\Document\\InvalidDocumentException $e) {\n    // get validation errors\n    var_dump($document-\u003egetErrors());\n    // get document instance from exception\n    var_dump($e-\u003egetDocument()-\u003egetErrors());\n}\n```\nor\n```php\nif ($document-\u003eisValid())\n    $document-\u003esave();\n} else {\n    var_dump($document-\u003egetErrors());\n}\n```\n\nBy 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`\nwhen document was invalid. Since `v.1.11.6` this exception is deprecated. Use `\\Sokil\\Mongo\\Document\\InvalidDocumentException` instead.\n\nErrors may be accessed through `Document::getErrors()` method of document object. \n\nAlso instabce of document may be get from exception method:\n```php\n\u003c?php\ntry {\n    $document-\u003esave();\n} catch(\\Sokil\\Mongo\\Document\\InvalidDocumentException $e) {\n    $e-\u003egetDocument()-\u003egetErrors();\n}\n```\n\nIf allowed to save invalid documents, disable validation on save:\n```php\n$document-\u003esave(false);\n```\n\nError may be triggered manually by calling method `triggerError($fieldName, $rule, $message)`\n```php\n\u003c?php\n$document-\u003etriggerError('someField', 'email', 'E-mail must be at domain example.com');\n```\n\nYou may add you custom validation rule just adding method to document class and defining method name as rule:\n```php\n\u003c?php\nclass CustomDocument extends \\Sokil\\Mongo\\Document\n{\n    punlic function rules()\n    {\n        return array(\n            array(\n                'email',\n                'uniqueFieldValidator',\n                'message' =\u003e 'E-mail must be unique in collection'\n            ),\n        );\n    }\n\n    /**\n     * Validator\n     */\n    public function uniqueFieldValidator($fieldName, $params)\n    {\n        // Some logic of checking unique mail.\n        //\n        // Before version 1.7 this method must return true if validator passes,\n        // and false otherwise.\n        //\n        // Since version 1.7 this method return no values and must call\n        // Document::addError() method to add error into stack.\n    }\n}\n```\n\nYou may create your own validator class, if you want to use validator in few classes.\nJust extend your class from abstract validator class `\\Sokil\\Mongo\\Validator` and register your own validator namespace:\n\n```php\n\u003c?php\nnamespace Vendor\\Mongo\\Validator;\n\n/**\n * Validator class\n */\nclass MyOwnEqualsValidator extends \\Sokil\\Mongo\\Validator\n{\n    public function validateField(\\Sokil\\Mongo\\Document $document, $fieldName, array $params)\n    {\n        if (!$document-\u003eget($fieldName)) {\n            return;\n        }\n\n        if ($document-\u003eget($fieldName) === $params['to']) {\n            return;\n        }\n\n        if (!isset($params['message'])) {\n            $params['message'] = 'Field \"' . $fieldName . '\" must be equals to \"' . $params['to'] . '\" in model ' . get_called_class();\n        }\n\n        $document-\u003eaddError($fieldName, $this-\u003egetName(), $params['message']);\n    }\n}\n\n/**\n * Registering validator in document\n */\n\nclass SomeDocument extends \\Sokil\\Mongo\\Document\n{\n    public function beforeConstruct()\n    {\n        $this-\u003eaddValidatorNamespace('Vendor\\Mongo\\Validator');\n    }\n\n    public function rules()\n    {\n        return array(\n            // 'my_own_equals' converts to 'MyOwnEqualsValidator' class name\n            array('field', 'my_own_equals', 'to' =\u003e 42, 'message' =\u003e 'Not equals'),\n        );\n    }\n}\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nGetting documents by id\n-----------------------\n\nTo get document from collection by its id:\n```php\n\u003c?php\n$document = $collection-\u003egetDocument('5332d21b253fe54adf8a9327');\n```\n\nTo add additional checks or query modifiers use callable:\n```php\n\u003c?php\n$document = $collection-\u003egetDocument(\n    '5332d21b253fe54adf8a9327',\n    function(\\Sokil\\Mongo\\Cursor $cursor) {\n        // get document only if active\n        $cursor-\u003ewhere('status', 'active');\n        // slice embedded documents\n        $cursor-\u003eslice('embdocs', 10, 30);\n    }\n);\n```\n\nNote that if callable specified, document always loaded directly omitting document pool.\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nCreate new document\n-------------------\n\nCreate new empty document object:\n\n```php\n\u003c?php\n$document = $collection-\u003ecreateDocument();\n```\n\nOr with pre-defined values:\n\n```php\n\u003c?php\n$document = $collection-\u003ecreateDocument([\n    'param1' =\u003e 'value1',\n    'param2' =\u003e 'value2'\n]);\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nGet and set data in document\n----------------------------\n\n### Get\n\nTo get value of document's field you may use one of following ways:\n```php\n\u003c?php\n$document-\u003erequiredField; // defaultValue\n$document-\u003eget('requiredField'); // defaultValue\n$document-\u003egetRequiredField(); // defaultValue\n\n$document-\u003esomeField; // ['subDocumentField' =\u003e 'value']\n$document-\u003eget('someField'); // ['subDocumentField' =\u003e 'value']\n$document-\u003egetSomeField(); // ['subDocumentField' =\u003e 'value']\n$document-\u003eget('someField.subDocumentField'); // 'value'\n\n$document-\u003eget('some.unexisted.subDocumentField'); // null\n```\nIf field not exists, null value returned.\n\n### Set\n\nTo set value you may use following ways:\n```php\n\u003c?php\n$document-\u003esomeField = 'someValue'; // {someField: 'someValue'}\n$document-\u003eset('someField', 'someValue'); // {someField: 'someValue'}\n$document-\u003eset('someField.sub.document.field', 'someValue'); // {someField: {sub: {document: {field: {'someValue'}}}}}\n$document-\u003esetSomeField('someValue');  // {someField: 'someValue'}\n```\n\n### Push\nPush will add value to array field:\n```php\n\u003c?php\n\n$document-\u003epush('field', 1);\n$document-\u003epush('field', 2);\n\n$document-\u003eget('field'); // return [1, 2]\n```\n\nIf field already exists, and not sequential list of values, then in case of scalar, scalar will be converted to array.\nIf values pushed to field with subdocument, then triggered `Sokil\\Mongo\\Document\\InvalidOperationException`.\n\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nEmbedded documents\n------------------\n\n### Get embedded document\n\nImagine that you have document, which represent `User` model:\n\n```javascript\n{\n    \"login\": \"beebee\",\n    \"email\": \"beebee@gmail.com\",\n    \"profile\": {\n        \"birthday\": \"1984-08-11\",\n        \"gender\": \"female\",\n        \"country\": \"Ukraine\",\n        \"city\": \"Kyiv\"\n    }\n}\n```\n\nYou can define embedded `profile` document as standalone class:\n```php\n\u003c?php\n\n/**\n * Profile class\n */\nclass Profile extends \\Sokil\\Mongo\\Structure\n{\n    public function getBirthday() { return $this-\u003eget('birthday'); }\n    public function getGender() { return $this-\u003eget('gender'); }\n    public function getCountry() { return $this-\u003eget('country'); }\n    public function getCity() { return $this-\u003eget('city'); }\n}\n\n/**\n * User model\n */\nclass User extends \\Sokil\\Mongo\\Document\n{\n    public function getProfile()\n    {\n        return $this-\u003egetObject('profile', '\\Profile');\n    }\n}\n```\n\nNow you are able to get profile params:\n\n```php\n\u003c?php\n$birthday = $user-\u003egetProfile()-\u003egetBirthday();\n```\n\n### Set embedded document\n\nYou can also set embedded document. If embedded document has validation rules, they will be checked before embed it to document:\n\n```php\n/**\n * Profile class\n */\nclass Profile extends \\Sokil\\Mongo\\Structure\n{\n    public function getBirthday() { return $this-\u003eget('birthday'); }\n\n    public function rules()\n    {\n        return array(\n            array('birthday', 'required', 'message' =\u003e 'REQUIRED_FIELD_EMPTY_MESSAGE'),\n        );\n    }\n}\n\n/**\n * User model\n */\nclass User extends \\Sokil\\Mongo\\Document\n{\n    public function setProfile(Profile $profile)\n    {\n        return $this-\u003eset('profile', $profile);\n    }\n}\n```\n\nIf embedded document is invalid, it will throw `Sokil\\Mongo\\Document\\InvalidDocumentException`. Embedded \ndocument may be obtained from exception object:\n\n```php\ntry {\n    $user-\u003eset('profile', $profile);\n} catch (InvalidDocumentException $e) {\n    $e-\u003egetDocument()-\u003egetErrors();\n}\n```\n\n### Get embedded list of documents\n\nImagine that you have stored post data in collection 'posts', and post document has\nembedded comment documents:\n\n```javascript\n{\n    \"title\": \"Storing embedded documents\",\n    \"text\": \"MongoDb allows to atomically modify embedded documents\",\n    \"comments\": [\n        {\n            \"author\": \"MadMike42\",\n            \"text\": \"That is really cool\",\n            \"date\": ISODate(\"2015-01-06T06:49:41.622Z\"\n        },\n        {\n            \"author\": \"beebee\",\n            \"text\": \"Awesome!!!11!\",\n            \"date\": ISODate(\"2015-01-06T06:49:48.622Z\"\n        },\n    ]\n}\n```\n\nSo we can create `Comment` model, which extends `\\Sokil\\Mongo\\Structure`:\n\n```php\n\u003c?php\nclass Comment extends \\Sokil\\Mongo\\Structure\n{\n    public function getAuthor() { return $this-\u003eget('author'); }\n    public function getText() { return $this-\u003eget('text'); }\n    public function getDate() { return $this-\u003eget('date')-\u003esec; }\n}\n\n```\n\nNow we can create `Post` model with access to embedded `Comment` models:\n\n```php\n\u003c?php\n\nclass Post extends \\Sokil\\Mongo\\Document\n{\n    public function getComments()\n    {\n        return $this-\u003egetObjectList('comments', '\\Comment');\n    }\n}\n```\n\nMethod `Post::getComments()` allows you to get all of embedded document. To\npaginate embedded documents you can use `\\Sokil\\Mongo\\Cursor::slice()` functionality.\n\n```php\n\u003c?php\n$collection-\u003efind()-\u003eslice('comments', $limit, $offset)-\u003efindAll();\n```\n\nIf you get `Document` instance through `Collection::getDocument()` you can define\nadditional expressions for loading it:\n\n```php\n\u003c?php\n$document = $collection-\u003egetDocument('54ab8585c90b73d6949d4159', function(Cursor $cursor) {\n    $cursor-\u003eslice('comments', $limit, $offset);\n});\n\n```\n\n### Set embedded list of documents\n\nYou can store embedded document to array, and validate it before pushing:\n```php\n\u003c?php\n$post-\u003epush('comments', new Comment(['author' =\u003e 'John Doe']));\n$post-\u003epush('comments', new Comment(['author' =\u003e 'Joan Doe']));\n```\n\n### Validation of embedded documents\n\nAs 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`.\n\n```php\nclass EmbeddedDocument extends Structure()\n{\n    public function rules() {}\n}\n\n$embeddedDocument = new EmbeddedDocument();\n// auto validation\ntry {\n    $document-\u003eset('some', embeddedDocument);\n    $document-\u003eaddToSet('some', embeddedDocument);\n    $document-\u003epush('some', embeddedDocument);\n} catch (InvalidDocumentException $e) {\n    \n}\n\n// manual validation\nif ($embeddedDocument-\u003eisValid()) {\n    $document-\u003eset('some', embeddedDocument);\n    $document-\u003eaddToSet('some', embeddedDocument);\n    $document-\u003epush('some', embeddedDocument);\n}\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nDBRefs\n------\n\nIn most cases you should use the manual reference method for connecting two or more related documents -\nincluding one document’s _id field in another document.\nThe application can then issue a second query to resolve the referenced fields as needed.\nSee more info about supporting manual references in section [Relations](#relations)\n\nHowever, if you need to reference documents from multiple collections, or use\nlegacy database with DBrefs inside, consider using DBRefs.\nSee more info about DBRef at https://docs.mongodb.com/manual/reference/database-references/.\n\nIf you have DBRef array, you can get document instance:\n\n```php\n\u003c?php\n$collection-\u003egetDocumentByReference(array('$ref' =\u003e 'col', '$id' =\u003e '23ef12...ff452'));\n$database-\u003egetDocumentByReference(array('$ref' =\u003e 'col', '$id' =\u003e '23ef12...ff452'));\n```\n    \nAdding reference to one document in another:\n\n```php\n\u003c?php\n$relatedDocument = $this-\u003ecollection-\u003ecreateDocument(array('param' =\u003e 'value'))-\u003esave();\n$document = $this-\u003ecollection\n    -\u003ecreateDocument()\n    -\u003esetReference('related', $relatedDocument)\n    -\u003esave();\n```\n\nGet document from reference field:\n\n```php\n\u003c?php\n\n$relatedDocument = $document-\u003egetReferencedDocument('related');\n\n```\n\nPush relation to list of relations:\n\n```php\n\u003c?php\n\n$relatedDocument = $this-\u003ecollection-\u003ecreateDocument(array('param' =\u003e 'value'))-\u003esave();\n$document = $this-\u003ecollection\n    -\u003ecreateDocument()\n    -\u003epushReference('related', $relatedDocument)\n    -\u003esave();\n```\n\nGet list of related documents:\n\n```php\n\u003c?php\n\n$foundRelatedDocumentList = $document-\u003egetReferencedDocumentList('related');\n\n```\n\nList of references may be from different collections of same database.\nSpecifying of database in reference not supported.\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nStoring document\n----------------\n\n### Storing mapped object\n\nIf you have previously loaded and modified instance of `\\Sokil\\Mongo\\Document`, just save it.\nDocument will automatically be inserted or updated if it already stored.\n\n```php\n\u003c?php\n\n// create new document and save\n$document = $collection\n    -\u003ecreateDocument(['param' =\u003e 'value'])\n    -\u003esave();\n\n// load existed document, modify and save\n$document = $collection\n    -\u003egetDocument('23a4...')\n    -\u003eset('param', 'value')\n    -\u003esave();\n```\n\n### Insert and update documents without ODM\n\nIf required quick insert of document without validating, events just insert it as array:\n\n```php\n\u003c?php\n$collection-\u003einsert(['param' =\u003e 'value']);\n```\n\nTo update existed documents, use:\n\n```php\n\u003c?php\n$collection-\u003eupdate($expression, $data, $options);\n```\n\nExpression may be defined as array, `\\Sokil\\Mongo\\Expressin` object or callable, which configure expression.\nOperator may be defined as array, `\\Sokil\\Mongo\\Operator` object or callable which configure operator.\nOptions is array of all allowed options, described in http://php.net/manual/ru/mongocollection.update.php.\n\nFor example:\n```php\n\u003c?php\n$collection-\u003eupdate(\n    function(\\Sokil\\Mongo\\Expression $expression) {\n        $expression-\u003ewhere('status', 'active');\n    },\n    function(\\Sokil\\Mongo\\Operator $operator) {\n        $operator-\u003eincrement('counter');\n    },\n    array(\n        'multiple' =\u003e true,\n    )\n);\n```\n\n### Batch insert\n\nTo insert many documents at once with validation of inserted document:\n```php\n\u003c?php\n$collection-\u003ebatchInsert(array(\n    array('i' =\u003e 1),\n    array('i' =\u003e 2),\n));\n```\n\nAlso supported `\\MongoInsertBatch` through interface:\n```php\n\u003c?php\n$collection\n    -\u003ecreateBatchInsert()\n    -\u003einsert(array('i' =\u003e 1))\n    -\u003einsert(array('i' =\u003e 2))\n    -\u003eexecute('majority');\n```\n\n### Batch update\n\nMaking changes in few documents:\n\n```php\n\u003c?php\n\n$collection-\u003ebatchUpdate(\n    function(\\Sokil\\Mongo\\Expression $expression) {\n        return $expression-\u003ewhere('field', 'value');\n    },\n    ['field' =\u003e 'new value']\n);\n```\n\nTo update all documents use:\n```php\n\u003c?php\n\n$collection-\u003ebatchUpdate([], array('field' =\u003e 'new value'));\n```\n\nRename fields of documents:\n```php\n\u003c?php\n$collection-\u003ebatchUpdate(\n    [],\n    function(Operator $operator) {\n        $operator-\u003erenameField('param', 'renamedParam');\n    }\n);\n```\n\nAlso supported `\\MongoUpdateBatch` through interface:\n```php\n\u003c?php\n$collection\n    -\u003ecreateBatchUpdate()\n    -\u003eupdate(\n        array('a' =\u003e 1),\n        array('$set' =\u003e array('b' =\u003e 'updated1')),\n        $multiple,\n        $upsert\n    )\n    -\u003eupdate(\n        $collection-\u003eexpression()-\u003ewhere('a', 2),\n        $collection-\u003eoperator()-\u003eset('b', 'updated2'),\n        $multiple,\n        $upsert\n    )\n    -\u003eupdate(\n        function(Expression $e) { $e-\u003ewhere('a', 3); },\n        function(Operator $o) { $o-\u003eset('b', 'updated3'); },\n        $multiple,\n        $upsert\n    )\n    -\u003eexecute('majority');\n```\n\n### Moving data between collections\n\nTo copy documents from one collection to another according to expression:\n\n```php\n\u003c?php\n// to new collection of same database\n$collection\n    -\u003efind()\n    -\u003ewhere('condition', 1)\n    -\u003ecopyToCollection('newCollection');\n\n// to new collection in new database\n$collection\n    -\u003efind()\n    -\u003ewhere('condition', 1)\n    -\u003ecopyToCollection('newCollection', 'newDatabase');\n```\n\nTo move documents from one collection to another according to expression:\n\n```php\n\u003c?php\n// to new collection of same database\n$collection\n    -\u003efind()\n    -\u003ewhere('condition', 1)\n    -\u003emoveToCollection('newCollection');\n\n// to new collection in new database\n$collection\n    -\u003efind()\n    -\u003ewhere('condition', 1)\n    -\u003emoveToCollection('newCollection', 'newDatabase');\n```\n\nImportant to note that there is no transactions so if error will occur\nduring process, no changes will rollback.\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nQuerying documents\n------------------\n\n### Query Builder\n\nTo query documents, which satisfy some conditions you need to use query builder:\n```php\n\u003c?php\n$cursor = $collection\n    -\u003efind()\n    -\u003efields(['name', 'age'])\n    -\u003ewhere('name', 'Michael')\n    -\u003ewhereGreater('age', 29)\n    -\u003ewhereIn('interests', ['php', 'snowboard', 'traveling'])\n    -\u003eskip(20)\n    -\u003elimit(10)\n    -\u003esort([\n        'name'  =\u003e 1,\n        'age'   =\u003e -1,\n    ]);\n```\n\nAll \"where\" conditions added with logical AND. To add condition with logical OR:\n```php\n\u003c?php\n$cursor = $collection\n    -\u003efind()\n    -\u003ewhereOr(\n        $collection-\u003eexpression()-\u003ewhere('field1', 50),\n        $collection-\u003eexpression()-\u003ewhere('field2', 50),\n    );\n```\n\nResult of the query is iterator `\\Sokil\\Mongo\\Cursor`, which you can then iterate:\n```php\n\u003c?php\nforeach($cursor as $documentId =\u003e $document) {\n    echo $document-\u003eget('name');\n}\n```\n\nOr you can get result array:\n```php\n\u003c?php\n$result = $cursor-\u003efindAll();\n```\n\nTo get only one result:\n```php\n\u003c?php\n$document = $cursor-\u003efindOne();\n```\n\nTo get only one random result:\n```php\n\u003c?php\n$document = $cursor-\u003efindRandom();\n```\n\nTo get values from a single field in the result set of documents:\n```php\n\u003c?php\n$columnValues = $cursor-\u003epluck('some.field.name');\n```\n\nTo map found documents:\n```php\n\u003c?php\n$result = $collection-\u003efind()-\u003emap(function(Document $document) {\n    return $document-\u003eparam;\n});\n```\n\nTo filter found documents:\n```php\n\u003c?php\n$result = $collection-\u003efind()-\u003efilter(function(Document $document) {\n    return $document-\u003eparam % 2;\n});\n```\n\nTo apply chain of functions to result, use `ResultSet`:\n```php\n\u003c?php\n$collection-\u003efind()\n    -\u003egetResultSet()\n    -\u003efilter(function($doc) { return $doc-\u003eparam % 2 })\n    -\u003efilter(function($doc) { return $doc-\u003eparam \u003e 6 })\n    -\u003emap(function($item) {\n        $item-\u003eparam = 'update' . $item-\u003eparam;\n        return $item;\n    });\n```\n\nWhen iterating through cursor client\n[retrieve some amount of documents](http://docs.mongodb.org/manual/reference/method/cursor.batchSize/)\nfrom the server in one round trip.\nTo define this numner of documents:\n\n```php\n\u003c?php\n\n$cursor-\u003esetBatchSize(20);\n```\n\n#### Query timeouts\n\nClient timeout defined to stop waiting for a response and throw a \\MongoCursorTimeoutException after a set time.\nA timeout can be set at any time and will affect subsequent queries on the cursor, including fetching more results from the database.\n\n```php \n$collection-\u003efind()-\u003ewhere('name', 'Michael')-\u003esetClientTimeout(4200);\n```\n\nServer-side timeout for a query specifies a cumulative time limit in milliseconds to be allowed by the server for processing operations on the cursor.\n\n```php\n$collection-\u003efind()-\u003ewhere('name', 'Michael')-\u003esetServerTimeout(4200);\n```\n\n### Distinct values\n\nTo get distinct values of field:\n```php\n\u003c?php\n// return all distinct values\n$values = $collection-\u003egetDistinct('country');\n```\n\nValues may be filtered by expression specified as array, callable or `Expression` object:\n```php\n\u003c?php\n// by array\n$collection-\u003egetDistinct('country', array('age' =\u003e array('$gte' =\u003e 25)));\n// by object\n$collection-\u003egetDistinct('country', $collection-\u003eexpression()-\u003ewhereGreater('age', 25));\n// by callable\n$collection-\u003egetDistinct('country', function($expression) { return $expression-\u003ewhereGreater('age', 25); });\n```\n\n### Extending Query Builder\n\nFor extending standart query builder class with custom condition methods you need to create expression class which extends `\\Sokil\\Mongo\\Expression`:\n\n```php\n\u003c?php\n\n// define expression\nclass UserExpression extends \\Sokil\\Mongo\\Expression\n{\n    public function whereAgeGreaterThan($age)\n    {\n        $this-\u003ewhereGreater('age', (int) $age);\n    }\n}\n```\n\nAnd then specify it in collection mapping:\n```php\n\u003c?php\n\n$client-\u003emap([\n    'myDb' =\u003e [\n        'user' =\u003e [\n            'class' =\u003e '\\UserCollection',\n            'expressionClass' =\u003e '\\UserExpression',\n        ],\n    ],\n]);\n```\n\nAlso there is _deprecated_ feature to override property `Collection::$_queryExpressionClass`:\n\n```php\n\u003c?php\n\n// define expression in collection\nclass UserCollection extends \\Sokil\\Mongo\\Collection\n{\n    protected $_queryExpressionClass = 'UserExpression';\n}\n```\n\nNow new expression methods available in the query buiilder:\n\n```php\n\u003c?php\n// use custom method for searching\n$collection = $db-\u003egetCollection('user'); // instance of UserCollection\n$queryBuilder = $collection-\u003efind(); // instance of UserExpression\n\n// now methods available in query buider\n$queryBuilder-\u003ewhereAgeGreaterThan(18)-\u003efetchRandom();\n\n// since v.1.3.2 also supported query builder configuration through callable:\n$collection\n    -\u003efind(function(UserExpression $e) {\n        $e-\u003ewhereAgeGreaterThan(18);\n    })\n    -\u003efetchRandom();\n```\n\n### Identity Map\n\nImagine that you have two different query builders and they are both\nreturn same document. Identity map helps us to get same instance of object\nfrom different queries, so if we made changes to document from first query,\nthat changes will be in document from second query:\n\n```php\n\u003c?php\n\n$document1 = $collection-\u003efind()-\u003ewhereGreater('age' \u003e 18)-\u003efindOne();\n$document2 = $collection-\u003efind()-\u003ewhere('gender', 'male')-\u003efindOne();\n\n$document1-\u003ename = 'Mary';\necho $document2-\u003ename; // Mary\n```\n\nThis two documents referenced same object. Collection by default store all requested documents to identity map. \nIf 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()](#getting-documents-by-id).\n\nIf 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. \n\nIf we know that documents never be reused, we can disable storing documents to identity map:\n\nDocument pool may be disabled or enabled in mapping. By default it is enabled:\n\n```php\n\u003c?php\n$collection-\u003emap([\n    'someDb' =\u003e [\n        'someCollection', array(\n            'documentPool' =\u003e false,\n        ),\n    ],\n]);\n\n```\n\n\n```php\n\u003c?php\n\n$collection-\u003edisableDocumentPool();\n```\n\nTo enable identity mapping:\n```php\n\u003c?php\n\n$collection-\u003eenableDocumentPool();\n```\n\nTo check if identity mapping enabled:\n```php\n\u003c?php\n\n$collection-\u003eisDocumentPoolEnabled();\n```\n\nTo clear pool identity map from previously stored documents:\n```php\n\u003c?php\n\n$collection-\u003eclearDocumentPool();\n```\n\nTo check if there are documents in map already:\n```php\n\u003c?php\n\n$collection-\u003eisDocumentPoolEmpty();\n```\n\nIf document already loaded, but it may be changed from another proces in db,\nthen your copy is not fresh. You can manually refresh document state\nsyncing it with db:\n```php\n\u003c?php\n\n$document-\u003erefresh();\n```\n\n### Comparing queries\n\nIf you want to cache your search results or want to compare two queries, you need some\nidentifier which unambiguously identify query. You can use `Cursor::getHash()` for\nthat reason. This hash uniquely identify just query parameners rather\nthan result set of documents, because it calculated from all query parameters:\n\n```php\n\u003c?php\n\n$queryBuilder = $this-\u003ecollection\n    -\u003efind()\n    -\u003efield('_id')\n    -\u003efield('interests')\n    -\u003esort(array(\n        'age' =\u003e 1,\n        'gender' =\u003e -1,\n    ))\n    -\u003elimit(10, 20)\n    -\u003ewhereAll('interests', ['php', 'snowboard']);\n\n$hash = $queryBuilder-\u003egetHash(); // will return 508cc93b371c222c53ae90989d95caae\n\nif($cache-\u003ehas($hash)) {\n    return $cache-\u003eget($hash);\n}\n\n$result = $queryBuilder-\u003efindAll();\n\n$cache-\u003eset($hash, $result);\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nGeospatial queries\n------------------\n\nBefore querying geospatial coordinates we need to create geospatial index\nand add some data.\n\nIndex 2dsphere available since MongoDB version 2.4 and may be created in few ways:\n```php\n\n\u003c?php\n// creates index on location field\n$collection-\u003eensure2dSphereIndex('location');\n// cerate compound index\n$collection-\u003eensureIndex(array(\n    'location' =\u003e '2dsphere',\n    'name'  =\u003e -1,\n));\n```\n\nGeo data can be added as array in [GeoJson](http://geojson.org/) format or\nusing GeoJson objects of library [GeoJson](https://github.com/jmikola/geojson):\n\nAdd data as GeoJson object\n```php\n\u003c?php\n\n$document-\u003esetGeometry(\n    'location',\n    new \\GeoJson\\Geometry\\Point(array(30.523400000000038, 50.4501))\n);\n\n$document-\u003esetGeometry(\n    'location',\n    new \\GeoJson\\Geometry\\Polygon(array(\n        array(24.012228, 49.831485), // Lviv\n        array(36.230376, 49.993499), // Harkiv\n        array(34.174927, 45.035993), // Simferopol\n        array(24.012228, 49.831485), // Lviv\n    ))\n);\n\n```\n\nData may be set througn array:\n```php\n\u003c?php\n\n// Point\n$document-\u003esetPoint('location', 30.523400000000038, 50.4501);\n// LineString\n$document-\u003esetLineString('location', array(\n    array(30.523400000000038, 50.4501),\n    array(36.230376, 49.993499),\n));\n// Polygon\n$document-\u003esetPolygon('location', array(\n    array(\n        array(24.012228, 49.831485), // Lviv\n        array(36.230376, 49.993499), // Harkiv\n        array(34.174927, 45.035993), // Simferopol\n        array(24.012228, 49.831485), // Lviv\n    ),\n));\n// MultiPoint\n$document-\u003esetMultiPoint('location', array(\n    array(24.012228, 49.831485), // Lviv\n    array(36.230376, 49.993499), // Harkiv\n    array(34.174927, 45.035993), // Simferopol\n));\n// MultiLineString\n$document-\u003esetMultiLineString('location', array(\n    // line string 1\n    array(\n        array(34.551416, 49.588264), // Poltava\n        array(35.139561, 47.838796), // Zaporizhia\n    ),\n    // line string 2\n    array(\n        array(24.012228, 49.831485), // Lviv\n        array(34.174927, 45.035993), // Simferopol\n    )\n));\n// MultiPolygon\n$document-\u003esetMultyPolygon('location', array(\n    // polygon 1\n    array(\n        array(\n            array(24.012228, 49.831485), // Lviv\n            array(36.230376, 49.993499), // Harkiv\n            array(34.174927, 45.035993), // Simferopol\n            array(24.012228, 49.831485), // Lviv\n        ),\n    ),\n    // polygon 2\n    array(\n        array(\n            array(24.012228, 49.831485), // Lviv\n            array(36.230376, 49.993499), // Harkiv\n            array(34.174927, 45.035993), // Simferopol\n            array(24.012228, 49.831485), // Lviv\n        ),\n    ),\n));\n// GeometryCollection\n$document-\u003esetGeometryCollection('location', array(\n    // point\n    new \\GeoJson\\Geometry\\Point(array(30.523400000000038, 50.4501)),\n    // line string\n    new \\GeoJson\\Geometry\\LineString(array(\n        array(30.523400000000038, 50.4501),\n        array(24.012228, 49.831485),\n        array(36.230376, 49.993499),\n    )),\n    // polygon\n    new \\GeoJson\\Geometry\\Polygon(array(\n        // line ring 1\n        array(\n            array(24.012228, 49.831485), // Lviv\n            array(36.230376, 49.993499), // Harkiv\n            array(34.174927, 45.035993), // Simferopol\n            array(24.012228, 49.831485), // Lviv\n        ),\n        // line ring 2\n        array(\n            array(34.551416, 49.588264), // Poltava\n            array(32.049226, 49.431181), // Cherkasy\n            array(35.139561, 47.838796), // Zaporizhia\n            array(34.551416, 49.588264), // Poltava\n        ),\n    )),\n));\n\n```\n\nQuery documents near point on flat surface, defined by latitude 49.588264 and\nlongitude 34.551416 and distance 1000 meters from this point:\n\n```php\n\u003c?php\n$collection-\u003efind()-\u003enearPoint('location', 34.551416, 49.588264, 1000);\n```\n\nThis query require `2dsphere` or `2d` indexes.\n\nDistance may be specified as array `[minDistance, maxDistance]`. This\nfeature allowed for MongoDB version 2.6 and greater. If some value\nempty, only existed value applied.\n\n```php\n\u003c?php\n// serch distance less 100 meters\n$collection-\u003efind()-\u003enearPoint('location', 34.551416, 49.588264, array(null, 1000));\n// search distabce between 100 and 1000 meters\n$collection-\u003efind()-\u003enearPoint('location', 34.551416, 49.588264, array(100, 1000));\n// search distabce greater than 1000 meters\n$collection-\u003efind()-\u003enearPoint('location', 34.551416, 49.588264, array(1000, null));\n```\n\nTo search on spherical surface:\n```php\n\u003c?php\n$collection-\u003efind()-\u003enearPointSpherical('location', 34.551416, 49.588264, 1000);\n```\n\nTo find geometries, which intersect specified:\n```php\n\u003c?php\n$this-\u003ecollection\n    -\u003efind()\n    -\u003eintersects('link', new \\GeoJson\\Geometry\\LineString(array(\n        array(30.5326905, 50.4020355),\n        array(34.1092134, 44.946798),\n    )))\n    -\u003efindOne();\n```\n\nTo select documents with geospatial data that exists entirely within a specified shape:\n```php\n\u003c?php\n$point = $this-\u003ecollection\n    -\u003efind()\n    -\u003ewithin('point', new \\GeoJson\\Geometry\\Polygon(array(\n        array(\n            array(24.0122356, 49.8326891), // Lviv\n            array(24.717129, 48.9117731), // Ivano-Frankivsk\n            array(34.1092134, 44.946798), // Simferopol\n            array(34.5572385, 49.6020445), // Poltava\n            array(24.0122356, 49.8326891), // Lviv\n        )\n    )))\n    -\u003efindOne();\n```\n\nSearch documents within flat circle:\n```php\n\u003c?php\n$this-\u003ecollection\n    -\u003efind()\n    -\u003ewithinCircle('point', 28.46963, 49.2347, 0.001)\n    -\u003efindOne();\n```\n\nSearch document within spherical circle:\n```php\n\u003c?php\n$point = $this-\u003ecollection\n    -\u003efind()\n    -\u003ewithinCircleSpherical('point', 28.46963, 49.2347, 0.001)\n    -\u003efindOne();\n```\n\nSearch documents with points (stored as legacy coordinates) within box:\n```php\n\u003c?php\n$point = $this-\u003ecollection\n    -\u003efind()\n    -\u003ewithinBox('point', array(0, 0), array(10, 10))\n    -\u003efindOne();\n```\n\nSearch documents with points (stored as legacy coordinates) within polygon:\n```php\n\u003c?php\n$point = $this-\u003ecollection\n    -\u003efind()\n    -\u003ewithinPolygon(\n        'point',\n        array(\n            array(0, 0),\n            array(0, 10),\n            array(10, 10),\n            array(10, 0),\n        )\n    )\n    -\u003efindOne();\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nFulltext search\n---------------\n\nBefore search field must be previously indexed as fulltext search field:\n\n```php\n\u003c?php\n\n// one field\n$collection-\u003eensureFulltextIndex('somefield');\n\n// couple of fields\n$collection-\u003eensureFulltextIndex(['somefield1', 'somefield2']);\n```\n\nSearching on fulltext field:\n\n```php\n\u003c?php\n\n$collection-\u003efind()-\u003ewhereText('string searched in all fulltext fields')-\u003efindAll();\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nPagination\n----------\n\nQuery builder allows you to create pagination.\n```php\n\u003c?php\n$paginator = $collection-\u003efind()-\u003ewhere('field', 'value')-\u003epaginate(3, 20);\n$totalDocumentNumber = $paginator-\u003egetTotalRowsCount();\n$totalPageNumber = $paginator-\u003egetTotalPagesCount();\n\n// iterate through documents\nforeach($paginator as $document) {\n    echo $document-\u003egetId();\n}\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nPersistence (Unit of Work)\n--------------------------\n\nInstead 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.\n\nLets create persistance manager\n```php\n\u003c?php\n$persistence = $client-\u003ecreatePersistence();\n```\n\nNow we can add some documents to be saved or removed later\n```php\n\u003c?php\n$persistence-\u003epersist($document1);\n$persistence-\u003epersist($document2);\n\n$persistence-\u003eremove($document3);\n$persistence-\u003eremove($document4);\n```\n\nIf later we decice do not save or remove document, we may detach it from persistence manager\n```php\n\u003c?php\n$persistence-\u003edetach($document1);\n$persistence-\u003edetach($document3);\n```\n\nOr we even may remove them all:\n```php\n\u003c?php\n$persistence-\u003eclear();\n```\n\nNote 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.\n\nIf we decide to store changes to databasae we may flush this changes:\n```php\n\u003c?php\n$persistence-\u003eflush();\n```\n\nNote that persisted documents do not deleted from persistence manager after flush, but removed will be deleted.\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nDeleting collections and documents\n-----------------------------------\n\nDeleting of collection:\n```php\n\u003c?php\n$collection-\u003edelete();\n```\n\nDeleting of document:\n```php\n\u003c?php\n$document-\u003edelete();\n```\n\nDeleting of few documents by expression:\n```php\n\u003c?php\n\n$collection-\u003ebatchDelete($collection-\u003eexpression()-\u003ewhere('param', 'value'));\n// deprecated since 1.13\n$collection-\u003edeleteDocuments($collection-\u003eexpression()-\u003ewhere('param', 'value'));\n```\nAlso supported `\\MongoDeleteBatch` through interface:\n\n```php\n\u003c?php\n$batch = $collection-\u003ecreateBatchDelete();\n$batch\n    -\u003edelete(array('a' =\u003e 2))\n    -\u003edelete($collection-\u003eexpression()-\u003ewhere('a', 4))\n    -\u003edelete(function(Expression $e) { $e-\u003ewhere('a', 6); })\n    -\u003eexecute();\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nAggregation framework\n--------------------------------\n\nCreate aggregator:\n```php\n\u003c?php\n$aggregator = $collection-\u003ecreateAggregator();\n````\n\nThan you need to configure aggregator by pipelines.\n```php\n\u003c?php\n// through array\n$aggregator-\u003ematch(array(\n    'field' =\u003e 'value'\n));\n// through callable\n$aggregator-\u003ematch(function($expression) {\n    $expression-\u003ewhereLess('date', new \\MongoDate);\n});\n```\n\nTo get results of aggregation after configuring pipelines:\n```php\n\u003c?php\n/**\n * @var array list of aggregation results\n */\n$result = $aggregator-\u003eaggregate();\n// or\n$result = $collection-\u003eaggregate($aggregator);\n```\n\nYou can execute aggregation without previously created aggregator:\n\n```php\n\u003c?php\n// by array\n$collection-\u003eaggregate(array(\n    array(\n        '$match' =\u003e array(\n            'field' =\u003e 'value',\n        ),\n    ),\n));\n// or callable\n$collection-\u003eaggregate(function($aggregator) {\n    $aggregator-\u003ematch(function($expression) {\n        $expression-\u003ewhereLess('date', new \\MongoDate);\n    });\n});\n```\n#### Options\n\nAvailable aggregation options may be found at \nhttps://docs.mongodb.org/manual/reference/command/aggregate/#dbcmd.aggregate.\n\nOptions may be passed as argument of `aggregate` method:\n\n```php\n\u003c?php\n\n// as argument of Pipeline::aggregate\n$collection-\u003ecreateAggregator()-\u003ematch()-\u003egroup()-\u003eaggregate($options);\n\n// as argument of Collection::aggregate\n$collection-\u003eaggregate($pipelines, $options);\n\n// as calling of Pipeline methods\n$collection\n    -\u003ecreateAggregator()\n    -\u003eexplain()\n    -\u003eallowDiskUse()\n    -\u003esetBatchSize(100);\n```\n\n#### Debug\n\nIf client in debug mode and logger configured, pipelines will be logged.\nThere is ability to get explanation of aggregation:\n\n```php\n\u003c?php\n\n// set explain option\n$collection-\u003eaggregate($pipelines, ['explain' =\u003e true]);\n\n// or configure pipeline\n$collection-\u003ecreateAggregator()-\u003ematch(...)-\u003egroup(...)-\u003eexplain()-\u003eaggregate();\n```\n\n#### Aggregation cursor\n\n`Collection::aggregate` return array as result, but also iterator may be optained:\nRead more at http://php.net/manual/ru/mongocollection.aggregatecursor.php.\n\n```php\n\u003c?php\n\n// set as argument\n$asCursor = true;\n$collection-\u003eaggregate($pipelines, $options, $asCursor);\n\n// or call method\n$cursor = $collection-\u003ecreateAggregator()-\u003ematch()-\u003egroup()-\u003eaggregateCursor();\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nEvents\n-------\n\nEvent support based on Symfony's\n[Event Dispatcher](http://symfony.com/doc/current/components/event_dispatcher/introduction.html)\ncomponent. You can attach and trigger\nany event you want, but there are some already defined events:\n\n| Event name     | Description                                                |\n| -------------- | ---------------------------------------------------------- |\n| afterConstruct | Already after construct executed                           |\n| beforeValidate | Before document validation                                 |\n| afterValidate  | After document validation                                  |\n| validateError  | After document validation when document is invalid         |\n| beforeInsert   | Before document will insert to collection                  |\n| afterInsert    | After successfull insert                                   |\n| beforeUpdate   | Before document will be updated                            |\n| afterUpdate    | After successfull update of document                       |\n| beforeSave     | Before insert or update of document                        |\n| afterSave      | After insert or update of document                         |\n| beforeDelete   | Before delete of document                                  |\n| afterDelete    | After delete of document                                   |\n\nEvent listener is a function that calls when event triggered:\n\n```php\n\u003c?php\n$listener = function(\n    \\Sokil\\Mongo\\Event $event, // instance of event\n    string $eventName, // event name\n    \\Symfony\\Component\\EventDispatcher\\EventDispatcher $eventDispatcher // instance of dispatcher\n) {}\n```\n\nEvent listener may be attached by method `Document::attachEvent()`:\n```php\n\u003c?php\n$document-\u003eattachEvent('myOwnEvent', $listener, $priority);\n```\n\nIt also may be attached through helper methods:\n```php\n\u003c?php\n$document-\u003eonMyOwnEvent($listener, $priority);\n// which is equals to\n$this-\u003eattachEvent('myOwnEvent', $listener, $priority);\n```\n\nEvent may be attached in runtime or in `Document` class by override `Document::beforeConstruct()` method:\n```php\n\u003c?php\nclass CustomDocument extends \\Sokil\\Mongo\\Document\n{\n    public function beforeConstruct()\n    {\n        $this-\u003eonBeforeSave(function() {\n            $this-\u003eset('date' =\u003e new \\MongoDate);\n        });\n    }\n}\n```\n\nEvent may be triggered to call all attached event listeners:\n```php\n\u003c?php\n$this-\u003etriggerEvent('myOwnEvent');\n```\n\nYou can create your own event class, which extends `\\Sokil\\Mongo\\Event' and pass it to listeners.\nThis allows you to pass some data to listener:\n\n```php\n\u003c?php\n// create class\nclass OwnEvent extends \\Sokil\\Mongo\\Event {\n    public $status;\n}\n\n// define listener\n$document-\u003eattachEvent('someEvent', function(\\OwnEvent $event) {\n    echo $event-\u003estatus;\n});\n\n// configure event\n$event = new \\OwnEvent;\n$event-\u003estatus = 'ok';\n\n// trigger event\n$this-\u003etriggerEvent('someEvent', $event);\n```\n\nTo cancel operation execution on some condition use event handling cancel:\n```php\n\u003c?php\n$document-\u003eonBeforeSave(function(\\Sokil\\Mongo\\Event $event) {\n    if($this-\u003eget('field') === 42) {\n        $event-\u003ecancel();\n    }\n})\n-\u003esave();\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nBehaviors\n----------\n\nBehavior is a posibility to extend functionality of document object and reuse\ncode among documents of different class.\n\nBehavior is a class extended from `\\Sokil\\Mongo\\Behavior`. Any public method may be\naccessed through document, where behavior is attached.\n\n```php\n\u003c?php\nclass SomeBehavior extends \\Sokil\\Mongo\\Behavior\n{\n    public function return42()\n    {\n        return 42;\n    }\n}\n```\n\nTo get instance of object, to which behavior is attached, call `Behavior::getOwner()` method:\n```php\n\u003c?php\nclass SomeBehavior extends \\Sokil\\Mongo\\Behavior\n{\n    public function getOwnerParam($selector)\n    {\n        return $this-\u003egetOwner()-\u003eget($selector);\n    }\n}\n```\n\nYou can add behavior in document class:\n```php\n\u003c?php\nclass CustomDocument extends \\Sokil\\Mongo\\Document\n{\n    public function behaviors()\n    {\n        return [\n            '42behavior' =\u003e '\\SomeBehavior',\n        ];\n    }\n}\n```\n\nYou can attach behavior in runtime too:\n```php\n\u003c?php\n// single behavior\n$document-\u003eattachBehavior('42behavior', '\\SomeBehavior');\n// set of behaviors\n$document-\u003eattachBehaviors([\n    '42behavior' =\u003e '\\SomeBehavior',\n]);\n```\n\nBehaviors may be defined as fully qualified class names, arrays, or `Behavior` instances:\n```php\n\u003c?php\n// class name\n$document-\u003eattachBehavior('42behavior', '\\SomeBehavior');\n\n// array with parameters\n$document-\u003eattachBehavior('42behavior', [\n    'class'     =\u003e '\\SomeBehavior',\n    'param1'    =\u003e 1,\n    'param2'    =\u003e 2,\n]);\n\n// Behavior instance\n$document-\u003eattachBehavior('42behavior', new \\SomeBehavior([\n    'param1'    =\u003e 1,\n    'param2'    =\u003e 2,\n]);\n```\n\nThen you can call any methods of behaviors. This methods searches in order of atraching behaviors:\n```php\n\u003c?php\necho $document-\u003ereturn42();\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nRelations\n-------------\n\nYou can define relations between different documents, which helps you to load related documents.\n\nTo define relation to other document you need to override `Document::relations()` method and return array of relations in format `[relationName =\u003e [relationType, targetCollection, reference], ...]`. \n\nAlso you can define relations in mapping:\n```php\n\u003c?php\n\n$collection-\u003emap([\n    'someDb' =\u003e [\n        'someCollection', array(\n            'relations'     =\u003e array(\n                'someRelation'   =\u003e array(self::RELATION_HAS_ONE, 'profile', 'user_id'),\n            ),\n        ),\n    ],\n]);\n```\n\nIf relation specified both in mapping and document class, then mapping relation merged into document's relations, so mapping relations has more priority.\n\n### One-to-one relation\n\nWe have to classes User and Profile. User has one profile, and profile belongs to User.\n\n```php\n\u003c?php\nclass User extends \\Sokil\\Mongo\\Document\n{\n    protected $schema = [\n        'email'     =\u003e null,\n        'password'  =\u003e null,\n    ];\n\n    public function relations()\n    {\n        return [\n            'profileRelation' =\u003e [self::RELATION_HAS_ONE, 'profile', 'user_id'],\n        ];\n    }\n}\n\nclass Profile extends \\Sokil\\Mongo\\Document\n{\n    protected $schema = [\n        'name' =\u003e [\n            'last'  =\u003e null,\n            'first' =\u003e null,\n        ],\n        'age'   =\u003e null,\n    ];\n\n    public function relations()\n    {\n        return [\n            'userRelation' =\u003e [self::RELATION_BELONGS, 'user', 'user_id'],\n        ];\n    }\n}\n```\n\nNow we can lazy load related documnts just calling relation name:\n```php\n\u003c?php\n$user = $userColletion-\u003egetDocument('234...');\necho $user-\u003eprofileRelation-\u003eget('age');\n\n$profile = $profileCollection-\u003egetDocument('234...');\necho $pfofile-\u003euserRelation-\u003eget('email');\n```\n\n### One-to-many relation\n\nOne-to-many relation helps you to load all related documents. Class User has few posts of class Post:\n\n```php\n\u003c?php\nclass User extends \\Sokil\\Mongo\\Document\n{\n    protected $schema = [\n        'email'     =\u003e null,\n        'password'  =\u003e null,\n    ];\n\n    public function relations()\n    {\n        return [\n            'postsRelation' =\u003e [self::RELATION_HAS_MANY, 'posts', 'user_id'],\n        ];\n    }\n}\n\nclass Posts extends \\Sokil\\Mongo\\Document\n{\n    protected $schema = [\n        'user_id' =\u003e null,\n        'message'   =\u003e null,\n    ];\n\n    public function relations()\n    {\n        return [\n            'userRelation' =\u003e [self::RELATION_BELONGS, 'user', 'user_id'],\n        ];\n    }\n\n    public function getMessage()\n    {\n        return $this-\u003eget('message');\n    }\n}\n```\n\nNow you can load related posts of document:\n```php\n\u003c?php\nforeach($user-\u003epostsRelation as $post) {\n    echo $post-\u003egetMessage();\n}\n```\n\n### Many-to-many relation\n\nMany-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.\n\n\n```php\n\u003c?php\n\n// this document contains field 'driver_id' where array of ids stored\nclass CarDocument extends \\Sokil\\Mongo\\Document\n{\n    protected $schema = [\n        'brand' =\u003e null,\n    ];\n\n    public function relations()\n    {\n        return array(\n            'drivers'   =\u003e array(self::RELATION_MANY_MANY, 'drivers', 'driver_id', true),\n        );\n    }\n}\n\nclass DriverDocument extends \\Sokil\\Mongo\\Document\n{\n    protected $schema = [\n        'name' =\u003e null,\n    ];\n\n    public function relations()\n    {\n        return array(\n            'cars'    =\u003e array(self::RELATION_MANY_MANY, 'cars', 'driver_id'),\n        );\n    }\n}\n```\n\nNow you can load related documents:\n```php\n\u003c?php\nforeach($car-\u003edrivers as $driver) {\n    echo $driver-\u003ename;\n}\n```\n\n### Add relation\n\nThere is helper to add related document, if you don't\nwant modify relation field directly:\n\n```php\n\u003c?php\n$car-\u003eaddRelation('drivers', $driver);\n```\n\nThis helper automatically resolves collection and field\nwhere to store relation data.\n\n### Remove relation\n\nThere is helper to remove related document, if you don't\nwant modify relation field directly:\n\n```php\n\u003c?php\n$car-\u003eremoveRelation('drivers', $driver);\n```\n\nThis helper automatically resolves collection and field\nwhere to remove relation data. If relation type is `HAS_MANY` or `BELONS_TO`,\nsecond parameter wich defined related object may be omitted.\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nConcurency\n----------\n\n### Optimistic locking\n\nTo enable optimistic locking, specify lock mode in mapping:\n```php\nuse Sokil\\Mongo\\Collection\\Definition;\nuse Sokil\\Mongo\\Document\\OptimisticLockFailureException;\n\n$client-\u003emap([\n    'db' =\u003e [\n        'col' =\u003e [\n            'lock' =\u003e Definition::LOCK_OPTIMISTIC,\n        ],\n    ]\n]);\n```\n\nNow when some process try to update already updated document, exception\n`\\Sokil\\Mongo\\Document\\OptimisticLockFailureException` will be thrown.\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nRead preferences\n----------------\n[Read preference](http://docs.mongodb.org/manual/core/read-preference/) describes how MongoDB clients route read operations to members of a replica set. You can configure read preferences at any level:\n\n```php\n\u003c?php\n// in constructor\n$client = new Client($dsn, array(\n    'readPreference' =\u003e 'nearest',\n));\n\n// by passing to \\Sokil\\Mongo\\Client instance\n$client-\u003ereadNearest();\n\n// by passing to database\n$database = $client-\u003egetDatabase('databaseName')-\u003ereadPrimaryOnly();\n\n// by passing to collection\n$collection = $database-\u003egetCollection('collectionName')-\u003ereadSecondaryOnly();\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nWrite concern\n-------------\n[Write concern](http://docs.mongodb.org/manual/core/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:\n\n```php\n\u003c?php\n\n// by passing to \\Sokil\\Mongo\\Client instance\n$client-\u003esetMajorityWriteConcern(10000);\n\n// by passing to database\n$database = $client-\u003egetDatabase('databaseName')-\u003esetMajorityWriteConcern(10000);\n\n// by passing to collection\n$collection = $database-\u003egetCollection('collectionName')-\u003esetWriteConcern(4, 1000);\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nCapped collections\n------------------\n\nTo use capped collection you need previously to create it:\n```php\n\u003c?php\n$numOfElements = 10;\n$sizeOfCollection = 10*1024;\n$collection = $database-\u003ecreateCappedCollection('capped_col_name', $numOfElements, $sizeOfCollection);\n```\n\nNow you can add only 10 documents to collection. All old documents will ve rewritted ny new elements.\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nExecuting commands\n------------------\n\nCommand is universal way to do anything with mongo. Let's get stats of collection:\n```php\n\u003c?php\n$collection = $database-\u003ecreateCappedCollection('capped_col_name', $numOfElements, $sizeOfCollection);\n$stats = $database-\u003eexecuteCommand(['collstat' =\u003e 'capped_col_name']);\n```\n\nResult in $stats:\n```\narray(13) {\n  'ns' =\u003e  string(29) \"test.capped_col_name\"\n  'count' =\u003e  int(0)\n  'size' =\u003e  int(0)\n  'storageSize' =\u003e  int(8192)\n  'numExtents' =\u003e  int(1)\n  'nindexes' =\u003e  int(1)\n  'lastExtentSize' =\u003e  int(8192)\n  'paddingFactor' =\u003e  double(1)\n  'systemFlags' =\u003e  int(1)\n  'userFlags' =\u003e  int(1)\n  'totalIndexSize' =\u003e  int(8176)\n  'indexSizes' =\u003e  array(1) {\n    '_id_' =\u003e    int(8176)\n  }\n  'ok' =\u003e  double(1)\n}\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nQueue\n-----\n\nQueue gives functionality to send messages from one process and get them in another process. Messages can be send to different channels.\n\nSending message to queue with default priority:\n```php\n\u003c?php\n$queue = $database-\u003egetQueue('channel_name');\n$queue-\u003eenqueue('world');\n$queue-\u003eenqueue(['param' =\u003e 'value']);\n```\n\nSend message with priority\n```php\n\u003c?php\n$queue-\u003eenqueue('hello', 10);\n```\n\nReading messages from channel:\n```php\n\u003c?php\n$queue = $database-\u003egetQueue('channel_name');\necho $queue-\u003edequeue(); // hello\necho $queue-\u003edequeue(); // world\necho $queue-\u003edequeue()-\u003eget('param'); // value\n```\n\nNumber of messages in queue\n```php\n\u003c?php\n$queue = $database-\u003egetQueue('channel_name');\necho count($queue);\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nMigrations\n----------\n\nMigrations 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:\n```bash\ncomposer require sokil/php-mongo-migrator\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nGridFS\n------\n\nGridFS allows you to store binary data in mongo database. Details at http://docs.mongodb.org/manual/core/gridfs/.\n\nFirst get instance of GridFS. You can specify prefix for partitioning filesystem:\n\n```php\n\u003c?php\n$imagesFS = $database-\u003egetGridFS('image');\n$cssFS = $database-\u003egetGridFS('css');\n```\n\nNow you can store file, located on disk:\n```php\n\u003c?php\n$id = $imagesFS-\u003estoreFile('/home/sokil/images/flower.jpg');\n```\n\nYou can store file from binary data:\n```php\n\u003c?php\n$id1 = $imagesFS-\u003estoreBytes('some text content');\n$id2 = $imagesFS-\u003estoreBytes(file_get_contents('/home/sokil/images/flower.jpg'));\n```\n\nYou are able to store some metadata with every file:\n```php\n\u003c?php\n$id1 = $imagesFS-\u003estoreFile('/home/sokil/images/flower.jpg', [\n    'category'  =\u003e 'flower',\n    'tags'      =\u003e ['flower', 'static', 'page'],\n]);\n\n$id2 = $imagesFS-\u003estoreBytes('some text content', [\n    'category' =\u003e 'books',\n]);\n```\n\nGet file by id:\n```php\n\u003c?php\n$imagesFS-\u003egetFileById('6b5a4f53...42ha54e');\n```\n\nFind file by metadata:\n```php\n\u003c?php\nforeach($imagesFS-\u003efind()-\u003ewhere('category', 'books') as $file) {\n    echo $file-\u003egetFilename();\n}\n```\n\nDeleting files by id:\n```php\n\u003c?php\n$imagesFS-\u003edeleteFileById('6b5a4f53...42ha54e');\n```\n\n##### Reading of file content\n\n```php\n\u003c?php\n// dump binary data to file\n$file-\u003edump($filename);\n\n// get binary data\n$file-\u003egetBytes();\n\n// get resource\n$file-\u003egetResource();\n```\n\nIf you want to use your own `GridFSFile` classes, you need to define mapping, as it does with collections:\n```php\n\u003c?php\n// define mapping of prefix to GridFS class\n$database-\u003emap([\n    'GridFSPrefix' =\u003e '\\GridFSClass',\n]);\n\n// define GridFSFile class\nclass GridFSClass extends \\Sokil\\Mongo\\GridFS\n{\n    public function getFileClassName(\\MongoGridFSFile $fileData = null)\n    {\n        return '\\GridFSFileClass';\n    }\n}\n\n// define file class\nclass GridFSFileClass extends \\Sokil\\Mongo\\GridFSFile\n{\n    public function getMetaParam()\n    {\n        return $this-\u003eget('meta.param');\n    }\n}\n\n// get file as instance of class \\GridFSFileClass\n$database-\u003egetGridFS('GridFSPrefix')-\u003egetFileById($id)-\u003egetMetaParam();\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nVersioning\n----------\n\nVersioninbg allows you to have history of all document changes. To enable versioning of documents in collection, you can set protected\nproperty `Collection::$versioning` to `true`, call `Collection::enableVersioning()`\nmethod or define versioning in mapping.\n\n```php\n\u003c?php\n// througn protected property\nclass MyCollection extends \\Sokil\\Mongo\\Collection\n{\n    protected $versioning = true;\n}\n\n// througn method\n$collection = $database-\u003egetCollection('my');\n$collection-\u003eenableVersioning();\n\n// through mapping\n$database-\u003emap('someCollectionName', [\n    'versioning' =\u003e true,\n]);\n```\n\nTo check if documents in collections is versioned call:\n\n```php\n\u003c?php\nif($collection-\u003eisVersioningEnabled()) {}\n```\n\nRevision is an instance of class `\\Sokil\\Mongo\\Revision` and inherits `\\Sokil\\Mongo\\Document`,\nso any methods of document may be applied to revision. Revisions may be accessed:\n```php\n\u003c?php\n// get all revisions\n$document-\u003egetRevisions();\n\n// get slice of revisions\n$limit = 10;\n$offset = 15;\n$document-\u003egetRevisions($limit, $offset);\n```\n\nTo get one revision by id use:\n```php\n\u003c?php\n$revision = $document-\u003egetRevision($revisionKey);\n```\n\nTo get count of revisions:\n```php\n\u003c?php\n$count = $document-\u003egetRevisionsCount();\n```\n\nTo clear all revisions:\n```php\n\u003c?php\n$document-\u003eclearRevisions();\n```\n\nRevisions stored in separate collection, named `{COLLECTION_NAME}.revisions`\nTo obtain original document of collection `{COLLECTION_NAME}` from revision,\nwhich is document of collection `{COLLECTION_NAME}.revisions`,\nuse `Revision::getDocument()` method:\n\n```php\n\u003c?php\n$document-\u003egetRevision($revisionKey)-\u003egetDocument();\n```\n\nProperties of document from revision may be accessed directly:\n```\necho $document-\u003eproperty;\necho $document-\u003egetRevision($revisionKey)-\u003eproperty;\n```\n\nAlso date of creating revison may be obtained from document:\n```php\n\u003c?php\n// return timestamp\necho $document-\u003egetRevision($revisionKey)-\u003egetDate();\n// return formatted date string\necho $document-\u003egetRevision($revisionKey)-\u003egetDate('d.m.Y H:i:s');\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nIndexes\n-------\n\nCreate index with custom options (see options in http://php.net/manual/en/mongocollection.ensureindex.php):\n```php\n\u003c?php\n$collection-\u003eensureIndex('field', [ 'unique' =\u003e true ]);\n```\n\nCreate unique index:\n```php\n\u003c?php\n$collection-\u003eensureUniqueIndex('field');\n```\n\nCreate sparse index (see http://docs.mongodb.org/manual/core/index-sparse/ for details about sparse indexes):\n```php\n\u003c?php\n$collection-\u003eensureSparseIndex('field');\n```\n\nCreate TTL index (see http://docs.mongodb.org/manual/tutorial/expire-data/ for details about TTL indexes):\n```php\n\u003c?php\n$collection-\u003eensureTTLIndex('field');\n```\n\nYou may define field as array where key is field name and value is direction:\n```php\n\u003c?php\n$collection-\u003eensureIndex(['field' =\u003e 1]);\n```\n\nAlso you may define compound indexes:\n```php\n\u003c?php\n$collection-\u003eensureIndex(['field1' =\u003e 1, 'field2' =\u003e -1]);\n```\n\nYou may define all collection indexes in property `Collection::$_index`\nas array, where each item is an index definition.\nEvery index definition must contain key `keys` with list of fields and orders,\nand optional options, as described in http://php.net/manual/en/mongocollection.createindex.php.\n\n```php\n\u003c?php\nclass MyCollection extends \\Sokil\\Mongo\\Collection\n{\n    protected $_index = array(\n        array(\n            'keys' =\u003e array('field1' =\u003e 1, 'field2' =\u003e -1),\n            'unique' =\u003e true\n        ),\n    );\n}\n```\n\nThen you must create this indexes by call of `Collection::initIndexes()`:\n\n```php\n\u003c?php\n$collection = $database-\u003egetCollection('myCollection')-\u003einitIndexes();\n```\n\nYou may use [Mongo Migrator](https://github.com/sokil/php-mongo-migrator) package\nto ensure indexes in collections from migration scripts.\n\n[Query optimiser](http://docs.mongodb.org/manual/core/query-plans/#read-operations-query-optimization)\n automatically choose which index to use, but you can manuallty define it:\n\n```php\n\u003c?php\n$collection-\u003efind()-\u003ewhere('field', 1)-\u003ehind(array('field' =\u003e 1));\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nCaching and documents with TTL\n------------------------------\n\nIf you want to get collection where documents will expire after some specified time,\njust add special index to this collection.\n\n```php\n\u003c?php\n$collection-\u003eensureTTLIndex('createDate', 1000);\n```\n\nYou can do this also in migration script, using [Mongo Migrator](https://github.com/sokil/php-mongo-migrator).\nFor details see related documentation.\n\nYou also can use `\\Sokil\\Mongo\\Cache` class, which already implement this functionality and compatible with PSR-16 interface.\n\n```php\n\u003c?php\n\n// Get cache instance\n$cache = $database-\u003egetCache('some_namespace');\n```\n\nNamespace is a name of collection to be created in database.\n\nBefore use cache must be initialised by calling method `Cache:init()`:\n\n```php\n\u003c?php\n$cahce-\u003einit();\n```\n\nThis operation creates index with `expireAfterSecond` key in collection `some_namespace`.\n\nThis operation may be done in some console command or migration script, e.g. by using migration tool [sokil/php-mongo-migrator](https://github.com/sokil/php-mongo-migrator), or\nyou can create manually in mongo console:\n\n```javascript\ndb.some_namespace.ensureIndex('e', {expireAfterSeconds: 0});\n```\n\nNow you can store new value with:\n```php\n\u003c?php\n// this store value for 10 seconds\n// expiration defined relatively to current time\n$cache-\u003eset('key', 'value', 10);\n```\n\nYou can define value which never expired and must be deleted manually:\n```php\n\u003c?php\n$cache-\u003eset('key', 'value', null);\n```\n\nYou can define some tags defined with key:\n```php\n\u003c?php\n$cache-\u003eset('key', 'value', 10, ['php', 'c', 'java']);\n```\n\nTo get value\n```php\n\u003c?php\n$value = $cache-\u003eget('key');\n```\n\nTo delete cached value by key:\n```php\n\u003c?php\n$cache-\u003edelete('key');\n```\n\nDelete few values by tags:\n```php\n\u003c?php\n// delete all values with tag 'php'\n$cache-\u003edeleteMatchingTag('php');\n// delete all values without tag 'php'\n$cache-\u003edeleteNotMatchingTag('php');\n// delete all values with tags 'php' and 'java'\n$cache-\u003edeleteMatchingAllTags(['php', 'java']);\n// delete all values which don't have tags 'php' and 'java'\n$cache-\u003edeleteMatchingNoneOfTags(['php', 'java']);\n// Document deletes if it contains any of passed tags\n$cache-\u003edeleteMatchingAnyTag(['php', 'elephant']);\n// Document deletes if it contains any of passed tags\n$cache-\u003edeleteNotMatchingAnyTag(['php', 'elephant']);\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\nDebugging\n---------\n\nIn debug mode client may log some activity to pre-configured logger or show extended errors.\n```php\n\u003c?php\n\n// start debugging\n$client-\u003edebug();\n\n// stop debugging\n$client-\u003edebug(false);\n\n// check debug state\n$client-\u003eisDebugEnabled();\n```\n\n### Logging\n\nLibrary suports logging of queries. To configure logging, you need to pass logger object to instance of `\\Sokil\\Mongo\\Client` and enable debug of client. \nLogger must implement `\\Psr\\Log\\LoggerInterface` due to [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md):\n\n```php\n\u003c?php\n$client = new Client($dsn);\n$client-\u003esetLogger($logger);\n```\n\n### Profiling\n\nMore details about profiling at [Analyze Performance of Database Operations](http://docs.mongodb.org/manual/tutorial/manage-the-database-profiler/)\nprofiler data stores to `system.profile` collection, which you can query through query builder:\n\n```php\n\u003c?php\n\n$qb = $database\n    -\u003efindProfilerRows()\n    -\u003ewhere('ns', 'social.users')\n    -\u003ewhere('op', 'update');\n```\n\nStructure of document described in article [Database Profiler Output](http://docs.mongodb.org/manual/reference/database-profiler/)\n\nThere is three levels of profiling, described in article [Profile command](http://docs.mongodb.org/manual/reference/command/profile/).\nSwitching between then may be done by calling methods:\n\n```php\n\u003c?php\n\n// disable profiles\n$database-\u003edisableProfiler();\n\n// profile slow queries slower than 100 milliseconds\n$database-\u003eprofileSlowQueries(100);\n\n// profile all queries\n$database-\u003eprofileAllQueries();\n```\n\nTo get current level of profiling, call:\n```php\n\u003c?php\n$params = $database-\u003egetProfilerParams();\necho $params['was'];\necho $params['slowms'];\n\n// or directly\n$level = $database-\u003egetProfilerLevel();\n$slowms = $database-\u003egetProfilerSlowMs();\n```\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n\nUnit tests\n---------\n\n[![Build Status](https://travis-ci.org/sokil/php-mongo.png?branch=master\u00261)](https://travis-ci.org/sokil/php-mongo)\n[![Coverage Status](https://coveralls.io/repos/sokil/php-mongo/badge.png)](https://coveralls.io/r/sokil/php-mongo)\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/sokil/php-mongo/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/sokil/php-mongo/?branch=master)\n[![Code Climate](https://codeclimate.com/github/sokil/php-mongo/badges/gpa.svg)](https://codeclimate.com/github/sokil/php-mongo)\n[![SensioLabsInsight](https://insight.sensiolabs.com/projects/45b7bd7f-9145-49af-8d6a-9380f14e12b6/mini.png)](https://insight.sensiolabs.com/projects/45b7bd7f-9145-49af-8d6a-9380f14e12b6)\n\n### Local PHPUnit tests\n\nTo start unit tests, run:\n\n```\n./vendor/bin/phpunit -c tests/phpunit.xml tests\n```\n\n### Docker PHPUnit tests\n\nAlso available Docker containers. \nThey start with xdebug enabled, so you can sign in to any container and debug code there. \n\nTo start tests on all supported PHP and MongoDB versions, run \n\n```\n./run-docker-tests.sh\n```\n\nTo run test on concrete platforms, specify them:\n```\n./run-docker-tests.sh -p 56 -p 70 -m 30 -m 32\n```\n\nTo run concrete test, pass it:\n```\n./run-docker-tests.sh -t DocumentTest.php\n```\n\nTo run concrete method of test, pass it:\n```\n./run-docker-tests.sh -t DocumentTest.php -f ::testElemMatch\n```\n\nTests may be found at `./share/phpunit` dir after finishing tests. \n\n\nContributing\n------------\n\nPull requests, bug reports and feature requests is welcome. Please see [CONTRIBUTING](https://github.com/sokil/php-mongo/blob/master/CONTRIBUTING.md) for details.\n\n\nChange log\n----------\n\nPlease see [CHANGELOG](https://github.com/sokil/php-mongo/blob/master/CHANGELOG.md) for more information on what has changed recently.\n\n\nLicense\n-------\n\nThe MIT License (MIT). Please see [License File](https://github.com/sokil/php-mongo/blob/master/LICENSE) for more information.\n\n[badge-totalDownloads-img]: http://img.shields.io/packagist/dt/sokil/php-mongo.svg?1\n[badge-totalDownloads-url]: https://packagist.org/packages/sokil/php-mongo\n","funding_links":[],"categories":["Libraries","目录","Table of Contents","PHP","NoSQL NoSQL","非关系型数据库( NoSQL )"],"sub_categories":["PHP","NoSQL"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsokil%2Fphp-mongo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsokil%2Fphp-mongo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsokil%2Fphp-mongo/lists"}