mandango/mondator
Mondator is the code generator for Mandango, a PHP MongoDB ODM. It compiles YAML document definitions into optimized PHP model classes, reducing boilerplate and helping you build structured MongoDB-backed applications faster.
Install via Composer:
composer require mandango/mondator
Before writing a single line, open the Configuration class and the bundled Yaml, Xml, and Php config loaders — that is where 90% of day-to-day behavior lives. Mondator is fundamentally a config-in, PHP-classes-out pipeline, so your first step is always authoring a config file, not PHP code.
A minimal YAML config looks like:
Blog\Document\Post:
fields:
title: { type: string }
body: { type: string }
Blog\Repository\PostRepository:
document: Blog\Document\Post
Then generate:
vendor/bin/mondator generate --config=mondator.yml --output=src/
Your first real use case is typical for a MongoDB ODM project: define your document schemas in YAML (or XML/PHP), let Mondator emit concrete PHP classes with getters/setters, mapping metadata, and repository scaffolding, then commit the generated classes (or regenerate in CI — see Gotchas).
The two directories to read in the source tree, in order:
src/Mandango/Mondator/Configuration.php — the schema for what you can generate.src/Mandango/Mondator/Extension/ and src/Mandango/Mondator/Processor/ — the default pipeline that turns config into classes. Every customization hooks here.Config-driven generation with multiple config files. Split configs by domain (blog.yml, users.yml, billing.yml) and pass --config multiple times, or use a parent config that includes others. Each top-level key is a fully-qualified class name to generate, so keeping domains separate keeps diffs reviewable.
Custom processors for cross-cutting concerns. The default pipeline runs processors in a fixed order (fields → methods → metadata → parent class → interfaces → constructor → extensions). If you need every generated class to, say, implement JsonSerializable or add a toArray() method, write a ProcessorInterface implementation and register it in Configuration rather than post-processing the output with regex. This is the idiomatic extension point.
Custom extensions for whole-project wiring. Subclass Extension and override configClass() or add processors to the default list. This is how Mandango itself layers ODM-specific behavior on top of base Mondator. If your project has a repeated shape (e.g. every document has an id, createdAt, updatedAt), bake it into a reusable extension instead of repeating config.
Templating with Twig. Generated code comes from Twig templates under src/Mandango/Mondator/Resources/templates/. Override a template by setting template in config to a path, or by registering a custom template loader. For one-off class shapes (value objects, aggregates), a dedicated template is cleaner than stacking processors.
CLI in your build flow. Wire mondator generate into a Composer script:
"scripts": {
"generate": "mondator generate --config=mondator.yml --output=src/Generated",
"post-install-cmd": ["@generate"]
}
Regenerating in tests. In integration tests, call the Mondator builder directly with an in-memory config and assert on the emitted class source. This catches template regressions faster than booting the full ODM.
Generated output is not automatically refreshed. Mondator has no watch mode — you must rerun the CLI after every config change. Adopt a make generate target and a CI check (git diff --exit-code after regeneration) or you will silently ship stale classes.
Namespace-to-path mapping is strict. The output directory plus the FQCN determine the file path. A typo in a namespace key puts the file in the wrong directory with no error — just a class your autoloader can't find. Use --verbose when debugging "class not found" after generation.
Template overrides are all-or-nothing per type. If you override the document template to tweak one method, you inherit maintenance of the entire template forever as Mondator evolves. Prefer a processor that injects a method into the existing rendered code, or extend the template with a custom block if the base template permits it via {% block %}.
Processor order matters more than you think. Default processors assume fields and metadata exist before methods and parent classes are resolved. A custom processor that depends on $class->getParentClass() must be inserted after the parent-class processor; a processor that adds fields must run before metadata generation. Inspect Configuration's processor list before appending.
YAML parsing is picky about anchors and booleans. yes/no/on/off become booleans in some parsers, which quietly breaks field configs named on or off. Quote ambiguous values. Prefer the PHP config loader in environments where you want IDE autocompletion and static checking.
Do not commit generated classes twice under different roots. It's tempting to have src/ and a separate generated/ directory — pick one. Mixed roots cause duplicate class definitions once you switch branches.
Debugging a bad generation: the fastest path is --verbose plus setting debug: true in config (if using the Symfony-style config layer), then reading the intermediate ClassDefinition via a tiny standalone script that calls Mondator::createBuilder($config)->build(). Inspecting the definition object before templates render isolates config issues from template issues.
Extension points are stable API; templates are not. Processors and extensions are documented and versioned. The Twig templates are internal — upgrading Mondator versions may break a forked template without a minor-version signal. Treat a template override as a locked-in fork and note the Mondator version alongside it.
How can I help you explore Laravel packages today?