|
| 1 | +--- |
| 2 | +name: laravel-mongodb |
| 3 | +description: Implementation specialist for the mongodb/laravel-mongodb package. Triggers on "Laravel MongoDB", "mongodb/laravel-mongodb", "Eloquent MongoDB", "MongoDB model", "_id", "ObjectId in Laravel", "MongoDB queue/cache/session driver", "Atlas Search Laravel", "Laravel Scout MongoDB", "embedsMany", "embedsOne", "withCount MongoDB", "distinct MongoDB", "distinct array MongoDB", "get unique values MongoDB", "Laravel aggregation pipeline", "cross-database relationship MongoDB". Corrects LLM mistakes when MySQL/Eloquent patterns are applied to MongoDB. |
| 4 | +license: Apache-2.0 |
| 5 | +metadata: |
| 6 | + version: "1.0.0" |
| 7 | +--- |
| 8 | + |
| 9 | +# Laravel MongoDB |
| 10 | + |
| 11 | +Implementation skill for `mongodb/laravel-mongodb`. Exists to prevent the common mistakes: auto-increment IDs, `withCount()`, `toSql()`, SQL `JOIN`, `distinct()->get()` expecting scalar arrays, and `belongsTo()` over native ObjectId FKs. |
| 12 | + |
| 13 | +## Core Workflow |
| 14 | + |
| 15 | +1. Identify layer: model, query builder, relationship, schema/index, queue/cache/session, search, or transaction. |
| 16 | +2. Confirm model extends `MongoDB\Laravel\Eloquent\Model` (or uses `DocumentModel` trait). |
| 17 | +3. Map every FK and `_id`: ObjectId in DB, **string** in Eloquent — cast accordingly. |
| 18 | +4. Replace unsupported helpers (`withCount`, `toSql`, `groupByRaw`, `whereFulltext`, `union`, `inRandomOrder`, `whereColumn`) with MongoDB alternatives. |
| 19 | +5. Validate: `php artisan migrate`, Pest tests, `phpcs`/`phpstan`. |
| 20 | + |
| 21 | +## Related MongoDB tooling |
| 22 | + |
| 23 | +If the user needs MongoDB-related skills that aren't otherwise available through e.g. Boost or the skills directory, such as: |
| 24 | + |
| 25 | +- Inspecting Atlas system resources |
| 26 | +- Managing an Atlas cluster |
| 27 | +- Analysing MongoDB schema anti-patterns |
| 28 | +- Migrating an SQL schema to MongoDB |
| 29 | +- Setting up the official MongoDB MCP server |
| 30 | + |
| 31 | +Suggest installing the MongoDB agent skills plugin if not already installed. Instructions for installing it are in the [MongoDB agent skills plugin README](https://github.com/mongodb/agent-skills/blob/main/README.md). |
| 32 | + |
| 33 | +## Reference Guide |
| 34 | + |
| 35 | +| Topic | Reference file | Load When | |
| 36 | +|---|---|---| |
| 37 | +| Models, casts, `_id` mapping | `references/eloquent-models.md` | Defining or modifying a model | |
| 38 | +| Query builder gotchas, aggregation | `references/query-builder.md` | Writing queries, `withCount`, `distinct`, grouping | |
| 39 | +| Embedded, hybrid, cross-database relations | `references/relationships.md` | `belongsTo`, `hasMany`, `embedsMany`, `hasManyIn` | |
| 40 | +| Connection setup | `references/connection.md` | `config/database.php`, multiple connections | |
| 41 | +| Indexes, migrations | `references/schema.md` | Creating indexes, migrations, collections | |
| 42 | +| Queue driver | `references/queues.md` | Dispatching jobs, queue config | |
| 43 | +| Transactions | `references/transactions.md` | Multi-document atomic writes | |
| 44 | +| Cache & sessions | `references/cache-sessions.md` | Configuring cache / session stores | |
| 45 | +| Atlas Search / Scout | `references/search-engine.md` | Full-text search, Scout integration | |
| 46 | +| Vector search, auto-embedding | `references/vector-search.md` | Semantic search, embedding pipelines, hybrid search | |
| 47 | +| Installation | `references/installation.md` | Setting up ext-mongodb and the package | |
| 48 | +| Support & issue reporting | `references/support.md` | Reporting bugs, finding the right repo | |
| 49 | + |
| 50 | +## Constraints |
| 51 | + |
| 52 | +### MUST DO |
| 53 | + |
| 54 | +- Extend `MongoDB\Laravel\Eloquent\Model` (or apply `DocumentModel` trait to base classes you cannot change). |
| 55 | +- Cast `_id` to string in every API resource: `'id' => (string) $this->_id`. |
| 56 | +- Cast FK fields to `string` via `$casts` on the child model when FK values may come from outside model attributes (imports, raw ObjectIds) — prevents BSON type mismatches on direct `where('author_id', $id)` queries. |
| 57 | +- Eager-load with `::with()` — MongoDB does no server-side joins for Eloquent relations. |
| 58 | +- Use aggregation pipeline for grouping, counting per group, `$lookup`, and `$sample`. |
| 59 | +- Create indexes in migrations: `Schema::connection('mongodb')->create('posts', fn (Blueprint $c) => $c->index('user_id'))`. |
| 60 | +- Use `DB::connection('mongodb')->transaction(...)` only on replica set / sharded cluster. |
| 61 | + |
| 62 | +### MUST NOT DO |
| 63 | + |
| 64 | +- `withCount()` / `withAvg()` / `withSum()` — silently wrong or throws. Use `$lookup` + `$size`/`$avg`/`$sum` aggregation. |
| 65 | +- `toSql()` / `toRawSql()` — no SQL. Use `->dump()` / `->dd()`. |
| 66 | +- `distinct('field')->get()` expecting scalars — returns a Collection. Use `->distinct()->pluck('field')`. |
| 67 | +- `groupByRaw()`, `orderByRaw()`, `havingRaw()`, `whereFulltext()`, `union()`, `whereColumn()` — use aggregation. |
| 68 | +- `inRandomOrder()` — use `Model::raw(fn($c) => $c->aggregate([['$sample' => ['size' => N]]]))`. |
| 69 | +- Auto-increment IDs — primary keys are ObjectIds. |
| 70 | +- `protected $collection` — removed. Use `protected $table` instead. |
| 71 | +- `$keyType = 'string'` on a SQL model in a cross-database relationship — only needed on MongoDB models. The `HybridRelations` trait handles the comparison on the SQL side. |
| 72 | +- Unencrypted PII — use Laravel encrypted casts or Queryable Encryption. |
| 73 | + |
| 74 | +## Code Templates |
| 75 | + |
| 76 | +### 1. Eloquent model |
| 77 | + |
| 78 | +```php |
| 79 | +<?php |
| 80 | + |
| 81 | +namespace App\Models; |
| 82 | + |
| 83 | +use MongoDB\Laravel\Eloquent\Model; |
| 84 | + |
| 85 | +final class Post extends Model |
| 86 | +{ |
| 87 | + protected $connection = 'mongodb'; |
| 88 | + protected $table = 'posts'; // $table not $collection |
| 89 | + |
| 90 | + protected $fillable = ['title', 'body', 'author_id', 'published_at']; |
| 91 | + |
| 92 | + protected $casts = [ |
| 93 | + 'author_id' => 'string', // FK as string for Eloquent relationship matching |
| 94 | + 'published_at' => 'datetime', |
| 95 | + ]; |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +### 2. Relationship with ObjectId/string casting |
| 100 | + |
| 101 | +```php |
| 102 | +<?php |
| 103 | + |
| 104 | +namespace App\Models; |
| 105 | + |
| 106 | +use MongoDB\Laravel\Eloquent\Model; |
| 107 | +use MongoDB\Laravel\Relations\BelongsTo; |
| 108 | +use MongoDB\Laravel\Relations\EmbedsMany; |
| 109 | + |
| 110 | +final class Post extends Model |
| 111 | +{ |
| 112 | + protected $casts = ['author_id' => 'string']; // cast FK to string for relation matching |
| 113 | + |
| 114 | + public function author(): BelongsTo |
| 115 | + { |
| 116 | + return $this->belongsTo(User::class, 'author_id'); |
| 117 | + } |
| 118 | + |
| 119 | + public function comments(): EmbedsMany |
| 120 | + { |
| 121 | + return $this->embedsMany(Comment::class); |
| 122 | + } |
| 123 | +} |
| 124 | + |
| 125 | +final class User extends Model |
| 126 | +{ |
| 127 | + protected $keyType = 'string'; // expose primary key as string so Post.author_id matches |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +### 3. Aggregation replacing `withCount` |
| 132 | + |
| 133 | +```php |
| 134 | +<?php |
| 135 | + |
| 136 | +use App\Models\Post; |
| 137 | + |
| 138 | +// WRONG: Post::withCount('comments')->get(); |
| 139 | +$posts = Post::raw(fn ($collection) => $collection->aggregate([ |
| 140 | + ['$lookup' => [ |
| 141 | + 'from' => 'comments', |
| 142 | + 'localField' => '_id', |
| 143 | + 'foreignField' => 'post_id', |
| 144 | + 'as' => 'comments', |
| 145 | + ]], |
| 146 | + ['$addFields' => ['comments_count' => ['$size' => '$comments']]], |
| 147 | + ['$project' => ['comments' => 0]], |
| 148 | +])); |
| 149 | +``` |
| 150 | + |
| 151 | +### 4. Queue job |
| 152 | + |
| 153 | +```php |
| 154 | +<?php |
| 155 | + |
| 156 | +namespace App\Jobs; |
| 157 | + |
| 158 | +use Illuminate\Bus\Queueable; |
| 159 | +use Illuminate\Contracts\Queue\ShouldQueue; |
| 160 | +use Illuminate\Foundation\Bus\Dispatchable; |
| 161 | +use Illuminate\Queue\InteractsWithQueue; |
| 162 | +use Illuminate\Queue\SerializesModels; |
| 163 | + |
| 164 | +final class IndexPostJob implements ShouldQueue |
| 165 | +{ |
| 166 | + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; |
| 167 | + |
| 168 | + public function __construct(public string $postId) {} |
| 169 | + |
| 170 | + public function handle(): void {} |
| 171 | +} |
| 172 | + |
| 173 | +IndexPostJob::dispatch((string) $post->_id)->onConnection('mongodb'); |
| 174 | +``` |
| 175 | + |
| 176 | +### 5. Feature test (Pest) |
| 177 | + |
| 178 | +```php |
| 179 | +<?php |
| 180 | + |
| 181 | +use App\Models\Post; |
| 182 | + |
| 183 | +it('creates a post with an ObjectId primary key', function (): void { |
| 184 | + $post = Post::create(['title' => 'Hello Mongo', 'body' => 'first', 'tags' => ['mongo', 'laravel']]); |
| 185 | + |
| 186 | + expect($post->id)->toBeString() |
| 187 | + ->and(Post::query()->where('_id', $post->id)->exists())->toBeTrue(); |
| 188 | +}); |
| 189 | +``` |
| 190 | + |
| 191 | +## Validation Checkpoints |
| 192 | + |
| 193 | +| Stage | Command | Expected Result | |
| 194 | +|---|---|---| |
| 195 | +| Style | `vendor/bin/phpcbf && vendor/bin/phpcs` | No violations | |
| 196 | +| Static analysis | `vendor/bin/phpstan analyse` | Level 8 clean | |
| 197 | +| Indexes / migration | `php artisan migrate --database=mongodb` | Migrations run; indexes created | |
| 198 | +| Tests | `vendor/bin/pest` | All green | |
| 199 | +| Query inspection | `Model::query()->where(...)->dump()` | Prints MongoDB filter array (no SQL) | |
0 commit comments