simexis/mongodb
MongoDB integration for Laravel applications, offering Eloquent-style models and query builder support so you can work with MongoDB collections using familiar Laravel conventions.
composer require simexis/mongodb
Expect a hard dependency on an older Laravel/illuminate/* line plus ext-mongodb. Check composer.json in the repo before running this — with 2 stars and 0 dependents, the version matrix is the first thing that will bite you.Simexis\Mongodb\MongodbServiceProvider::class), and add the Moloquent alias in config/app.php if you want the short class name in models.config/database.php, not a separate config file. Moloquent-style packages reuse Laravel's DB config:
'mongodb' => [
'driver' => 'mongodb',
'host' => env('DB_MONGO_HOST', '127.0.0.1'),
'port' => env('DB_MONGO_PORT', 27017),
'database' => env('DB_MONGO_DATABASE'),
'username' => env('DB_MONGO_USERNAME'),
'password' => env('DB_MONGO_PASSWORD'),
'options' => ['database' => env('DB_MONGO_AUTH_DB')], // authSource
],
_id hydration works, and dates come back as Carbon instances before you build anything on top.
class Event extends Moloquent {
protected $connection = 'mongodb';
protected $collection = 'events';
}
$event = Event::where('type', 'login')->first();
jenssegers/laravel-mongodb docs (this fork's API is a snapshot of it) and the fork's src/ for any divergences. Diff the two before trusting a method signature.Model layer
Moloquent (or Simexis\Mongodb\Eloquent\Model) rather than Illuminate\Database\Eloquent\Model. Set $collection explicitly — pluralization guesses wrong on irregular names and on collections created by other services._id is the primary key by default; keep $primaryKey = '_id' and $incrementing = false. Route-model binding works, but the string-to-ObjectId conversion is the package's job — don't pass raw strings into find() if you can pass the model.$casts/$dates deliberately. Mongo stores dates as BSON UTCDateTime; anything you write as a string stays a string forever and will silently break range queries later.public function address() { return $this->embedsOne(Address::class); }
public function lines() { return $this->embedsMany(Line::class); }
Use embedsMany for genuinely owned, bounded arrays. Anything unbounded (event logs, message threads) belongs in its own collection with a hasMany and an index on the FK.Query patterns
where('address.city', 'Berlin'), where('tags', 'php') (array membership), where('meta.views', '>', 100).Model::where('_id', $id)->increment('stats.views');
Model::where('_id', $id)->push('tags', 'laravel');
Model::where('_id', $id)->pull('tags', 'draft');
->select('name', 'status') or ->project([...]) in list endpoints; pulling full documents into Eloquent hydration is where Mongo apps get slow.$lookup, $facet, $group:
DB::connection('mongodb')->collection('events')->raw(function ($c) {
return $c->aggregate([['$group' => ['_id' => '$type', 'n' => ['$sum' => 1]]]]);
});
->chunk() / ->cursor() rather than get() on large collections.Integration with a relational app
protected $connection = 'mongodb' per model and keep the two worlds joined in PHP, not in a query.belongsTo/hasMany pointing at MySQL from a Mongo model) technically work but do an extra query per hydration. Eager-load them like any N+1.php artisan mongo:indexes command or a seeder-style script running ensureIndex/createIndex idempotently, invoked from your deploy pipeline.composer.json ("simexis/mongodb": "dev-master#<sha>"), vendor it if CI needs determinism, and treat an eventual move to mongodb/laravel-mongodb as planned work rather than a surprise. If it's greenfield, start on the official package instead.ext-mongo (the removed legacy extension) rather than ext-mongodb + mongodb/mongodb. Verify with php -m | grep mongo and check the fork's composer.json require block before debugging anything else. A silent Class 'MongoClient' not found is an extension problem, not a package problem."42" and another writing 42 to the same field. Adopt a convention (casts on the model, or mutators) and enforce it in code review; you cannot fix it with a migration later without a backfill script._id and JSON. ObjectId serializes awkwardly to JSON and to array casts. If the API contract wants a plain string, add getRouteKeyName/toArray handling once, centrally, instead of at each endpoint.$fillable is enforced identically to Eloquent, which surprises people coming from the raw driver. Conversely, update() on a nested array replaces the whole field — use $unset/$pull/dot-path updates for surgical edits.count() and pagination. paginate() runs count() on the full filter; on large collections add a covering index or switch to range-based pagination (where('_id', '>', $last) + limit). Deep skip() is O(n).deleted_at to every query via the global scope. On collections written by other services, that scope silently hides documents — check whether SoftDeletes is actually appropriate before adding the trait.username/password without options.database (authSource) produces confusing auth failures on MongoDB 3.x+. Set it explicitly to admin or the target DB.toSql(). Log via the driver's command monitoring, or DB::connection('mongodb')->enableQueryLog() if the fork exposes it — verify that first, it's a common fork regression.tearDown. There's no RefreshDatabase equivalent — build a small trait that truncates the collections your suite touches. Never let a test suite hit a shared dev Mongo; there's no transaction rollback across collections here.newQuery()/newMoloquentBuilder() and the connection's getQueryBuilder() are the seams for adding custom operators or scopes. Prefer local scopes (scopeActive) over patching the builder — the fork will not take your PR.How can I help you explore Laravel packages today?