thoemo/persistence-mongo (2.2.1)
Installation
{
"repositories": [{
"type": "composer",
"url": " "
}
]
}composer config repositories.thoemo composer composer require thoemo/persistence-mongo:2.2.1About this package
thoemo/persistence-mongo
Base classes for MongoDB repositories built on mongodb/mongodb: a connection holder, an abstract
repository with collection access and a rebuild-and-swap "transaction", and an abstract hydrator
that stores an object's jsonSerialize() data in a document.
Requirements
- PHP >= 8.2
ext-json,ext-mongodbmongodb/mongodb^1.17 || ^2.0(installed with the package; 2.x is allowed because current Debian trixie PHP images shipext-mongodb2.x)
Installation
The package is served by the Forgejo Composer registry (no credentials needed). Add the repository
to your composer.json:
"repositories": {
"packages.link.thoemo.net/thoemo": {
"type": "composer",
"url": "https://packages.link.thoemo.net/api/packages/thoemo/composer"
}
}
Then:
composer require thoemo/persistence-mongo
The namespace is Thoemo\Persistence\Repository\Mongo. Before 2.2 it was elnebuloso\Persistence\Repository\Mongo; update your use statements when upgrading.
Overview
| Type | Purpose |
|---|---|
MongoConnection (class) |
Holds a MongoDB\Client and the database name: new MongoConnection($client, $databaseName). |
AbstractMongoRepository (class) |
Base repository. You implement getCollectionName() and your own store/find methods. |
AbstractMongoHydrator (class) |
Base hydrator with extractDocument() and hydrateObject() to build on. |
MongoDocumentAware (interface) |
Contract for stored objects: JsonSerializable, __construct(array $data), getId(): string. |
AbstractMongoRepository:
| Method | Visibility | Effect |
|---|---|---|
__construct(MongoConnection $connection) |
public | |
getCollectionName(): string |
abstract | Name of the collection this repository works on. |
getCollection(): MongoDB\Collection |
protected | The current collection (the temporary one during a transaction). |
selectCollection(string $collection) |
protected | Any other collection in the same database. |
getTotalCount(): int |
public | countDocuments() on the current collection. |
startTransaction() |
public | Drops <collection>_tmp and directs getCollection() to it. |
closeTransaction() |
public | Renames <collection>_tmp to <collection>, replacing it. |
AbstractMongoHydrator:
| Method | Effect |
|---|---|
extractDocument(MongoDocumentAware $document, array $impl = []): BSONDocument |
Returns ["data" => $document->jsonSerialize(), "_impl" => $impl]. |
hydrateObject(BSONDocument $document): array |
Returns the data field as a plain PHP array (via a JSON round trip). |
"Transactions"
startTransaction()/closeTransaction() are not MongoDB transactions. They rebuild a collection
from scratch: between the two calls every write through getCollection() goes to
<collection>_tmp, and closeTransaction() renames that collection over the real one
(renameCollection with dropTarget). Documents not written during the transaction are gone
afterwards. If nothing was written, the real collection is left as it is. Other repository instances
keep seeing the old collection until closeTransaction().
Usage
The entity (any class implementing MongoDocumentAware):
use Thoemo\Persistence\Repository\Mongo\MongoDocumentAware;
final class Product implements MongoDocumentAware
{
public function __construct(private array $data)
{
}
public function getId(): string
{
return $this->data["id"];
}
public function jsonSerialize(): array
{
return $this->data;
}
}
The hydrator exposes typed methods on top of the protected base methods:
use Thoemo\Persistence\Repository\Mongo\AbstractMongoHydrator;
use MongoDB\Model\BSONDocument;
final class ProductHydrator extends AbstractMongoHydrator
{
public function extract(Product $product): BSONDocument
{
return $this->extractDocument($product);
}
public function hydrate(BSONDocument $document): Product
{
return new Product($this->hydrateObject($document));
}
}
The repository decides how documents are keyed; here the entity id becomes _id:
use Thoemo\Persistence\Repository\Mongo\AbstractMongoRepository;
use Thoemo\Persistence\Repository\Mongo\MongoConnection;
use MongoDB\Model\BSONDocument;
final class ProductRepository extends AbstractMongoRepository
{
public function __construct(MongoConnection $connection, private ProductHydrator $hydrator)
{
parent::__construct($connection);
}
public function store(Product $product): void
{
$this->getCollection()->updateOne(
["_id" => $product->getId()],
['$set' => $this->hydrator->extract($product)],
["upsert" => true],
);
}
public function findOne(string $id): ?Product
{
$document = $this->getCollection()->findOne(["_id" => $id]);
return $document instanceof BSONDocument ? $this->hydrator->hydrate($document) : null;
}
protected function getCollectionName(): string
{
return "products";
}
}
Wiring it up and rebuilding the collection:
use Thoemo\Persistence\Repository\Mongo\MongoConnection;
use MongoDB\Client;
$connection = new MongoConnection(new Client("mongodb://user:password@mongo:27017"), "shop");
$repository = new ProductRepository($connection, new ProductHydrator());
$repository->startTransaction();
foreach ($importedProducts as $product) {
$repository->store($product);
}
$repository->closeTransaction();
$repository->findOne("p-1");
Development
Tests and tools run in Docker through redo; build.yml holds the project's redo properties.
The tests need a MongoDB server, so they run with phpunit:compose instead of phpunit: it starts
the stack in docker-compose-phpunit.yml (MongoDB 4.4 as mongo:27017, user user, password
password), runs phpunit in the main container and tears the stack down again. The tests drop
and recreate the database demo.
redo composer:install phpunit:compose # tests on PHP 8.2 (php_version in build.yml)
redo composer:install phpunit:compose -Dphp_version=8.4 # tests on another PHP version
redo commit # clean:dist, prettier:fix, phplint, phpunit:compose, composer:validate:require
Releases
Every push to main runs the GitLab pipeline: phplint, composer:validate:require and
phpunit:compose on PHP 8.2 to 8.5. It then raises the patch version in VERSION and uploads that
version to the Forgejo registry. Only README.md, composer.json and src/ are part of the
released archive.
Dependencies
Dependencies
| ID | Version |
|---|---|
| ext-json | * |
| ext-mongodb | * |
| mongodb/mongodb | ^1.17 || ^2.0 |
| php | >=8.2 |