PHPackages                             patchlevel/odm - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. patchlevel/odm

ActiveLibrary[Database &amp; ORM](/categories/database)

patchlevel/odm
==============

A simple ODM for MongoDB and Postgres

1.0.x-dev(1mo ago)23↑2900%[1 PRs](https://github.com/patchlevel/odm/pulls)MITPHPPHP ~8.3.0 || ~8.4.0 || ~8.5.0CI failing

Since Mar 2Pushed 1mo agoCompare

[ Source](https://github.com/patchlevel/odm)[ Packagist](https://packagist.org/packages/patchlevel/odm)[ Docs](https://github.com/patchlevel/odm)[ RSS](/packages/patchlevel-odm/feed)WikiDiscussions 1.0.x Synced 1mo ago

READMEChangelogDependencies (11)Versions (3)Used By (0)

Patchlevel ODM
==============

[](#patchlevel-odm)

Patchlevel ODM is a lightweight **Object Document Mapper (ODM)** for PHP that works with **MongoDB** and **PostgreSQL (via [patchlevel/rango](https://github.com/patchlevel/rango/))**. It is built on top of our superfast **[patchlevel/hydrator](https://github.com/patchlevel/hydrator/)**, providing a simple attribute-based mapping layer and enterprise-grade features like cyptography.

🚀 Why Patchlevel ODM?
---------------------

[](#-why-patchlevel-odm)

- **Postgres and MongoDB support** – Use the same ODM for both Postgres and MongoDB, with a consistent API.
- **Attribute-based Mapping** – Define documents and indexes using modern PHP attributes.
- **No Unit of Work** – Patchlevel ODM does not use a Unit of Work, giving you more control over when changes are persisted.
- **Built on Patchlevel Hydrator** – Benefit from the performance and extensibility (like [crypto shredding](https://github.com/patchlevel/hydrator/#cryptography)) of our powerful [hydrator library](https://github.com/patchlevel/hydrator/).

📦 Installation
--------------

[](#-installation)

You can install Patchlevel ODM using Composer. Depending on your database choice, you will need to require the appropriate packages.

### PostgreSQL (via [Rango](https://github.com/patchlevel/rango/))

[](#postgresql-via-rango)

```
composer require patchlevel/odm patchlevel/rango
```

### MongoDB

[](#mongodb)

```
composer require patchlevel/odm mongodb/mongodb
```

🛠 How it Works
--------------

[](#-how-it-works)

Patchlevel ODM maps PHP objects to document storage.

- **Documents** are defined using the `#[Document]` attribute.
- **Identifiers** are declared with `#[Id]`.
- **Indexes** can be defined using `#[Index]`.
- **Repositories** provide a simple API for loading and storing documents.

Internally the ODM uses:

- **[patchlevel/rango](https://github.com/patchlevel/rango/)** as the database abstraction layer for PostgreSQL
- **[mongodb/mongodb](https://github.com/mongodb/mongo-php-library)** as the database abstraction layer for MongoDB
- **[patchlevel/hydrator](https://github.com/patchlevel/hydrator/)** for object mapping and normalization

🚦 Quick Start
-------------

[](#-quick-start)

Define your documents and indexes using PHP attributes.

```
use Patchlevel\ODM\Attribute\Document;
use Patchlevel\ODM\Attribute\Id;
use Patchlevel\ODM\Attribute\Index;

#[Document('profiles')]
#[Index('by_status', ['status' => 'asc'])]
final class Profile
{
    /** @param list $skills */
    public function __construct(
        #[Id]
        public readonly string $id,
        public string $name,
        public Status $status,
        public array $skills,
    ) {
    }
}

final readonly class Skill
{
    public function __construct(
        public string $value,
    ) {
    }
}

enum Status: string
{
    case ACTIVE = 'active';
    case INACTIVE = 'inactive';
}
```

### Setup PostgreSQL (via [Rango](https://github.com/patchlevel/rango/))

[](#setup-postgresql-via-rango)

```
use Patchlevel\ODM\Repository\RangoRepositoryManager;
use Patchlevel\Rango\Client;

$client = new Client($_ENV['POSTGRES_URI']);

$manager = RangoRepositoryManager::create(
    $client->selectDatabase('patchlevel')
);
```

### Setup MongoDB

[](#setup-mongodb)

```
use MongoDB\Client;
use Patchlevel\ODM\Repository\MongoDBRepositoryManager;

$client = new Client($_ENV['MONGODB_URI']);

$manager = MongoDBRepositoryManager::create(
    $client->selectDatabase('patchlevel')
);
```

### Usage

[](#usage)

Now you can use the repository manager to access your documents.

```
$repository = $manager->get(Profile::class);

$repository->persist(new Profile('r-1', 'Rango', Status::ACTIVE, [new Skill('php')]));
$repository->persist(new Profile('r-2', 'Foo', Status::ACTIVE, [new Skill('node'), new Skill('js')]));
$repository->persist(new Profile('r-3', 'Bar', Status::INACTIVE, [new Skill('mongodb')]));

$profiles = $repository->findBy(
    filter: ['status' => Status::ACTIVE->value],
    sort: ['name' => 'asc'],
    limit: 10,
    offset: 0
);

$profile = $repository->find('r-2');
$profile->name = 'New Foo';
$repository->persist($profile);

$repository->remove('r-3');
```

🏗️ Design differences compared to Doctrine ODM
----------------------------------------------

[](#️-design-differences-compared-to-doctrine-odm)

Doctrine ODM has a feature that we don't want: a Unit of Work (UOW). A UOW tracks new objects and changes to existing objects. To commit changes to the database, you need to call `flush()`. In this step, the Unit of Work calculates the changes and applies them to the database.

This approach has several side effects:

- **Growing memory usage in long-running workers**
    Since the Unit of Work keeps references to managed documents, memory usage can continuously grow in worker processes unless documents are manually detached or the UOW is cleared.
- **Risk of unintentionally persisting changes**
    Because documents are tracked automatically, changes to a document may be persisted later by a `flush()` call in a completely different part of the codebase.
- **Risk of stale data**
    Managed documents may become outdated if they remain in the UOW for too long, especially in long-running processes.
- **Complex lifecycle management**
    To avoid these issues, developers often need to call `clear()` or `detach()`, which adds complexity and can easily introduce subtle bugs.

Because of these trade-offs, we intentionally avoid using a Unit of Work. Instead, repositories explicitly control persistence operations. This means every insert or update must be triggered deliberately, making database writes predictable and easier to reason about.

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance90

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 Bus Factor1

Top contributor holds 88.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

46d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/470138?v=4)[David Badura](/maintainers/DavidBadura)[@DavidBadura](https://github.com/DavidBadura)

---

Top Contributors

[![DavidBadura](https://avatars.githubusercontent.com/u/470138?v=4)](https://github.com/DavidBadura "DavidBadura (15 commits)")[![DanielBadura](https://avatars.githubusercontent.com/u/2017762?v=4)](https://github.com/DanielBadura "DanielBadura (2 commits)")

---

Tags

postgreshydratormongodbrango

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/patchlevel-odm/health.svg)

```
[![Health](https://phpackages.com/badges/patchlevel-odm/health.svg)](https://phpackages.com/packages/patchlevel-odm)
```

###  Alternatives

[mongodb/mongodb

MongoDB driver library

1.6k64.0M546](/packages/mongodb-mongodb)[doctrine/mongodb-odm

PHP Doctrine MongoDB Object Document Mapper (ODM) provides transparent persistence for PHP objects to MongoDB.

1.1k23.3M302](/packages/doctrine-mongodb-odm)[phpbu/phpbu

PHP Backup utility.

1.3k89.0k4](/packages/phpbu-phpbu)[scienta/doctrine-json-functions

A set of extensions to Doctrine that add support for json query functions.

58523.9M36](/packages/scienta-doctrine-json-functions)[apix/cache

A thin PSR-6 cache wrapper with a generic interface to various caching backends emphasising cache taggging and indexing to Redis, Memcached, PDO/SQL, APC and other adapters.

114542.8k6](/packages/apix-cache)[rybakit/phive-queue

$queue-&gt;push('I can be popped off after', '10 minutes');

16441.5k1](/packages/rybakit-phive-queue)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
