grefon / mongo-model
MongoDb Model
Requires
- php: >=7.3
- ext-json: *
- ext-mbstring: *
- ext-mongodb: *
- mongodb/mongodb: ^1.5.1
README
MongoModel allows you to conveniently work with MongoDB documents, as with objects.
Implemented:
- typification of properties
- required properties
- default values
- history of changes
- operations with objects
MongoModel work with MongoDB PHP Library
- Getting Started
- $fieldsModel
- Creating a new object
- Operations with an object
- Operations with objects
- History of changes
- To help the developer
Getting Started
Composer
Install this package through Composer.
Edit require
in your composer.json
:
{ "require": { "grefon/mongo-model": "*" } }
and run composer update
or
run this command in your command line:
composer require grefon/mongo-model
Initialization of connection
// Loading composer require __DIR__ . '/vendor/autoload.php'; use MongoDB\Client; use MongoModel\MongoDB; // Initialization of connection to MongoDB /** * @param Client $client - MongoDB Client * @param string $base - name of base * @param bool $debug - debug */ MongoDB::init(new Client('mongodb://127.0.0.1/'), 'baseName', true);
The MongoDB
class with debugging enabled will log requests and their status to the MongoDB::$trace
.
Your first model
Create collection users
in MongoDB.
Create new PHP Class User
extends ModelMongoDB
:
use MongoModel\ModelMongoDB; /** * User Class * * @property string userId * @property string surname * @property string name * @property string email * @property int rating * @property boolean ban * @property string timeCreate * @property string timeUpdate * */ class User extends ModelMongoDB { static protected $collection; static public $collectionName = 'users'; static public $primaryKey = 'userId'; static public $fieldsModel = [ 'userId' => ['string'], 'surname' => ['string'], 'name' => ['string', 'required'], 'email' => ['string'], 'rating' => ['int', 'default' => 0], 'ban' => ['boolean', 'default' => false], 'timeCreate' => ['datetime', 'timeCreate'], 'timeUpdate' => ['datetime', 'timeUpdate'] ]; }
Each model class must extend ModelMongoDB
. It must contain 4 main static variables:
$collection
- this variable will store an instance \MongoDB\Collection$collectionName
- collection name in MongoDB$primaryKey
- primary key name instead of_id
$fieldsModel
- array of model fields (object properties)
$fieldsModel
In $fieldsModel the data model is described as an associative array. The key is the name of the object's property, the value is an array of the property's attributes.
The first value in an attribute array must always be the data type.
Type | Description |
---|---|
string | string |
boolean or bool | boolean true or false |
int | number |
float | floating point number |
datetime | in PHP will be a string (2022-10-03 11:27:15), and MongoDB will store the timestamp ISODate |
array | array |
object | object; in PHP it is stdClass |
history | array with history of changes |
When data is loaded from MongoDB or saved, object properties will be typification.
All subsequent attributes in the array are free, but some of them are reserved:
Attribute | Description |
---|---|
required | Required object property. If the property value is not specified, then when trying to save the data throw new Exception |
default | Default value. For example: 'rating' => ['int', 'default' => 100] |
timeCreate | Can only be used on the datetime type. Sets the current time if is new object. |
timeUpdate | Can only be used on the datetime type. Sets the current time each time an object is saved by a method save(). |
onlyCreation | Creates a variable in the MongoDB document on first save and no longer updates the data in it. Useful when the data in this property is updated in a third party process by direct requests. |
hidden | Hide a property on a method getArray |
history | Write changes to this property to history. |
historyPrepare | Specifies the method to check if the property has changed. |
historyValue | Specifies a method that returns the standardized property value for history. |
Creating a new object
Example: examples/new.php
$user = new User(['name' => 'Will', 'surname' => 'Smith']); $user->save();
or
$user = new User; $user->name = 'Will'; $user->surname = 'Smith'; $user->save();
or
$user = new User; $user->save(['name' => 'Will', 'surname' => 'Smith']);
or
$user = User::new(['name' => 'Will', 'surname' => 'Smith'], false);
Each of these examples will create a new document in MongoDB:
{ "_id" : ObjectId("63399434089c8c26344ff2df"), "surname" : "Smith", "name" : "Will", "email" : null, "rating" : NumberInt(0), "ban" : false, "timeCreate" : ISODate("2022-10-02T13:37:56.000+0000"), "timeUpdate" : ISODate("2022-10-02T13:37:56.000+0000") }
It is also possible to create documents with your own ID:
$userID = User::new(['userId' => 'user123', 'name' => 'Bob', 'rating' => 15]);
{ "_id" : "user123", "surname" : null, "name" : "Bob", "email" : null, "rating" : NumberInt(15), "ban" : false, "timeCreate" : ISODate("2022-10-02T13:38:51.000+0000"), "timeUpdate" : ISODate("2022-10-02T13:38:51.000+0000") }
If the ID is 24 characters long, then an attempt will be made to convert the string to ObjectId.
In the static method User::new(array $data, bool $returnID = true)
the data array is passed as the first argument, the second - whether only the ID of the created document or the entire instance of the object should be returned; $returnID = false
- return the entire object.
Operations with an object
Loading
YourModelClass::get($data)
Example: examples/get.php
By ID
$user = User::get('63399434089c8c26344ff2df');
By properties
$user = User::get(['name' => 'Bob', 'rating' => ['$gte' => 10]]);
When loading by properties, a check is made for the existence of the required fields name
and rating
in $fieldsModel
.
It is possible to search by internal properties if there is a dot in the key:
$user = User::get(['phones.number' => 123456789);
You can also search MongoDB syntax if the key starts with $:
// The first found document with a rating less than zero or banned will be returned $user = User::get(['$or' => [['rating' => ['$lt' => 0]], ['ban' => true]]]);
If the document is not found in MongoDB User::get
will return null
.
Method save
save(array $data = null)
Example: examples/save.php
The method saves the current object instance to a MongoDB document. Only changed properties are sent to the database.
if ($user = User::get('63399434089c8c26344ff2df')) { $user->rating = '20'; // string will be converted to int $user->save(['email' => 'test@test.com']); }
{ "_id" : ObjectId("63399434089c8c26344ff2df"), "surname" : "Smith", "name" : "Will", "email" : "test@test.com", "rating" : NumberInt(20), "ban" : false, "timeCreate" : ISODate("2022-10-02T13:37:56.000+0000"), "timeUpdate" : ISODate("2022-10-02T14:12:02.000+0000") }
If the object is updated and not created for the first time, then properties with the onlyCreation
attribute are ignored, while those with the history
attribute are checked for changes and written to the history.
Method saveField
saveField(string $field, $value = '__EMPTY__')
Example: examples/save.php
Store in MongoDB only one property of the current object.
Light saving method. History of changes is not created. The timeUpdate property is not updated automatically.
$user->email = 'test@test.com'; $user->rating = 50; $user->saveField('rating');
or
$user->email = 'test@test.com'; $user->saveField('rating', 50);
Even though you have changed email
its new value will not be written to MongoDB. Only the rating
value will be saved to the database.
Method saveFields
saveFields($fields = null)
Example: examples/save.php
Save only specific properties of the current object to MongoDB.
Light saving method. History of changes is not created. The timeUpdate property is not updated automatically.
$user->surname = 'TEST'; $user->email = 'test@test.com'; $user->rating = 50; // Saves only rating $user->saveFields('rating'); // Saves only rating and email $user->saveFields(['rating', 'email']);
In your own methods of the User
class, you can append to the protected changedFields array:
class User extends ModelMongoDB { // .............................. function changeEmail(string $email) { $this->email = $email; $this->changedFields[] = 'email'; return $this; } function resetRating() { $this->rating = 0; $this->changedFields[] = 'rating'; return $this; } }
if ($user = User::get('63399434089c8c26344ff2df')) { if (!empty($_POST['email'])) { // Change email $user->changeEmail($_POST['email']); } // Reset the rating and save the changed object properties in MongoDB $user->resetRating() ->saveFields(); }
Method delete
delete()
Example: examples/delete.php
Delete a document from MongoDB.
if ($user = User::get('63399434089c8c26344ff2df')) { $user->delete(); }
Method getArray
getArray($includeField = null, bool $skipHidden = true)
Example: examples/getArray.php
if ($user = User::get('63399434089c8c26344ff2df')) { print_r($user->getArray()); }
[
'userId' => '63399434089c8c26344ff2df',
'surname' => 'Smith',
'name' => 'Will',
'email' => null,
'rating' => 0,
'ban' => false,
'timeCreate' => '2022-10-02 13:37:56',
'timeUpdate' => '2022-10-02 13:37:56'
]
The getArray
method by default does not return properties that have hidden
in the $fieldsModel
. You can disable this by passing $skipHidden = false.
$includeField
You can pass a single attribute or an array of attributes that are specified for properties in $fieldsModel
. For example:
// Will return only properties that have required in their attributes $user->getArray('required'); // Will return only properties that have card and short in their attributes $user->getArray(['card', 'short']);
Loading with cache
YourModelClass::getFromCache($data)
Example: examples/getFromCache.php
When loading with caching, the object instance is stored in memory and when reloading, there is no call to MongoDB. As in the get() method, you can load by ID or properties.
Useful when working with an object in different parts of the code.
$userId = '63399434089c8c26344ff2df'; $email = $_POST['email'] ?? null; $name = $_POST['name'] ?? null; // ................................... // In the first section of code if ($email) { $user = User::getFromCache($userId); $user->email = $email; } // ................................... // In the second section of code if ($name) { $user = User::getFromCache($userId); $user->name = $name; } // ................................... if ($email or $name) { User::getFromCache($userId)->save(); }
Operations with objects
Method itemsGet
YourModelClass::itemsGet($data = null, $fields = null, $orderBy = null, $limited = null, array $settings = [])
Example: examples/itemsGet.php
Find documents in MongoDB and return an array of object instances or their defined properties.
Search data
$data
- null - search without conditions in all documents
- string or int - search by ID
- array - an array of IDs or an associative array of properties
// Returns an array of all documents User::itemsGet(); // Returns an array with documents that have _id = 26 User::itemsGet(26); // Returns an array with documents that have _id = user_12 User::itemsGet('user_12'); // Returns an array with documents that have _id = ObjectId("63399434089c8c26344ff2df") User::itemsGet('63399434089c8c26344ff2df'); // Returns an array with documents that have _id = user_12 or ObjectId("63399434089c8c26344ff2df") User::itemsGet(['user_12', '63399434089c8c26344ff2df']); // Returns an array with documents that have name = Will and surname = Smith User::itemsGet(['name' => 'Will', 'surname' => 'Smith']);
The search works in the same way as on the get() method, but the result is always an array with all found documents.
Return fields
$fields
If $fields = null then itemsGet
will return an array of object instances. With each instance, you can perform the same actions as if it were processed after loading.
foreach (User::itemsGet(['rating' => ['$lt' => 10]], null) as $user) { $user->rating += 5; $user->saveField('rating'); }
If a property is passed to $fields as a string, then an associative array with the specified property will be returned as a result of itemsGet
.
foreach (User::itemsGet(['name' => 'Will'], 'rating') as $userId => $rating) { }
If you pass an array of properties to $fields, then an associative array will be returned to the itemsGet
result, where the key will be _id, and the value stdClass with the specified fields.
foreach (User::itemsGet(['name' => 'Will'], ['name', 'surname', 'rating']) as $userId => $item) { echo $item->name . ' ' . $item->surname . ' has a rating ' . $item->rating; // Will Smith has a rating 10 // Will Duk has a rating 7 // .......... }
Sorting
$orderBy
Specify the field by which you want to sort documents when searching.
// Ascending User::itemsGet(['name' => 'Will'], null, 'rating'); // Descending User::itemsGet(['name' => 'Will'], null, ['rating', 'DESC']);
Pagination
$limited
Specify the number of documents to be searched and the indent (skip and limit).
// Returns the first 10 documents found User::itemsGet(null, null, null, 10); // Returns the found document from 21 to 30 User::itemsGet(null, null, null, [20, 10]);
Request settings
$settings
Settings for searching in MongoDB. For example, you can suggest an index:
User::itemsGet(['name' => 'Will'], null, 'rating', 10, ['hint' => 'index_name_rating']);
Also in the settings you can pass more complex conditions for sorting by several fields.
Method itemsHas
YourModelClass::itemsHas($data = null)
Example: examples/itemsHas.php
Check if documents exist in the collection according to given conditions.
In $data is passed search data, as in the itemsGet method.
Returns true or false.
if (User::itemsHas(['email' => 'mail@test.com'])) { die('Email busy'); }
Method itemsCount
YourModelClass::itemsCount($data = null, array $settings = [])
Example: examples/itemsCount.php
Returns the number of documents matching the query.
In $data is passed search data, as in the itemsGet method.
In $settings - request-settings
// How many documents have a 'rating' greater than 100 echo User::itemsCount(['rating' => ['$gt' => 100]]); // We suggest the index and count up to a maximum of 20 echo User::itemsCount(['rating' => ['$gt' => 100]], ['hint' => 'my_index', 'limit' => 20]);
Method itemsDelete
YourModelClass::itemsDelete($data = null)
Example: examples/itemsDelete.php
Removes all documents matching the query from the collection.
In $data is passed search data, as in the itemsGet method.
Returns the number of deleted documents.
// Delete all documents with 'rating' property less than zero echo User::itemsDelete(['rating' => ['$lt' => 0]]);
Method itemsNew
YourModelClass::itemsNew(array $items, bool $returnID = true)
Example: examples/itemsNew.php
Creates several objects and inserts insertMany
into MongoDB.
The method is similar YourModelClass::new()
, with the only difference that the first argument is an array of data arrays.
Returns array of IDs of created objects, or array of objects (if $returnID = false).
$newUsersID = User::itemsNew( [ [ 'name' => 'Ben', 'rating' => 77 ], [ 'name' => 'Robin', 'surname' => 'Collins' ] ], true );
History of changes
When an object is saved using the save() method, a history of changes is created. The history is written to an object property of type history
. Properties that have history
in their attributes are checked for change.
class User extends ModelMongoDB { // .............................. static public $fieldsModel = [ 'userId' => ['string'], 'surname' => ['string'], // DO NOT track changes 'name' => ['string', 'required'], // DO NOT track changes 'email' => ['string', 'history'], // Tracking changes 'phones' => ['array', 'history', // Tracking changes 'historyPrepare' => 'historyPrepareArray:number', 'historyValue' => 'historyValuePhones' ], 'history' => ['history'] // History array ]; }
If the properties of an object with the history
attribute have changed, then an entry will be added to the history array with the date of the change and a changes
object listing all the changed properties. Each property is an array with two elements array(old value, new value).
{ "datetime" : "2022-10-05 17:48:41", "changes" : { "phones" : [ [], ["(+1) 555331188"] ], "email" : [ "test@gmail.com", "email@gmail.com" ] } }
Attribute historyPrepare
In the historyPrepare
attribute, you can specify your method, which will be used to compare old value
=== new value
.
Besides comparing simple data types (string, int, float, bool) there are 2 methods:
- historyPrepareArray to compare arrays
- historyPrepareObject to compare objects
Your own method from the historyPrepare
attribute and to the historyPrepareArray
and historyPrepareObject
methods will be passed two values:
- value to be standardized somehow
- an optional variable from the attribute value after the colon. For example, number will be passed to
'historyPrepare' => 'historyPrepareArray:number'
Let's imagine that the user has a phones property - it's an array of stdClass objects with phones:
[ { "code" : 1, "phone" : 555331188, "number" : 1555331188, "formatted" : "(+1) 555331188", "timeAdd" : "2022-10-05 17:48:41" }, { "code" : 12, "phone" : 7774477, "number" : 127774477, "formatted" : "(+1) 7774477", "timeAdd" : "2022-10-05 19:31:02" } ]
From the given example 'historyPrepare' => 'historyPrepareArray:number'
the phone array will be compared by the number
field. When saved, the historyPrepareArray
method converts the comparison data to the string [127774477,1555331188]
. The same conversion will be performed on data from snapshot after the last save. Further comparison and record in history if there are changes.
Attribute historyValue
Your custom method that will process and return the value to write to the history.
See example: examples/User.php
Method checkRecordHistory
checkRecordHistory(array $changes)
Returns true by default; If it returns false - the history of changes will not be added.
Called during save when the history of changes has already been compiled.
In $changes is passed current changes.
The method is useful if you need to keep a history of changes, but under some circumstances sometimes do not record it.
For example, you can check the size of the current history and not write more than 100 entries:
class User extends ModelMongoDB { // .............................. protected function checkRecordHistory($changes) { return count($this->history) < 100; } }
To help the developer
Triggers
Method | Description |
---|---|
afterCreate | Called after class initialization in __construct. |
afterLoad | Called after loading data from MongoDB. |
preSave | Called before saving. |
afterSave | Called after saving. The method is passed an array of property names that have changed and been submitted to MongoDB. |
class User extends ModelMongoDB { // .............................. function afterLoad() { // If the rating in MongoDB was less than 500 if (empty($this->rating) or $this->rating < 500) { $this->rating = 500; } } function preSave() { // If after all the manipulations the rating has become less than -100 if ($this->rating < -100) { $this->ban = true; } } }
Snapshots
getSnapshot($field)
and getSnapshotUpdate($field)
After loading data from MongoDB, created a snapshot of current state. In the process, you may need to find out what data was in the database or since the last save.
if ($user = User::get('63399434089c8c26344ff2df')) { $user->name = 'Jack'; echo $user->name; // Jack echo $user->getSnapshot('name'); // Will echo $user->getSnapshotUpdate('name'); // Will // Save name Jack $user->saveFields('name'); $user->name = 'Bob'; echo $user->name; // Bob echo $user->getSnapshot('name'); // Will echo $user->getSnapshotUpdate('name'); // Jack // Save name Dan $user->save(['name' => 'Dan']); $user->name = 'Test'; echo $user->name; // Test echo $user->getSnapshot('name'); // Will echo $user->getSnapshotUpdate('name'); // Dan }
Method getCollection
YourModelClass::getCollection()
Returns a MongoDB collection. Can be used for direct queries.
use MongoModel\MongoDB; // Ban all users with a rating less than -100 MongoDB::execute(User::getCollection(), 'updateMany', [ 'rating' => ['$lt' => -100] ], [ '$set' => [ 'ban' => true ] ]);
Method collectionInfo
MongoDB::collectionInfo(string $collection)
Example: examples/collectionInfo.php
Returns information about the collection.
use MongoModel\MongoDB; print_r(MongoDB::collectionInfo('users'));
stdClass Object
(
[count] => 27
[storageSize] => 36864
[indexSize] => 36864
[indexCount] => 1
[size] => 6189
[avgObjSize] => 229
)