grefon / mongo-model
MongoDb 模型
Requires
- php: >=7.3
- ext-json: *
- ext-mbstring: *
- ext-mongodb: *
- mongodb/mongodb: ^1.5.1
README
MongoModel 允许您方便地像处理对象一样处理 MongoDB 文档。
实现
- 属性类型化
- 必填属性
- 默认值
- 变更历史
- 对象操作
MongoModel 与 MongoDB PHP 库 一起工作
入门
Composer
通过 Composer 安装此包。编辑 composer.json
中的 require
{ "require": { "grefon/mongo-model": "*" } }
并运行 composer update
或者
在您的命令行中运行此命令
composer require grefon/mongo-model
初始化连接
// 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);
启用调试的 MongoDB
类会将请求及其状态记录到 MongoDB::$trace
。
您的第一个模型
在 MongoDB 中创建名为 users
的集合。
创建新的 PHP 类 User
继承 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'] ]; }
每个模型类都必须继承 ModelMongoDB
。它必须包含 4 个主要静态变量
$collection
- 此变量将存储 \MongoDB\Collection 实例$collectionName
- MongoDB 中的集合名称$primaryKey
- primary key 名称(代替_id
)$fieldsModel
- 模型字段(对象属性)数组
$fieldsModel
在 $fieldsModel 中,数据模型被描述为一个关联数组。键是对象的属性名,值是属性属性的数组。
属性数组中的第一个值必须是数据类型。
当从 MongoDB 加载数据或保存时,对象属性将进行类型化。
数组中的所有后续属性都是自由的,但其中一些是保留的
创建新对象
示例: examples/new.php
$user = new User(['name' => 'Will', 'surname' => 'Smith']); $user->save();
或者
$user = new User; $user->name = 'Will'; $user->surname = 'Smith'; $user->save();
或者
$user = new User; $user->save(['name' => 'Will', 'surname' => 'Smith']);
或者
$user = User::new(['name' => 'Will', 'surname' => 'Smith'], false);
这些示例中的每个都会在 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") }
也可以创建具有您自己的 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") }
如果 ID 长度为 24 个字符,则尝试将字符串转换为 ObjectId。
在静态方法 User::new(array $data, bool $returnID = true)
中,数据数组作为第一个参数传递,第二个参数 - 是否只返回创建的文档的 ID 或整个对象实例; $returnID = false
- 返回整个对象。
对象操作
加载
YourModelClass::get($data)
示例: examples/get.php
按 ID
$user = User::get('63399434089c8c26344ff2df');
按属性
$user = User::get(['name' => 'Bob', 'rating' => ['$gte' => 10]]);
通过属性加载时,会在 $fieldsModel
中检查必需字段 name
和 rating
的存在。如果键中有点,则可以按内部属性进行搜索。
$user = User::get(['phones.number' => 123456789);
如果键以 $ 开头,也可以搜索 MongoDB 语法。
// The first found document with a rating less than zero or banned will be returned $user = User::get(['$or' => [['rating' => ['$lt' => 0]], ['ban' => true]]]);
如果文档在 MongoDB 中找不到,则 User::get
将返回 null
。
save 方法
save(array $data = null)
该方法将当前对象实例保存到 MongoDB 文档中。只有更改过的属性才会发送到数据库。
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") }
如果对象已更新且不是首次创建,则具有 onlyCreation
属性的属性将被忽略,而具有 history
属性的属性将检查更改并写入历史记录。
saveField 方法
saveField(string $field, $value = '__EMPTY__')
仅将当前对象的单个属性存储在 MongoDB 中。
轻量级保存方法。不创建更改历史。自动更新时间属性。
$user->email = 'test@test.com'; $user->rating = 50; $user->saveField('rating');
或者
$user->email = 'test@test.com'; $user->saveField('rating', 50);
即使您已更改 email
,其新值也不会写入 MongoDB。只有 rating
值将保存到数据库。
saveFields 方法
saveFields($fields = null)
仅将当前对象的特定属性保存到 MongoDB。
轻量级保存方法。不创建更改历史。自动更新时间属性。
$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']);
在您的 User
类的自身方法中,您可以向受保护的 changedFields 数组中追加。
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(); }
delete 方法
delete()
从 MongoDB 中删除文档。
if ($user = User::get('63399434089c8c26344ff2df')) { $user->delete(); }
getArray 方法
getArray($includeField = null, bool $skipHidden = true)
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'
]
默认情况下,getArray
方法不返回在 $fieldsModel
中具有 hidden
的属性。您可以通过传递 $skipHidden = false 来禁用此功能。
$includeField
您可以传递单个属性或属性数组,这些属性在 $fieldsModel
中指定。例如
// 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']);
带缓存加载
YourModelClass::getFromCache($data)
当使用缓存加载时,对象实例存储在内存中,在重新加载时,不会调用 MongoDB。与 get() 方法一样,您可以按 ID 或属性进行加载。
当在代码的不同部分处理对象时很有用。
$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(); }
对象操作
itemsGet 方法
YourModelClass::itemsGet($data = null, $fields = null, $orderBy = null, $limited = null, array $settings = [])
在 MongoDB 中查找文档,并返回对象实例数组或它们的定义属性。
搜索数据
$data
- null - 在所有文档中无条件搜索
- string 或 int - 按 ID 搜索
- array - ID 数组或属性关联数组
// 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']);
搜索方式与 get() 方法相同,但结果总是一个包含所有找到的文档的数组。
返回字段
$fields
如果 $fields = null,则 itemsGet
将返回对象实例数组。您可以对每个实例执行与加载后处理相同的行为。
foreach (User::itemsGet(['rating' => ['$lt' => 10]], null) as $user) { $user->rating += 5; $user->saveField('rating'); }
如果将属性传递给 $fields 作为字符串,则 itemsGet
的结果将返回一个包含指定属性的关联数组。
foreach (User::itemsGet(['name' => 'Will'], 'rating') as $userId => $rating) { }
如果您将属性数组传递给 $fields,那么将返回一个关联数组给 itemsGet
结果,其中键将是 _id,值是包含指定字段的 stdClass。
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 // .......... }
排序
$orderBy
指定搜索时按哪个字段排序。
// Ascending User::itemsGet(['name' => 'Will'], null, 'rating'); // Descending User::itemsGet(['name' => 'Will'], null, ['rating', 'DESC']);
分页
$limited
指定要搜索的文档数量和缩进(跳过和限制)。
// 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]);
请求设置
$settings
这是在 MongoDB 中进行搜索的设置。例如,您可以建议一个索引
User::itemsGet(['name' => 'Will'], null, 'rating', 10, ['hint' => 'index_name_rating']);
在设置中,您还可以传递更复杂的条件,以便根据多个字段进行排序。
itemsHas 方法
YourModelClass::itemsHas($data = null)
根据给定条件检查集合中是否存在文档。
$data 传递了 搜索数据,如同在 itemsGet 方法中。
返回 true 或 false。
if (User::itemsHas(['email' => 'mail@test.com'])) { die('Email busy'); }
itemsCount 方法
YourModelClass::itemsCount($data = null, array $settings = [])
返回符合查询条件的文档数量。
$data 传递了 搜索数据,如同在 itemsGet 方法中。
在 $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]);
itemsDelete 方法
YourModelClass::itemsDelete($data = null)
从集合中删除所有匹配查询的文档。
$data 传递了 搜索数据,如同在 itemsGet 方法中。
返回被删除的文档数量。
// Delete all documents with 'rating' property less than zero echo User::itemsDelete(['rating' => ['$lt' => 0]]);
itemsNew 方法
YourModelClass::itemsNew(array $items, bool $returnID = true)
创建多个对象并将 insertMany
插入到 MongoDB 中。
该方法类似于 YourModelClass::new()
,唯一的不同之处在于第一个参数是数据数组的数组。
返回创建的对象的 ID 数组,或者对象数组(如果 $returnID = false)。
$newUsersID = User::itemsNew( [ [ 'name' => 'Ben', 'rating' => 77 ], [ 'name' => 'Robin', 'surname' => 'Collins' ] ], true );
变更历史
当使用 save() 方法保存对象时,将创建一个更改历史记录。历史记录将写入对象的 history
类型属性。检查具有 history
属性的属性是否已更改。
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 ]; }
如果具有 history
属性的对象的属性已更改,则会在历史记录数组中添加一个条目,其中包含更改的日期和一个列出所有已更改属性的 changes
对象。每个属性都是一个包含两个元素的数组(旧值,新值)。
{ "datetime" : "2022-10-05 17:48:41", "changes" : { "phones" : [ [], ["(+1) 555331188"] ], "email" : [ "test@gmail.com", "email@gmail.com" ] } }
historyPrepare 属性
在 historyPrepare
属性中,您可以指定自己的方法,该方法将用于比较 old value
=== new value
。
除了比较简单的数据类型(字符串、整数、浮点数、布尔值)之外,还有 2 个方法
- historyPrepareArray 用于比较数组
- historyPrepareObject 用于比较对象
从 historyPrepare
属性和到 historyPrepareArray
和 historyPrepareObject
方法的自己的方法将传递两个值
- 要按某种方式标准化的值
- 一个可选的变量,从冒号后的属性值。例如,数字将传递到
'historyPrepare' => 'historyPrepareArray:number'
假设用户有一个 phones 属性 - 它是一个包含电话的 stdClass 对象数组
[ { "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" } ]
从给定的示例 'historyPrepare' => 'historyPrepareArray:number'
,电话数组将通过 number
字段进行比较。当保存时,historyPrepareArray
方法将比较数据转换为字符串 [127774477,1555331188]
。在最后一次保存之后,对 快照 的数据也会进行相同的转换。如果存在变化,将进行进一步的比较并在历史记录中记录。
historyValue 属性
您将处理并返回要写入历史的值的自定义 方法。
请参阅示例:examples/User.php
checkRecordHistory 方法
checkRecordHistory(array $changes)
默认返回 true;如果返回 false,则不会添加更改历史。
在已经编译更改历史的情况下,在保存期间调用。
$changes 传递当前更改。
当您需要记录更改历史,但有时在某些情况下不记录时,此方法很有用。
例如,您可以检查当前历史的尺寸,并且不写入超过100个条目
class User extends ModelMongoDB { // .............................. protected function checkRecordHistory($changes) { return count($this->history) < 100; } }
帮助开发者
触发器
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; } } }
快照
getSnapshot($field)
和 getSnapshotUpdate($field)
在从 MongoDB 加载数据后,创建当前状态的快照。在此过程中,您可能需要找出数据库中的数据或自上次保存以来的数据。
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 }
getCollection 方法
YourModelClass::getCollection()
返回一个 MongoDB 集合。可用于直接查询。
use MongoModel\MongoDB; // Ban all users with a rating less than -100 MongoDB::execute(User::getCollection(), 'updateMany', [ 'rating' => ['$lt' => -100] ], [ '$set' => [ 'ban' => true ] ]);
collectionInfo 方法
MongoDB::collectionInfo(string $collection)
示例:examples/collectionInfo.php
返回有关集合的信息。
use MongoModel\MongoDB; print_r(MongoDB::collectionInfo('users'));
stdClass Object
(
[count] => 27
[storageSize] => 36864
[indexSize] => 36864
[indexCount] => 1
[size] => 6189
[avgObjSize] => 229
)