mindy/orm-bundle
Symfony bundle that integrates Mindy ORM, providing configuration, service wiring, and persistence support for entities and repositories. It offers a lightweight alternative to Doctrine for PHP applications that need straightforward database mapping and querying.
Confirm the target framework before anything else. This is a MindyPHP bundle (Mindy\Bundle\OrmBundle\MindyOrmBundle), not a Laravel package. There is no ServiceProvider, no config/*.php publish step, and no Artisan integration. If you're in a Laravel app, stop here — Eloquent is your ORM. Everything below assumes a MindyPHP (or Symfony-kernel-compatible) application where you can register a bundle.
1. Require and register.
composer require mindy/orm-bundle
Register MindyOrmBundle in the kernel's bundle list. The bundle's DI extension bootstraps the ORM's connection state — models calling Model::objects() before the bundle is registered will fail with connection-not-configured style errors, not a clear "bundle missing" message.
2. Configure the connection. Configuration lives under the DI extension's alias (verify via DependencyInjection/*Extension::getAlias() — likely mindy_orm). There's no published config reference; Configuration.php is the reference. Expect the usual driver/host/database/user/password/charset keys.
3. Read source in this order.
MindyOrmBundle.php + DependencyInjection/ — config tree, service IDs, autowiring.Resources/config/ — service definitions; this is your integration surface.mindy/orm package — Model, QuerySet, fields/. That's ~90% of the daily API; the bundle is thin glue.4. First real task: map one table and read a list. Define a class extending Mindy\Orm\Model, implement fields() with IntegerField, CharField, DateTimeField, etc., then fetch via Model::objects()->filter([...])->order(['-id'])->paginate($page, $perPage). Get read working end-to-end before touching writes — writes drag in Unit of Work semantics and are where this ORM's rough edges live.
Thin model, explicit fields. Keep fields() declarative and free of persistence logic. Field instances carry metadata (type, nullable, PK, defaults); the model is just a shape. Anything that needs the connection belongs in a repository or the queryset, not a field definition.
Model::objects() is your queryset entry point. Build reads as method chains returning QuerySet: filter(), exclude(), order(), limit(), offset(), all(), count(), exists(). This is the pattern you'll use 80% of the time; treat filter() and order() signatures as the first thing to learn from QuerySet.php.
Repository pattern on top, not instead. For anything reused, wrap objects() calls in a repository class that returns models or DTOs. This caps the surface area of the ORM you depend on — critical for a 1-star, single-maintainer package. If you later migrate, you swap the repository body, not every call site.
Unit of Work for writes. Mutations are tracked and flushed, not executed per-property. The pattern is: load via queryset → mutate fields on the model → flush via the manager (or save() on the model, depending on which layer the bundle exposes). Batch related inserts/updates inside one flush to avoid partial-write surprises. Verify explicitly whether the bundle exposes an injectable manager service or whether the unit of work is model-scoped — the answer changes your service wiring.
Relations are lazy — plan around it. Expect ForeignKeyField / ManyToManyField resolution on access. Use queryset-level filters (join-equivalent conditions in filter()) to avoid N+1 when listing. If eager loading exists (with() / select_related()-style), use it on every list view; if it doesn't, restructure the query rather than iterating relations.
Transactions at the repository boundary. Since the Unit of Work is flush-driven, wrap multi-model operations in an explicit transaction at the repository method level. Do not assume a flush is atomic across models.
Seed config-driven tests. Because models carry no connection of their own, the fastest test setup is a bundle-configured in-memory SQLite (or test DB) with migrations applied per test class. Keep repository tests integration-style — mocking a data mapper this thin gives you confidence in the mock, not the query.
Framework coupling is the top gotcha. The bundle assumes kernel boot and a Symfony-style container. It will not resolve in a bare PHP script without manually bootstrapping the connection — useful to know if you're writing CLI tools or integration tests outside the app.
Static/global connection state. Mindy's ORM historically holds connection state statically via the bundle extension. Under long-running workers (Octane-like or queue daemons), verify that connection state is reset per job. If it isn't, expect stale identity-map reads and, worst case, cross-request leakage. Test this before production.
Config alias mismatch. The config key is derived from getAlias(); if you guess mindy_orm and get no error but also no working connection, the extension likely silently accepted an empty tree. Confirm the exact alias from source.
Field default ≠ DB default. Field objects carry defaults used when instantiating a model; they don't necessarily emit DEFAULT clauses in the schema. Don't rely on the ORM for DDL — own migrations separately, or you'll chase phantom differences between PHP defaults and DB defaults.
No published docs. Debugging means reading mindy/orm source directly. Keep a local checkout of the upstream package and search Model.php / QuerySet.php for the method you're using before assuming behavior. Do not trust README examples for edge cases (nulls, empty filters, pagination boundaries).
Watch PHP version constraints. Before you build on it, check composer.json in both this bundle and mindy/orm for a hard ^7.x constraint. Modern PHP 8.2+ apps will hit type-deprecation issues at best and runtime errors at worst; there is no CI signal upstream to tell you.
Extension points to prefer. If you must customize behavior, subclass Model / QuerySet rather than editing vendor code, and bind your subclass in the bundle's service definitions. This is the only sustainable customization path when upstream is dormant — you keep a clean diff against vendor for the fork you'll inevitably maintain.
Treat it as internally owned code. One maintainer, one star, zero dependents. If it ships, you're the maintainer. Keep every direct reference to the bundle's namespace confined to one small package (App\Support\MindyOrm or equivalent) so a future swap to Eloquent, Doctrine, or Cycle is a rewrite of that package, not the app.
How can I help you explore Laravel packages today?