doctrine/mongodb-odm
Doctrine MongoDB ODM is an object document mapper for PHP that brings Doctrine-style persistence to MongoDB. Define documents with metadata, map fields and relations, run queries, and handle unit of work, identity map, and migrations for MongoDB apps.
Start by installing the package via Composer and configuring a basic DocumentManager. The Configuration class is central—create one, set your MongoDB connection (via MongoDB\Client), and register your document namespaces. Use attributes (not annotations) for mapping—2.16+ moved annotation support to optional dependencies. A minimal setup:
composer require doctrine/mongodb-odm
use Doctrine\ODM\MongoDB\Configuration;
use Doctrine\ODM\MongoDB\DocumentManager;
$config = new Configuration();
$config->setProxyDir(__DIR__ . '/proxies');
$config->setProxyNamespace('Proxies');
$config->setHydratorDir(__DIR__ . '/hydrators');
$config->setHydratorNamespace('Hydrators');
$config->setDefaultDB('myapp');
$config->setPersistentDir(__DIR__ . '/persistent'); // for PersistentCollection
$dm = DocumentManager::create(new \MongoDB\Client('mongodb://localhost:27017'), $config);
Then define your first document using attributes (2.16+ requires #[Document], #[Id], etc.):
use Doctrine\ODM\MongoDB\Mapping\Attributes as ODM;
#[ODM\Document]
class User
{
#[ODM\Id]
public ?string $id = null;
#[ODM\Field(type: 'string')]
public string $email;
#[ODM\Field(type: 'string')]
public string $name;
}
First use case: persist and fetch a user.
$user = new User();
$user->email = 'alice@example.com';
$user->name = 'Alice';
$dm->persist($user);
$dm->flush();
$alice = $dm->getRepository(User::class)->findOneBy(['email' => 'alice@example.com']);
flush() inside a transaction—ODM now supports MongoDB multi-document transactions.$dm->transactional(function ($dm) {
$user = new User();
$user->email = 'bob@example.com';
$dm->persist($user);
// Multiple operations can be grouped; all succeed or fail.
});
$query = $dm->createQueryBuilder(User::class)
->field('name')->equals('Alice')
->field('createdAt')->gt(new \DateTime('-30 days'))
->sort('createdAt', 'desc')
->limit(10);
$users = $query->getQuery()->execute();
VectorField and $vectorSearch for embedding-based similarity:$vector = [0.1, 0.9, -0.3]; // embedding vector
$result = $dm->createQueryBuilder(Product::class)
->vectorSearch('embedding', $vector, 10, 'vectorIndex')
->execute();
#[ODM\MappedSuperclass(collection: 'users')]
abstract class BaseUser { /* ... */ }
#[ODM\Document(collection: 'admin_users')]
class AdminUser extends BaseUser { /* ... */ }
$config->setUseNativeLazyObject(true);
$config->setLazyGhostObjectEnabled(false); // prefer native over proxy-manager
Attribute namespace: Since 2.16+, attributes live under Doctrine\ODM\MongoDB\Mapping\Attributes, not Annotations. Mixing old annotations without doctrine/annotations installed leads to silent failures.
Cache dependencies: doctrine/cache is now optional (2.16+). If you relied on caching metadata, you must configure a custom MetadataCache or expect performance regression.
Discriminator deprecation: Array syntax for discriminatorField is deprecated (2.16+). Use #[DiscriminatorField] and #[DiscriminatorMap] instead.
PersistentCollection selection: As of 2.16, PersistentCollection implements Selectable, enabling easier integration with Symfony forms and UI libraries—use it to populate select menus.
Type deprecations: Overriding Type::closureToPHP() without implementing it is deprecated (2.16+). Also, avoid overriding sleep() on ClassMetadata or PersistentCollection.
UUID hydration: Use uuid field type with Symfony\Component\Uid\Uuid, and ensure binary storage (bin type) for efficient index scans.
Search index dependency: If $search or $vectorSearch pipelines return empty results, the new assertion in 2.13 will throw—ensure search indexes exist via SchemaManager::waitForSearchIndexes() in dev/test.
Testing pitfalls: Queries with ReferenceMany or EmbedMany always hydrate full references in 2.15.2+—this avoids partial references but may impact perf if you forget to use cascade="remove" or cascade="persist".
Migrating schemas: Always regenerate proxies and hydrators (or enable auto-generation in dev) when changing metadata—ClassMetadata changes are not auto-refreshed.
Upgrade path: 2.15+ requires doctrine/collections v2—update symfony/var-exporter and phpdocumentor/reflection-docblock if you hit type errors in generated proxy files.
How can I help you explore Laravel packages today?