wangzd/mongodb
A MongoDB integration package for Laravel and PHP, offering database connectivity, query building, and model support so applications can use MongoDB alongside familiar Laravel conventions.
This package is a fork of jenssegers/laravel-mongodb (the "Moloquent" lineage): it swaps Eloquent's SQL grammar for a MongoDB query/collection builder, so you get models, casts, relations, and Schema helpers backed by mongodb/mongodb.
Before you write a line of code, do three checks in this order — forks frequently diverge here:
composer.json and note the require block. Which PHP driver does it expect — the modern ext-mongodb (with mongodb/mongodb) or the legacy ext-mongo? If it pins the legacy one, it will not run on PHP 7+, and you should stop here.src/ and read the namespace on the service provider and Model class. Forks of this repo usually keep Jenssegers\Mongodb\..., but verify — you need the exact FQCN for the provider and for every use statement.illuminate/* version constraints. A zero-star fork is almost certainly pinned to an older Laravel major.Then install and wire it up:
composer require wangzd/mongodb
Register the provider (if the package predates auto-discovery) in config/app.php:
'providers' => [
Jenssegers\Mongodb\MongodbServiceProvider::class, // confirm against src/
],
Add a connection in config/database.php:
'mongodb' => [
'driver' => 'mongodb',
'host' => env('MONGO_HOST', '127.0.0.1'),
'port' => env('MONGO_PORT', 27017),
'database' => env('MONGO_DATABASE'),
'username' => env('MONGO_USERNAME'),
'password' => env('MONGO_PASSWORD'),
'options' => [
'database' => env('MONGO_AUTH_DB', 'admin'), // authSource
],
],
First use case — a flexible-schema collection that would be painful as SQL columns (catalog items, CMS blocks, event payloads):
use Jenssegers\Mongodb\Eloquent\Model; // verify namespace
class Product extends Model
{
protected $connection = 'mongodb';
protected $collection = 'products';
protected $fillable = ['sku', 'name', 'price', 'attributes'];
protected $dates = ['released_at'];
}
Product::create(['sku' => 'A-1', 'name' => 'Widget', 'attributes' => ['color' => 'red']]);
Product::where('attributes.color', 'red')->where('price', '<', 50)->get();
Where to look first: src/Eloquent/Model.php, src/Query/Builder.php, src/Collection.php, and src/Schema/Builder.php. Those four files tell you exactly which operators and schema helpers this fork actually supports — do not trust the parent project's README for the details.
One model per collection, connection declared explicitly. Never rely on the default connection being Mongo; set protected $connection = 'mongodb' on every model so relational models continue to work untouched.
Embed what you read together, reference what you query independently. This is the single most important modeling decision and it drives everything else:
class Post extends Model
{
public function comments() { return $this->embedsMany(Comment::class); } // read with the post
public function author() { return $this->belongsTo(User::class); } // queried on its own
}
Embdeds are stored inline, so Post::where('comments.author', 'taylor')->get() uses dot-notation and needs no join. Eager loading works: Post::with('comments', 'author')->paginate(20).
Use the query builder escape hatch for aggregation. Eloquent covers CRUD; reporting almost always needs the driver directly:
$totals = Order::raw(function ($collection) {
return $collection->aggregate([
['$match' => ['status' => 'paid']],
['$group' => ['_id' => '$customer_id', 'total' => ['$sum' => '$amount']]],
['$sort' => ['total' => -1]],
['$limit' => 10],
]);
});
Reach for raw() rather than fighting groupBy/having translations — the grammar only covers a subset of SQL semantics.
Atomic field updates for counters and arrays. These bypass model hydration and are far cheaper than read-modify-save:
Product::where('sku', 'A-1')->increment('views');
Product::where('_id', $id)->push('tags', 'featured');
Product::where('_id', $id)->pull('tags', 'archived');
Product::where('_id', $id)->unset('legacy_field');
Manage indexes in migrations, not by hand. Treat them as schema:
Schema::connection('mongodb')->create('products', function ($collection) {
$collection->index('sku', null, null, ['unique' => true]);
$collection->index(['name' => 'text', 'description' => 'text']);
$collection->index('attributes.color');
});
Ship this in a migration so every environment gets the same indexes; unindexed dot-notation queries are the usual cause of "Mongo is slow" complaints.
Hybrid persistence is the normal shape of a real app. Keep users/orders/payments in MySQL for referential integrity, and put the malleable stuff in Mongo. Cross-connection hasMany will not join — resolve it in PHP (User::find($id)->posts where posts is a Mongo model referencing user_id) or denormalize the fields you need onto the document.
Thin repository layer for aggregation-heavy reads. Wrap raw()/aggregate() calls in a repository so tests can stub them and so you have one place to add explain() and index hints.
Testing: use a dedicated database (app_test) and drop collections in setUp(). RefreshDatabase and DatabaseTransactions do not apply — there are no transactions to roll back.
_id is an ObjectId, not a string. Model::find($id) casts correctly; where('_id', $id) with a raw string may silently return nothing depending on the fork's version. When in doubt: ->where('_id', new \MongoDB\BSON\ObjectId($id)).$incrementing = false and $keyType = 'string' on your base model. Auto-increment assumptions leak in through save() and relation resolution otherwise.$model->attributes['color'] in place often won't mark the field dirty. Reassign the whole array or use ->push()/->pull() for atomic, guaranteed writes.Product::where(...)->update([...]) won't touch updated_at. Add '$currentDate' => ['updated_at' => true] or go through the model.unique validation rule has no database backstop unless you created a unique index. Register a custom validator that queries the collection, and create the index._id references. Handle cleanup explicitly (an observer or a queued prune job).paginate() runs a count() over the whole filter. Fine at 50k documents, painful at 5M. For big collections use range-based pagination on an indexed field (_id > $lastId) instead of skip/limit.where('$text' => ['$search' => ...]), or denormalize a lowercase field and match exactly.authSource. Set options.database => 'admin' (or the user's database) when credentials live outside the app database.DB::connection('mongodb')->enableQueryLog() then getQueryLog(); ->explain() on any builder for index usage; ->toMongo() on a query to see the raw filter before it hits the server.php artisan config:cache) after changing connection options — Laravel will happily keep using the stale cached array and confuse you for an hour.composer.json, keep it behind a composer.json repositories vcs entry so you can patch it in place, and write your Mongo usage so it's portable: models, $fillable, and raw() calls are the same API in mongodb/laravel-mongodb. Budget a migration to the official driver before your next Laravel major upgrade; the fork will break on it.ext-mongodb (new) and ext-mongo (legacy, dead since PHP 7) are not interchangeable. If composer install or the provider's boot() blows up, check php -m | grep mongo first.How can I help you explore Laravel packages today?