dunglas/doctrine-json-odm
Doctrine JSON ODM maps JSON documents to PHP objects using Doctrine-style metadata, enabling persistence and querying of JSON data through a familiar ORM-like API. Useful for working with JSON stored in files or other backends while keeping domain models clean.
Installation Update Composer to ensure compatibility with newer dependencies:
composer require dunglas/doctrine-json-odm:^1.5.0
Verify your config/app.php includes the service provider:
'providers' => [
// ...
Dunglas\DoctrineJsonOdm\DoctrineJsonOdmServiceProvider::class,
],
Database Setup
Ensure your RDBMS supports JSONB (PostgreSQL recommended) or JSON (MySQL 5.7+, SQLite 3.9+). Run migrations to create tables with JSON/JSONB columns.
For PostgreSQL, leverage the new jsonb_document type for optimized storage/retrieval.
First Model
Define a model with a JSON field (e.g., settings) or use the new jsonb_document type:
use Dunglas\DoctrineJsonOdm\Mapping\Annotations as Json;
#[Json\Document(collection: "users")]
class User {
#[Json\Id]
private ?string $id;
// Standard JSON field
#[Json\Field(type: "json")]
private array $settings = [];
// NEW: jsonb_document type (PostgreSQL optimized)
#[Json\Field(type: "jsonb_document")]
private array $metadata = [];
}
Basic CRUD Use Eloquent-like syntax. The package now supports Symfony 8 and DBAL 4.3+:
$user = new User();
$user->settings = ['theme' => 'dark'];
$user->metadata = ['created_at' => now()->toDateTimeString()]; // JSONB-optimized
$user->save(); // Automatically serializes to JSON/JSONB
Nested JSON Structures
Leverage the new jsonb_document type for complex nested data (PostgreSQL):
#[Json\Field(type: "jsonb_document")]
private array $profile = [
'address' => [
'street' => '',
'city' => ''
],
'preferences' => ['notifications' => true]
];
Querying JSON/JSONB Fields Use Doctrine’s query builder with JSONB-specific operators (PostgreSQL):
$users = $entityManager->createQueryBuilder()
->select('u')
->from(User::class, 'u')
->where('u.metadata @> :value') // JSONB contains operator
->setParameter('value', '{"created_at": "2023-01-01"}')
->getQuery()
->getResult();
Hybrid ORM/ODM
Combine with Eloquent for relational data. Ensure jsonb_document fields are used where performance is critical:
#[Json\Document(collection: "posts")]
class Post {
#[Json\Id]
private ?int $id;
#[Json\Field(type: "jsonb_document")] // Optimized for large metadata
private array $metadata;
#[Json\ReferenceOne(targetDocument: User::class)]
private ?User $author;
}
Event Listeners Hook into lifecycle events for pre/post-processing (now compatible with Symfony 8):
$entityManager->getEventManager()->addEventListener(
\Doctrine\ODM\MongoDB\Events::prePersist,
function ($event) {
$user = $event->getDocument();
$user->metadata['last_updated'] = now()->toDateTimeString();
}
);
Custom Types
Extend JsonType or JsonbDocumentType (new) for domain-specific logic:
use Dunglas\DoctrineJsonOdm\Types\JsonbDocumentType;
class TagsType extends JsonbDocumentType {
public function convertToPHPValue($value) {
return explode(',', $value['tags'] ?? '');
}
}
Schema Migrations
Schema::table('users', function (Blueprint $table) {
$table->jsonb('metadata')->nullable()->change(); // Explicit JSONB
});
jsonb_document type.Case Sensitivity
jsonb_path_ops for case-insensitive queries:
$query->where('u.metadata ? :value')->setParameter('value', '{"Theme": "dark"}');
Circular References
@Json\ReferenceOne for relationships.Performance
jsonb_document for large datasets or frequent queries (PostgreSQL).#[Json\Index(name: "metadata_created_idx", fields: ["metadata->>'created_at'"])]
$entityManager->getConnection()->getConfiguration()->setSQLLogger(
new \Doctrine\DBAL\Logging\EchoSQLLogger()
);
symfony/dependency-injection and symfony/http-client are updated:
composer require symfony/dependency-injection:^6.3 symfony/http-client:^6.3
Custom Hydration
Override JsonbDocumentType::convertToPHPValue() for domain objects:
public function convertToPHPValue($value) {
return new SettingsCollection(json_decode($value, true));
}
Indexing Use PostgreSQL’s GIN indexes for JSONB fields:
#[Json\Index(name: "metadata_gin_idx", type: "gin")]
private array $metadata;
Validation Combine with Symfony’s Validator (tested with Symfony 8):
use Symfony\Component\Validator\Constraints as Assert;
#[Json\Field(type: "jsonb_document")]
#[Assert\All({
new Assert\Type('string'),
new Assert\Length(max: 50)
})]
private array $tags;
Caching
Cache queries with Doctrine\Common\Cache\CacheProvider (works with Symfony 8):
$query->useResultCache(true, 3600, 'app_jsonb_odm_cache');
Symfony 8 Integration
autowire: true in config/services.yaml for dependency injection:
services:
Dunglas\DoctrineJsonOdm\DoctrineJsonOdmServiceProvider:
autowire: true
How can I help you explore Laravel packages today?