thoemo/persistence-mongo (2.2.1)

Published 2026-10-25 16:13:08 +00:00 by elnebuloso

Installation

{
	"repositories": [{
			"type": "composer",
			"url": ""
		}
	]
}
composer config repositories.thoemo composer 
composer require thoemo/persistence-mongo:2.2.1

About this package

Persistence MongoDB Implementation

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-mongodb
  • mongodb/mongodb ^1.17 || ^2.0 (installed with the package; 2.x is allowed because current Debian trixie PHP images ship ext-mongodb 2.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
Details
Composer
2026-10-25 16:13:08 +00:00
2
2026-10-25 16:22:32 +00:00
proprietary
5.8 KiB
Assets (1)
Versions (3) View all
2.2.1 2026-10-25
2.1.3 2026-10-25
2.1.2 2026-10-25