symfomany/laravel-mongo
MongoDB support for Laravel applications: work with Mongo collections through a model and query builder layer, with configuration for connecting your Laravel app to a MongoDB database. A small package for projects that want document storage alongside familiar Laravel conventions.
This is the first release of the package, so treat it as an early-adopter integration rather than a battle-tested one. The first step is confirming your environment: the package targets PHP 7 and requires the mongodb PECL extension (not the legacy mongo extension). Verify with php -m | grep mongodb before installing anything.
Installation:
composer require <vendor>/<package>
Publish any provided config (if the package ships one) and set your connection in config/database.php under a dedicated mongodb connection. Keep credentials in .env (MONGO_HOST, MONGO_DATABASE, MONGO_USERNAME, MONGO_PASSWORD) rather than hardcoding them.
Where to look first:
Moloquent, others a namespaced Model).First use case: a single Eloquent-style model backed by a MongoDB collection for data whose shape varies per record — activity logs, third-party webhook payloads, product catalog attributes, or CMS content blocks. This is where document storage pays off immediately and where the lack of a schema won't hurt you.
// app/Models/ActivityLog.php
class ActivityLog extends Moloquent
{
protected $connection = 'mongodb';
protected $collection = 'activity_logs';
protected $guarded = [];
}
Then ActivityLog::create(['event' => 'login', 'meta' => ['ip' => $request->ip()]]); and you have a working write path. Confirm reads with tinker before building anything on top.
Keep MongoDB models isolated from relational models. Do not try to swap the base model on existing Eloquent classes. Introduce parallel model classes with the Mongo base class, and let each side own its own data. Mixing the two on one model is the fastest route to confusing, hard-to-debug failures.
Relationships: embed vs. reference. For data you always read together and that has a bounded size (an address on a user, a handful of tags), use embedded relationships (embedsMany, embedsOne). For anything unbounded or independently queried (a user's orders, comments), use references (referencesMany, belongsTo) so you don't hit the 16 MB document limit or rewrite large documents on every append.
Repository/service layering. Because the query API differs from the SQL builder, wrap Mongo access behind a repository or service class rather than scattering where('meta.foo', ...) calls through controllers. This keeps the blast radius small when the package's API changes in a 1.x minor release — which it will.
Dotted-path querying is your primary tool. Nested document fields are queried with dot notation:
ActivityLog::where('meta.ip', $ip)->orderBy('created_at', 'desc')->take(50)->get();
Aggregation for anything analytical. Reach for the aggregation pipeline rather than pulling documents into PHP and filtering there:
ActivityLog::raw(function ($collection) {
return $collection->aggregate([
['$match' => ['event' => 'purchase']],
['$group' => ['_id' => '$meta.country', 'total' => ['$sum' => 1]]],
]);
});
Seed and index in code, not in the schema. There are no migrations in the SQL sense. Write a seeder for baseline data and put index creation in an idempotent artisan command (or a boot() hook guarded for local env) that calls createIndex on the underlying collection. Indexes are the single biggest lever on read performance and nothing creates them for you.
Testing. Point the test suite at a separate Mongo database and drop collections in setUp/tearDown. Avoid RefreshDatabase — it's built for SQL transactions and won't clean Mongo collections. A trait that truncates each collection used by the test is the reliable pattern.
Hybrid architecture. A common and healthy pattern: keep users, billing, and anything requiring joins or transactions in MySQL; put the high-volume, schema-flexible, append-heavy data in Mongo. Cross-store references should be plain IDs resolved in application code, not attempted joins.
Package maturity. A 1.0 with a one-line changelog means the surface area is unproven. Pin the exact version in composer.json, read the source for the methods you depend on, and budget time for a fork or patch. Assume documentation is incomplete and the test suite is the spec.
No transactions. At the time this release targets (PHP 7 era), MongoDB multi-document transactions are not available, and the package almost certainly doesn't expose them. Any operation touching more than one document is non-atomic. Design writes to be single-document where correctness matters, or implement compensating logic.
No joins. join() is not supported and will not behave as expected. Denormalize deliberately, or resolve relationships with a second query and stitch in PHP.
No schema migrations. Adding or changing fields is free; enforcing consistency is not. Validate input at the application layer, and if you need structure, consider a JSON-schema validator at the collection level rather than assuming the model will enforce it.
_id and ObjectId handling. The primary key is an ObjectId, not an auto-increment integer. Watch for: route-model binding (may need a custom getRouteKey/resolution), JSON serialization shape (it does not serialize to a plain string by default in all paths), and comparisons — where('_id', $string) often needs an explicit new ObjectId($string).
Dates and timezones. Date fields come back as UTCDateTime/MongoDate objects. Configure the package's date handling and cast consistently, otherwise created_at comparisons and JSON output will surprise you.
Query builder differences. whereIn, orWhere, nested where closures, and orderBy mostly work, but edge cases diverge from SQL Eloquent. When a query returns nothing unexpectedly, drop to ->toMongo() (or the package's raw query dump) and inspect the actual query document sent to the server — this is the single most useful debugging step.
Silent failures from loose typing. MongoDB will happily store "5" and 5 as different values. A filter on an integer field won't match string-typed data. Cast at the model boundary ($casts) and be consistent from day one; retrofitting types across production documents is painful.
Connection config quirks. Auth against MongoDB often fails silently into an empty result set rather than throwing. If reads return nothing, check credentials, authSource (usually admin or the target database), and that the user has readWrite on the specific database — before assuming a package bug.
Missing indexes are invisible until they aren't. Every query pattern you ship should have a supporting index. Use explain() on the raw collection for slow queries; COLLSCAN in the output means you're missing an index on that field.
Extension vs. library confusion. Errors about MongoClient class not found mean the legacy mongo extension is installed instead of mongodb. They are unrelated; this package needs the latter. Rebuild the extension if you upgraded PHP.
Extension points for when you hit a wall. The underlying MongoDB\Collection is usually reachable via a raw() accessor or the connection's getCollection(). When the Eloquent-style layer can't express what you need, drop to the driver directly inside a repository method rather than contorting the model — and keep that escape hatch contained so it's easy to find later.
How can I help you explore Laravel packages today?