district5 / mondoc
Mondoc 是一个轻量级、易于使用的 MongoDB 模型和服务的抽象库
Requires
- php: >=8.1.0
- ext-mongodb: *
- district5/date: *
- district5/mondoc-builder: *
- mongodb/mongodb: *
Requires (Dev)
- phpunit/phpunit: 9.*
- dev-master
- 6.5.0
- 6.3.2
- 6.3.1
- 6.3.0
- 6.2.0
- 6.1.4
- 6.1.3
- 6.1.2
- 6.1.1
- 6.1.0
- 6.0.0
- 5.2.0
- 5.1.1
- 5.1.0
- 5.0.1
- 5.0.0
- 4.1.7
- 4.1.6
- 4.1.5
- 4.1.4
- 4.1.3
- 4.1.2
- 4.1.1
- 4.1.0
- 4.0.1
- 4.0.0
- 3.1.8
- 3.1.7
- 3.1.6
- 3.1.5
- 3.1.4
- 3.1.3
- 3.1.2
- 3.1.1
- 3.1.0
- 3.0.8
- 3.0.7
- 3.0.6
- 3.0.5
- 3.0.4
- 3.0.3
- dev-feature/override-collection-connection
- dev-feature/code-coverage
- dev-feature/automatic-created-modified
- dev-feature/81-refactor-with-batch-updates
This package is auto-updated.
Last update: 2024-09-12 10:16:25 UTC
README
Composer...
使用 composer 将此库作为项目依赖项添加。
composer require district5/mondoc
用法...
设置连接...
MondoConnections
对象是一个单例。请在您的代码中的某个位置设置此对象以初始化连接,并在您的服务中定义 protected static function getConnectionId(): string
以返回相关模型的正确标识符。
<?php use District5\Mondoc\MondocConfig; use MongoDB\Client; $connection = new Client('< mongo connection string >'); $database = $connection->selectDatabase('< database name >'); $config = MondocConfig::getInstance(); $config->addDatabase( $database, 'default' // a connection identifier ('default' is the default value). ); // Add another one for something else... // $config->addDatabase( // $database, // 'authentication' // ); $config->addServiceMapping( MyModel::class, // You can also just use a string like '\MyNamespace\Model\MyModel' MyService::class // You can also just use a string like '\MyNamespace\Service\MyService' ); // Or you can use... // $config->setServiceMap( // [ // MyModel::class => MyService::class, // Also replaceable by strings // AnotherModel::class => AnotherService::class, // ] // );
数据模型
<?php namespace MyNs\Model; use District5\Mondoc\Db\Model\MondocAbstractModel; /** * Class MyModel * @package MyNs\Model */ class MyModel extends MondocAbstractModel { /** * @var string */ protected $name = null; // This is optional, but can be used to map fields from the database to the model to keep keys short in storage. protected array $mondocFieldAliases = [ 'n' => 'name', // the `name` value would be stored in the database as the key `n` ]; /** * @return string */ public function getName(): ?string { return $this->name; } /** * @param string $val * @return $this */ public function setName(string $val) { $this->name = trim($val); return $this; } }
此外,在模型中,您可以使用 $mondocFieldAliases
属性将字段从数据库映射到模型,以在存储中保持键短。这虽然是可选的,但在某些情况下可能很有用。以下是一个示例
namespace MyNs\Model; use District5\Mondoc\Db\Model\MondocAbstractModel; class MyModel extends MondocAbstractModel { protected string $name = null; protected array $mondocFieldAliases = [ 'n' => 'name', ]; // Rest of your model code... }
可选特性...
MondocVersionedModelTrait
- 简单地对数据进行版本控制。- 您可以通过使用
\District5\Mondoc\Db\Model\Traits\MondocVersionedModelTrait
特性,轻松地对模型内的数据进行版本控制。此特性在模型中引入了一个_v
变量,您可以选择在需要时递增。- 您可以通过在模型上调用
isVersionableModel()
来检测模型是否有版本。
- 您可以通过在模型上调用
- 您可以通过使用
MondocRevisionNumberTrait
- 为模型添加一个_rn
属性,用作修订号。此值在模型首次保存时自动设置为1
,每次更新模型时都会递增。您可以通过在继承模型上调用setRevisionNumber
方法手动分配值到_rn
属性来覆盖此行为。这与版本化模型特性不同,因为修订号在每次模型更新时都会递增,而不管所做的更改是什么。- 您可以通过在模型上调用
isRevisionNumberModel()
来检测模型是否有修订号。
- 您可以通过在模型上调用
MondocCreatedDateTrait
- 为模型添加一个cd
属性,用作创建日期。- 此值在模型首次保存时自动分配给当前的 UTC 日期,或者如果现有模型更新且
cd
属性尚未设置。您可以通过分配值到cd
属性来覆盖此行为。
- 此值在模型首次保存时自动分配给当前的 UTC 日期,或者如果现有模型更新且
MondocModifiedDateTrait
- 为模型添加一个md
属性,用作更新日期。- 此值自动分配给当前的 UTC 日期,但在保存之前,您可以分配值到
md
属性来覆盖此行为。
- 此值自动分配给当前的 UTC 日期,但在保存之前,您可以分配值到
MondocCloneableTrait
- 为模型添加一个clone
方法,该方法将返回一个具有与原始模型相同属性的新模型实例。当调用->clone
时,您可以传递一个布尔值来指示是否要将新模型持久化到数据库。可选的第二个参数是要克隆的对象或类。例如,您可以通过调用$myModel->clone( < save:bool > , OtherModel::class)
来创建MyModel
的一个克隆并将其转换为OtherModel
。MondocRetentionTrait
- 将此特性添加到您的模型中,将公开setMondocRetentionChangeMeta
和setMondocRetentionExpiry
方法,允许您设置模型的保留数据。这对于设置诸如保留期限和保留策略等事物很有用。
特性示例
<?php class MyModel extends \District5\Mondoc\Db\Model\MondocAbstractModel { use \District5\Mondoc\Db\Model\Traits\MondocCreatedDateTrait; use \District5\Mondoc\Db\Model\Traits\MondocModifiedDateTrait; use \District5\Mondoc\Db\Model\Traits\MondocCloneableTrait; use \District5\Mondoc\Db\Model\Traits\MondocRevisionNumberTrait; use \District5\Mondoc\Db\Model\Traits\MondocVersionedModelTrait; use \District5\Mondoc\Db\Model\Traits\MondocRetentionTrait; // Rest of your model code... }
服务层
查询数据库的逻辑始终在服务层中执行。只有一个必需的方法,即 getCollectionName
,它应返回数据库中集合的名称。
可选地,您可以为 getConnectionId
方法定义,以返回从 MondocConfig
连接管理器中使用的连接 ID。如果您正在使用多个连接,例如,用于身份验证的连接和用于主应用的连接,这非常有用。
Mondoc 原生查询在查询集合时自动将 DateTime
对象转换为 UTCDateTime
对象。
请注意:在 6.3.0 版本之前,
PaginationTrait
需要您在每次方法调用中传递筛选器。现在不再需要,因为筛选器现在通过MondocPaginationHelper
对象传递。
<?php namespace Myns\Service; use MyNs\Model\MyModel; use District5\Mondoc\Db\Service\MondocAbstractService /** * Class MyService * @package MyNs\Service */ class MyService extends MondocAbstractService { /** * Get the collection name. * * @return string */ protected static function getCollectionName(): string { return 'users'; } /** * Get the connection ID to use from the MondocConfig manager. Defaults to 'default'. * This method isn't required if you're using the default connection, but if you're using * multiple connections, you can use this method to return the connection ID. * * @return string */ protected static function getConnectionId() : string { return 'default'; // this is the default connection. } }
嵌套对象
您可以在彼此中嵌套对象。主要模型必须扩展 \District5\Mondoc\Db\Model\MondocAbstractModel
,并且子模型必须在 $mondocNested
数组中定义。
子模型必须扩展 \District5\Mondoc\Db\Model\MondocAbstractSubModel
。
在实现 $mondocNested
时,您声明一个单个嵌套模型,或一个嵌套模型的数组。例如
protected Food $favouriteFood; protected array $allFoods; // Array of 'Food' objects protected array $mondocNested = [ 'favouriteFood' => Food::class, // Single nested model 'allFoods' => Food::class . '[]' // Array of nested models ];
请注意:在 5.1.0 版本之前,任何嵌套属性都必须在属性定义中包含
BSONDocument
或BSONArray
。现在不再需要,因为库将自动正确地膨胀类(或类)。
嵌套对象,无论深度如何,也可以利用 $mondocFieldAliases
属性将数据库字段映射到模型中。这可以在存储中保持键短,同时允许模型中使用更长的、更具描述性的键。对于上面的示例,您可以有如下所示
protected Food $favouriteFood; protected array $allFoods; // Array of 'Food' objects protected array $mondocFieldAliases = [ 'ff' => 'favouriteFood', 'af' => 'allFoods' ];
use District5\Mondoc\Db\Model\MondocAbstractModel; use District5\Mondoc\Db\Model\MondocAbstractSubModel; class FavouriteFood extends MondocAbstractSubModel { protected string $foodName; protected string $foodDescription; // This is optional, but can be used to map fields from the database to the model to keep keys short in storage protected array $mondocFieldAliases = [ 'fd' => 'foodDescription', ]; public function getFoodName() { return $this->foodName; } public function getFoodDescription() { return $this->foodDescription; } } class Car extends MondocAbstractSubModel { protected string|null $brand = null; protected string|null $colour = null; public function getBrand(): ?string { return $this->brand; } public function getColour(): ?string { return $this->colour; } } class Person extends MondocAbstractModel { /** * @var string|null */ protected string|null $name = null; /** * @var FavouriteFood */ protected FavouriteFood|null $favouriteFood = null; // Having BSONDocument here is important as inflation will use the property /** * @var FavouriteFood[] */ protected array $allFoods = []; // Having BSONArray here is important as inflation will use the property /** * @var Car */ protected Car|null $car = null; // Having BSONDocument here is important as inflation will use the property /** * @var string[] */ protected array $mondocNested = [ 'allFoods' => FavouriteFood::class . '[]', // Indicates an array of FavouriteFood objects 'favouriteFood' => FavouriteFood::class, 'car' => Car::class ]; public function getAllFoods(): array { return $this->allFoods; } public function getFavouriteFoodName(): ?string { return $this->favouriteFood->getFoodName(); } public function getCarBrand(): ?string { return $this->car->getBrand(); } public function getCarColour(): ?string { return $this->car->getColour(); } }
查找文档...
<?php // count documents matching a filter \District5Tests\MondocTests\TestObjects\MyService::countAll([], []); // count documents using a query builder $builder = \District5Tests\MondocTests\TestObjects\MyService::getQueryBuilder(); \District5Tests\MondocTests\TestObjects\MyService::countAllByQueryBuilder($builder); // get single model by id, accepts a string or ObjectId \District5Tests\MondocTests\TestObjects\MyService::getById('the-mongo-id'); // get multiple models by ids. accepts string or ObjectIds \District5Tests\MondocTests\TestObjects\MyService::getByIds(['an-id', 'another-id']); // get single model with options \District5Tests\MondocTests\TestObjects\MyService::getOneByCriteria(['foo' => 'bar'], ['sort' => ['foo' => -1]]); // get multiple models with options \District5Tests\MondocTests\TestObjects\MyService::getMultiByCriteria(['foo' => 'bar'], ['sort' => ['foo' => -1]]); // working with dates, both of these queries are the same $phpDate = new \DateTime(); \District5Tests\MondocTests\TestObjects\MyService::getMultiByCriteria(['dateField' => ['$lte' => $phpDate]]); $mongoDate = \District5\Mondoc\Helper\MondocTypes::phpDateToMongoDateTime($phpDate); \District5Tests\MondocTests\TestObjects\MyService::getMultiByCriteria(['dateField' => ['$lte' => $mongoDate]]); // paginating results by page number $currentPage = 1; $perPage = 10; $sortByField = 'foo'; $sortDirection = -1; $pagination = \District5Tests\MondocTests\TestObjects\MyService::getPaginationHelper($currentPage, $perPage, ['foo' => 'bar']) $results = \District5Tests\MondocTests\TestObjects\MyService::getPage($pagination, $sortByField, $sortDirection); // Since 6.3.0 the filter is carried through by the pagination helper // paginating results by ID number descending (first page) $currentId = null; $perPage = 10; $sortDirection = -1; $pagination = \District5Tests\MondocTests\TestObjects\MyService::getPaginationHelperForObjectIdPagination($perPage, ['foo' => 'bar']) $results = \District5Tests\MondocTests\TestObjects\MyService::getPageByByObjectIdPagination($pagination, $currentId, $perPage, $sortDirection); // Since 6.3.0 the filter is carried through by the pagination helper // paginating results by ID number descending $currentId = '5f7deca120c41f29827c0c60'; // or new ObjectId('5f7deca120c41f29827c0c60'); $perPage = 10; $sortDirection = -1; $pagination = \District5Tests\MondocTests\TestObjects\MyService::getPaginationHelperForObjectIdPagination($perPage, ['foo' => 'bar']) $results = \District5Tests\MondocTests\TestObjects\MyService::getPageByByObjectIdPagination($pagination, $currentId, $perPage, $sortDirection); // Since 6.3.0 the filter is carried through by the pagination helper // paginating results by ID number ascending $currentId = '5f7deca120c41f29827c0c60'; // or new ObjectId('5f7deca120c41f29827c0c60'); $perPage = 10; $sortDirection = 1; $pagination = \District5Tests\MondocTests\TestObjects\MyService::getPaginationHelperForObjectIdPagination($perPage, ['foo' => 'bar']) $results = \District5Tests\MondocTests\TestObjects\MyService::getPageByByObjectIdPagination($pagination, $currentId, $perPage, $sortDirection); // Since 6.3.0 the filter is carried through by the pagination helper // get the distinct values for 'age' with a filter and options \District5Tests\MondocTests\TestObjects\MyService::getDistinctValuesForKey('age', ['foo' => 'bar'], ['sort' => ['age' => 1]]); // average age with filter \District5Tests\MondocTests\TestObjects\MyService::aggregate()->getAverage('age', ['foo' => 'bar']); // 10% percentile, sorted asc with filter \District5Tests\MondocTests\TestObjects\MyService::aggregate()->getPercentile('age', 0.1, 1, ['foo' => 'bar']); // get sum of a field with a given filter \District5Tests\MondocTests\TestObjects\MyService::aggregate()->getSum('age', ['foo' => 'bar']); // get the min value of a field with a given filter \District5Tests\MondocTests\TestObjects\MyService::aggregate()->getMin('age', ['foo' => 'bar']); // ...or with a string... // \District5Tests\MondocTests\TestObjects\MyService::aggregate()->getMin('name', ['foo' => 'bar']); // get the max value of a field with a given filter \District5Tests\MondocTests\TestObjects\MyService::aggregate()->getMax('age', ['foo' => 'bar']); // ...or with a string... // \District5Tests\MondocTests\TestObjects\MyService::aggregate()->getMax('name', ['foo' => 'bar']);
模型到数组...
您可以通过在模型上调用 asArray()
将模型导出为数组。这将返回模型的属性数组。
asArray()
方法返回的属性是已在模型上设置的属性和类型,这意味着它们可能无法直接编码为 JSON。为了解决这个问题,您可以在模型上调用 asJsonEncodableArray()
,这将返回可以编码为 JSON 的数组。可选地,您还可以提供一个要从中省略字段的列表。
/* @var $model \District5\Mondoc\Db\Model\MondocAbstractModel */ $mongoInsertionDocument = $model->asArray(); // Not encodable to JSON $jsonEncodable = $model->asJsonEncodableArray(); // Encodable to JSON $jsonEncodable = $model->asJsonEncodableArray(['password', 'secret']); // Encodable to JSON omitting the 'password' and 'secret' properties $encodedJson = json_encode($jsonEncodable, JSON_THROW_ON_ERROR); echo $encodedJson;
有用信息...
要使用预定义的 ObjectId 作为文档 _id
,您可以调用模型上的 setPresetObjectId
。这将强制模型吸收此 ObjectId,并在插入时不会生成新的 ObjectId。例如
<?php /** @noinspection SpellCheckingInspection */ $theId = new \MongoDB\BSON\ObjectId('61dfee5591efcf44e023d692'); $person = new Person(); $person->setPresetObjectId(new ObjectId()); $insertOrUpdateOptions = []; $person->save($insertOrUpdateOptions); // optional echo $person->getObjectIdString(); // 61dfee5591efcf44e023d692
此外,还有一个名为 assignDefaultVars
的方法,可以用于将默认值分配给模型属性。这对于设置属性默认值非常有用。在膨胀发生之后调用 assignDefaultVars
,因此请注意属性可能已经分配了值。例如
<?php use District5\Mondoc\Db\Model\MondocAbstractModel; class MyModel extends MondocAbstractModel { protected string $name = null; protected int $version = 0; protected function assignDefaultVars() { if ($this->version < 1) { $this->version = 1; } } }
类型之间的转换
MongoDB 使用 BSON 类型进行数据。此库包含一个 MondocTypes
辅助工具,可以协助转换这些原生类型。
<?php use \District5\Mondoc\Helper\MondocTypes; // Dates $mongoDateTime = MondocTypes::phpDateToMongoDateTime(new \DateTime()); $phpDateTime = MondocTypes::dateToPHPDateTime($mongoDateTime); // BSON documents $bsonDocument = new \MongoDB\Model\BSONDocument(['foo' => 'bar']); $phpArrayFromDoc = MondocTypes::arrayToPhp($bsonDocument); // BSON arrays $bsonArray = new \MongoDB\Model\BSONArray(['foo', 'bar']); $phpArrayFromArray = MondocTypes::arrayToPhp($bsonArray); // ObjectIds /** @noinspection SpellCheckingInspection */ $anId = '61dfee5591efcf44e023d692'; $objectId = MondocTypes::toObjectId($anId); // You can also pass existing ObjectId's into the conversion and nothing happens. // MondocTypes::toObjectId(new \MongoDB\BSON\ObjectId()); // MondocTypes::toObjectId($objectId); $string = MondocTypes::objectIdToString($objectId); // less used, but still handled... $objectId = MondocTypes::toObjectId([ '$oid' => '61dfee5591efcf44e023d692' ]); $objectId = MondocTypes::toObjectId([ 'oid' => '61dfee5591efcf44e023d692' ]);
数据保留
当使用 MondocRetentionTrait
特性时,您可以通过调用 setMondocRetentionChangeMeta
来设置模型的保留数据。没有预设的保留数据,因此您必须自己设置。这有助于设置诸如发起更改的用户名称之类的信息,或者用于合规性和保留策略。此外,公开了 setMondocRetentionExpiry
方法,可以用于设置保留数据的过期日期。当保留期过期时,库不会自动删除数据,但您可以使用 MondocRetentionService
查询已过期的数据,使用 getPaginatorForExpiredRetentionForClassName
或 getPaginatorForExpiredRetentionForObject
,然后使用 getRetentionPage
方法。
MondocRetentionService
中公开的方法
create
- 创建新的保留模型。当模型包含MondocRetentionTrait
特性时,Mondoc 会自动调用此方法。createStub
- 创建新的保留模型,但不保存。这对于创建希望稍后保存的保留模型很有用。在插入多个模型时使用此方法。getLatestRetentionModelForModel
- 获取给定(之前已保存)模型的最新保留模型。countRetentionModelsForClassName
- 统计给定类名的保留模型数量。countRetentionModelsForModel
- 统计给定(之前已保存)模型的保留模型数量。getRetentionHistoryPaginationHelperForClassName
- 获取给定类名的保留历史记录分页助手。getRetentionHistoryPaginationHelperForModel
- 获取给定(之前已保存)模型的保留历史记录分页助手。addIndexes
- 向保留集合添加索引。Mondoc 不会自动调用此方法,您必须手动通过您的应用程序调用。hasIndexes
- 检查索引是否已添加到保留集合。这是一个辅助方法,允许您检查索引是否已添加;如果没有,您可以调用addIndexes
来添加它们。getPaginatorForExpiredRetentionForClassName
- 获取给定类名的保留历史记录分页助手,其中保留已过期。getPaginatorForExpiredRetentionForObject
- 获取给定(之前已保存)模型的保留历史记录分页助手,其中保留已过期。getRetentionPage
- 从分页助手获取保留模型的一页。这是您在通过getPaginatorForExpiredRetentionForClassName
或getPaginatorForExpiredRetentionForObject
获取分页助手之后使用的方法。
在 MondoRetentionModel
中,以下方法可用
toOriginalModel
- 获取与保留数据关联的原始模型。这将返回在保留数据保存时保存的数据已膨胀的模型。getSourceModelData
- 获取在保留数据保存时保存的数据。这将返回以数组格式保存的保留数据保存时保存的数据。getSourceObjectId
- 获取与保留数据关联的原始模型的 ObjectId。getSourceObjectIdString
- 以字符串形式获取与保留数据关联的原始模型的 ObjectId。getSourceClassName
- 获取与保留数据关联的原始模型的类名。getRetentionData
- 获取在保留数据保存时保存的保留数据,如通过setMondocRetentionChangeMeta
的原始调用在MondocRetentionTrait
中设置的。getRetentionExpiry
- 获取在保存保留数据时保存的保留到期日期,该日期由原始调用setMondocRetentionExpiry
设置,包含在MondocRetentionTrait
中。hasRetentionExpired
- 检查此保留模型是否已过期。如果保留数据已过期,则返回true
,如果没有过期,则返回false
。
以下是一个使用保留特征的示例
<?php use District5\Date\Date; use District5\Mondoc\Db\Model\MondocAbstractModel; use District5\Mondoc\Db\Model\Traits\MondocRetentionTrait; use District5\Mondoc\Helper\MondocTypes; use District5\Mondoc\Extensions\Retention\MondocRetentionService; class MyService extends MondocAbstractService { protected static function getCollectionName(): string { return 'data'; } } class MyModel extends MondocAbstractModel { use MondocRetentionTrait; protected string $name = null; public function setName(string $name): self { $this->name = $name; return $this; } } $model = new MyModel(); $model->setName('John Doe'); $model->setMondocRetentionData([ 'user' => 'joe.bloggs', ]); $model->setMondocRetentionExpiry( Date::modify( Date::nowUtc() )->plus()->days(30) ); $model->save(); // There is now both a `MyModel` saved, and a `MondocRetentionModel` saved with the retention data. $retrieved = MyService::getById($model->getObjectIdString()); $retrieved->setMondocRetentionData([ 'user' => 'jane.bloggs', ]); $retrieved->setMondocRetentionExpiry( null // this data will never expire ); $retrieved->save(); // A new `MondocRetentionModel` is saved with the updated retention data. There are now two `MondocRetentionModel`'s $paginator = MondocRetentionService::getRetentionHistoryPaginationHelperForClassName( MyModel::class, 1, 10, ['user' => 'joe.bloggs'] ); $results = MondocRetentionService::getPage( $paginator // The filter is carried through by the pagination helper ); // This will return the `MongoRetentionModel` for the `MyModel` with the user `joe.bloggs` $firstResultInflated = $results[0]->toOriginalModel(); echo $firstResultInflated->getName(); // John Doe
查询构建
查询构建由 MondocBuilder
库处理 https://github.com/district-5/php-mondoc-builder。
查询构建器不考虑模型的属性,而是考虑数据库的属性。这意味着它不会监听或遵循模型上设置的任何字段映射。
例如,此映射要求查询构建器使用 n
键,而不是 name
<?php use District5\Mondoc\Db\Model\MondocAbstractSubModel; class MyModel extends MondocAbstractSubModel { protected array $mondocFieldAliases = [ 'n' => 'name', ]; // Rest of your model code... } $wontWork = new \District5\Mondoc\Db\Builder\MondocBuilder\MondocBuilder(); $wontWork->addFilter('name', 'John'); // This WILL NOT WORK with the field mapping $willWork = new \District5\Mondoc\Db\Builder\MondocBuilder\MondocBuilder(); $willWork->addFilter('n', 'John'); // This will work with the field mapping
测试
您可以通过运行 composer install
然后运行 phpunit
来运行 PHPUnit 对库的测试。在此操作之前,您需要将 MONGO_CONNECTION_STRING
环境变量分配给有效的 MongoDB 连接字符串。