Skip to main content

MongoDB Support

Weaver ORM includes first-class support for MongoDB through a document-oriented abstraction layer. The API mirrors the relational ORM closely, so switching between SQL and MongoDB entities requires minimal mental overhead.


AbstractDocumentMapper​

All MongoDB document mappers extend Weaver\ORM\MongoDB\AbstractDocumentMapper. The mapper describes the collection name, field mappings, and embedded documents.

<?php

namespace App\Mapping\Document;

use Weaver\ORM\MongoDB\AbstractDocumentMapper;
use Weaver\ORM\MongoDB\Attributes\DocumentCollection;
use Weaver\ORM\MongoDB\Attributes\Field;
use Weaver\ORM\MongoDB\Attributes\EmbedOne;
use Weaver\ORM\MongoDB\Attributes\EmbedMany;
use App\Document\Product;

#[DocumentCollection(name: 'products')]
class ProductMapper extends AbstractDocumentMapper
{
public string $document = Product::class;

#[Field(name: '_id', type: 'objectId')]
public string $id;

#[Field(type: 'string')]
public string $name;

#[Field(type: 'float')]
public float $price;

#[Field(type: 'string')]
public string $status;

#[Field(type: 'date')]
public \DateTimeImmutable $createdAt;

#[EmbedMany(document: 'App\Document\Tag')]
public array $tags = [];

#[EmbedOne(document: 'App\Document\Address')]
public ?object $shippingAddress = null;
}

#[Document] mapping​

Annotate document classes with #[Document] to associate them with their mapper:

<?php

namespace App\Document;

use Weaver\ORM\MongoDB\Attributes\Document;

#[Document(mapper: \App\Mapping\Document\ProductMapper::class)]
class Product
{
public string $id;
public string $name;
public float $price;
public string $status;
public \DateTimeImmutable $createdAt;
public array $tags = [];
}

Embedded documents are plain PHP classes without the #[Document] attribute:

<?php

namespace App\Document;

class Tag
{
public function __construct(
public string $name,
public string $slug,
) {}
}

class Address
{
public string $street;
public string $city;
public string $country;
public string $postCode;
}

DocumentQueryBuilder​

DocumentQueryBuilder provides a fluent API for querying MongoDB collections. It maps closely to the relational EntityQueryBuilder:

<?php

use App\Document\Product;

$products = $this->productRepository
->query()
->where('status', 'active')
->where('price', '<=', 100.0)
->orderBy('createdAt', 'DESC')
->limit(20)
->get();

Filter operators​

MongoDB-specific filter operators are supported via the where() method with operator syntax and via dedicated helpers:

<?php

// Standard comparison operators
$query->where('price', '>=', 50);
$query->where('stock', '!=', 0);

// Array operators
$query->whereIn('status', ['active', 'featured']);
$query->whereNotIn('category', ['archived']);

// Existence check
$query->whereNull('deletedAt');
$query->whereNotNull('publishedAt');

// Regex (MongoDB-specific)
$query->whereRegex('name', '/^Widget/i');

// Embedded field queries using dot notation
$query->where('shippingAddress.country', 'DE');
$query->where('tags.name', 'sale');

Array field queries​

<?php

// Match documents where the 'tags' array contains 'sale'
$onSale = $this->productRepository
->query()
->whereContains('tags.slug', 'sale')
->get();

// Match documents where the array size equals N
$query->whereArraySize('images', 3);

Aggregation pipeline​

For complex aggregation queries, access the raw collection through the document repository:

<?php

$pipeline = [
['$match' => ['status' => 'active']],
['$group' => ['_id' => '$category', 'count' => ['$sum' => 1]]],
['$sort' => ['count' => -1]],
['$limit' => 10],
];

$results = $this->productRepository->aggregate($pipeline);

DocumentPersistence​

Weaver\ORM\MongoDB\DocumentPersistence handles insert, update, and delete operations for documents. It integrates with EntityWorkspace so the same add(), push(), and delete() calls work for both SQL entities and MongoDB documents.

<?php

use App\Document\Product;
use App\Document\Tag;

$product = new Product();
$product->name = 'Widget Pro';
$product->price = 49.99;
$product->status = 'active';
$product->tags = [new Tag('sale', 'sale'), new Tag('new', 'new')];

$this->workspace->add($product);
$this->workspace->push(); // inserts into MongoDB 'products' collection

Update operations track changed fields and issue targeted $set operations rather than replacing the entire document:

<?php

$product = $this->productRepository->findOrFail($id);
$product->price = 39.99;
$product->status = 'sale';

$this->workspace->push();
// Executes: db.products.updateOne({_id: ...}, {$set: {price: 39.99, status: 'sale'}})

Multi-database support​

Configure multiple MongoDB databases in config/packages/weaver.yaml:

weaver:
mongodb:
connections:
default:
uri: '%env(MONGODB_URI)%'
database: '%env(MONGODB_DATABASE)%'
analytics:
uri: '%env(ANALYTICS_MONGODB_URI)%'
database: 'analytics'

Assign a connection to a mapper:

<?php

use Weaver\ORM\MongoDB\Attributes\DocumentCollection;

#[DocumentCollection(name: 'events', connection: 'analytics')]
class EventMapper extends AbstractDocumentMapper
{
// This mapper uses the 'analytics' connection
}

Repositories for mappers bound to a specific connection automatically use that connection for all queries and writes.


Repository pattern for documents​

Document repositories follow the same pattern as relational entity repositories:

<?php

namespace App\Repository\Document;

use App\Document\Product;
use Weaver\ORM\MongoDB\DocumentRepository;

/**
* @extends DocumentRepository<Product>
*/
class ProductDocumentRepository extends DocumentRepository
{
public function findByCategoryAndPriceRange(
string $category,
float $minPrice,
float $maxPrice,
): \Weaver\ORM\Collection\EntityCollection {
return $this->query()
->where('category', $category)
->where('price', '>=', $minPrice)
->where('price', '<=', $maxPrice)
->where('status', 'active')
->orderBy('price')
->get();
}

public function findByTag(string $tagSlug): \Weaver\ORM\Collection\EntityCollection
{
return $this->query()
->whereContains('tags.slug', $tagSlug)
->orderByDesc('createdAt')
->get();
}
}

Inject and use exactly as you would a relational repository:

<?php

class ProductController extends AbstractController
{
public function __construct(
private readonly ProductDocumentRepository $products,
) {}

#[Route('/products/tag/{slug}')]
public function byTag(string $slug): JsonResponse
{
return $this->json(
$this->products->findByTag($slug)->toArray()
);
}
}