Skip to content

Commit 78e9319

Browse files
committed
Create agent skills
1 parent 3bd38e6 commit 78e9319

15 files changed

Lines changed: 1555 additions & 0 deletions

File tree

skills/laravel-mongodb/SKILL.md

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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+
## Reference Guide
22+
23+
| Topic | Reference file | Load When |
24+
|---|---|---|
25+
| Models, casts, `_id` mapping | `references/eloquent-models.md` | Defining or modifying a model |
26+
| Query builder gotchas, aggregation | `references/query-builder.md` | Writing queries, `withCount`, `distinct`, grouping |
27+
| Embedded, hybrid, cross-database relations | `references/relationships.md` | `belongsTo`, `hasMany`, `embedsMany`, `hasManyIn` |
28+
| Connection setup | `references/connection.md` | `config/database.php`, multiple connections |
29+
| Indexes, migrations | `references/schema.md` | Creating indexes, migrations, collections |
30+
| Queue driver | `references/queues.md` | Dispatching jobs, queue config |
31+
| Transactions | `references/transactions.md` | Multi-document atomic writes |
32+
| Cache & sessions | `references/cache-sessions.md` | Configuring cache / session stores |
33+
| Atlas Search / Scout | `references/search-engine.md` | Full-text search, Scout integration |
34+
| Vector search, auto-embedding | `references/vector-search.md` | Semantic search, embedding pipelines, hybrid search |
35+
| Installation | `references/installation.md` | Setting up ext-mongodb and the package |
36+
| Support & issue reporting | `references/support.md` | Reporting bugs, finding the right repo |
37+
38+
## Constraints
39+
40+
### MUST DO
41+
42+
- Extend `MongoDB\Laravel\Eloquent\Model` (or apply `DocumentModel` trait to base classes you cannot change).
43+
- Cast `_id` to string in every API resource: `'id' => (string) $this->_id`.
44+
- 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.
45+
- Eager-load with `::with()` — MongoDB does no server-side joins for Eloquent relations.
46+
- Use aggregation pipeline for grouping, counting per group, `$lookup`, and `$sample`.
47+
- Create indexes in migrations: `Schema::connection('mongodb')->create('posts', fn (Blueprint $c) => $c->index('user_id'))`.
48+
- Use `DB::connection('mongodb')->transaction(...)` only on replica set / sharded cluster.
49+
50+
### MUST NOT DO
51+
52+
- `withCount()` / `withAvg()` / `withSum()` — silently wrong or throws. Use `$lookup` + `$size`/`$avg`/`$sum` aggregation.
53+
- `toSql()` / `toRawSql()` — no SQL. Use `->dump()` / `->dd()`.
54+
- `distinct('field')->get()` expecting scalars — returns a Collection. Use `->distinct()->pluck('field')`.
55+
- `groupByRaw()`, `orderByRaw()`, `havingRaw()`, `whereFulltext()`, `union()`, `whereColumn()` — use aggregation.
56+
- `inRandomOrder()` — use `Model::raw(fn($c) => $c->aggregate([['$sample' => ['size' => N]]]))`.
57+
- Auto-increment IDs — primary keys are ObjectIds.
58+
- `protected $collection` — removed. Use `protected $table` instead.
59+
- `$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.
60+
- Unencrypted PII — use Laravel encrypted casts or Queryable Encryption.
61+
62+
## Code Templates
63+
64+
### 1. Eloquent model
65+
66+
```php
67+
<?php
68+
69+
namespace App\Models;
70+
71+
use MongoDB\Laravel\Eloquent\Model;
72+
73+
final class Post extends Model
74+
{
75+
protected $connection = 'mongodb';
76+
protected $table = 'posts'; // $table not $collection
77+
78+
protected $fillable = ['title', 'body', 'author_id', 'published_at'];
79+
80+
protected $casts = [
81+
'author_id' => 'string', // FK as string for Eloquent relationship matching
82+
'published_at' => 'datetime',
83+
];
84+
}
85+
```
86+
87+
### 2. Relationship with ObjectId/string casting
88+
89+
```php
90+
<?php
91+
92+
namespace App\Models;
93+
94+
use MongoDB\Laravel\Eloquent\Model;
95+
use MongoDB\Laravel\Relations\BelongsTo;
96+
use MongoDB\Laravel\Relations\EmbedsMany;
97+
98+
final class Post extends Model
99+
{
100+
protected $casts = ['author_id' => 'string']; // cast FK to string for relation matching
101+
102+
public function author(): BelongsTo
103+
{
104+
return $this->belongsTo(User::class, 'author_id');
105+
}
106+
107+
public function comments(): EmbedsMany
108+
{
109+
return $this->embedsMany(Comment::class);
110+
}
111+
}
112+
113+
final class User extends Model
114+
{
115+
protected $keyType = 'string'; // expose primary key as string so Post.author_id matches
116+
}
117+
```
118+
119+
### 3. Aggregation replacing `withCount`
120+
121+
```php
122+
<?php
123+
124+
use App\Models\Post;
125+
126+
// WRONG: Post::withCount('comments')->get();
127+
$posts = Post::raw(fn ($collection) => $collection->aggregate([
128+
['$lookup' => [
129+
'from' => 'comments',
130+
'localField' => '_id',
131+
'foreignField' => 'post_id',
132+
'as' => 'comments',
133+
]],
134+
['$addFields' => ['comments_count' => ['$size' => '$comments']]],
135+
['$project' => ['comments' => 0]],
136+
]));
137+
```
138+
139+
### 4. Queue job
140+
141+
```php
142+
<?php
143+
144+
namespace App\Jobs;
145+
146+
use Illuminate\Bus\Queueable;
147+
use Illuminate\Contracts\Queue\ShouldQueue;
148+
use Illuminate\Foundation\Bus\Dispatchable;
149+
use Illuminate\Queue\InteractsWithQueue;
150+
use Illuminate\Queue\SerializesModels;
151+
152+
final class IndexPostJob implements ShouldQueue
153+
{
154+
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
155+
156+
public function __construct(public string $postId) {}
157+
158+
public function handle(): void {}
159+
}
160+
161+
IndexPostJob::dispatch((string) $post->_id)->onConnection('mongodb');
162+
```
163+
164+
### 5. Feature test (Pest)
165+
166+
```php
167+
<?php
168+
169+
use App\Models\Post;
170+
171+
it('creates a post with an ObjectId primary key', function (): void {
172+
$post = Post::create(['title' => 'Hello Mongo', 'body' => 'first', 'tags' => ['mongo', 'laravel']]);
173+
174+
expect($post->id)->toBeString()
175+
->and(Post::query()->where('_id', $post->id)->exists())->toBeTrue();
176+
});
177+
```
178+
179+
## Validation Checkpoints
180+
181+
| Stage | Command | Expected Result |
182+
|---|---|---|
183+
| Style | `vendor/bin/phpcbf && vendor/bin/phpcs` | No violations |
184+
| Static analysis | `vendor/bin/phpstan analyse` | Level 8 clean |
185+
| Indexes / migration | `php artisan migrate --database=mongodb` | Migrations run; indexes created |
186+
| Tests | `vendor/bin/pest` | All green |
187+
| Query inspection | `Model::query()->where(...)->dump()` | Prints MongoDB filter array (no SQL) |
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Cache and Sessions
2+
3+
## Cache
4+
5+
```php
6+
// config/cache.php
7+
return [
8+
'default' => env('CACHE_STORE', 'mongodb'),
9+
10+
'stores' => [
11+
'mongodb' => [
12+
'driver' => 'mongodb',
13+
'connection' => 'mongodb',
14+
'collection' => 'cache',
15+
'lock_connection' => 'mongodb',
16+
'lock_collection' => 'cache_locks',
17+
],
18+
],
19+
];
20+
```
21+
22+
TTL index — expiry timestamp field is `expires_at` (not `expiration`). Cache keys stored in `_id`, no extra unique index needed.
23+
24+
```php
25+
Schema::connection('mongodb')->create('cache', function (Blueprint $c): void {
26+
$c->expire('expires_at', 0);
27+
});
28+
29+
Schema::connection('mongodb')->create('cache_locks', function (Blueprint $c): void {
30+
$c->expire('expires_at', 0);
31+
});
32+
```
33+
34+
## Sessions
35+
36+
```php
37+
// config/session.php
38+
return [
39+
'driver' => env('SESSION_DRIVER', 'mongodb'),
40+
'connection' => 'mongodb',
41+
'table' => 'sessions', // collection name; keep key 'table' for Laravel compatibility
42+
'lifetime' => 120,
43+
];
44+
```
45+
46+
Session IDs stored in `_id` — do not add `$c->unique('id')`.
47+
48+
```php
49+
Schema::connection('mongodb')->create('sessions', function (Blueprint $c): void {
50+
$c->index('user_id');
51+
$c->index('last_activity');
52+
$c->expire('expires_at', 0);
53+
});
54+
```
55+
56+
## Usage
57+
58+
```php
59+
use Illuminate\Support\Facades\Cache;
60+
61+
Cache::put('user:1', $user, now()->addMinutes(10));
62+
Cache::remember('movies:top10', 300, fn () => Movie::orderBy('rating', 'desc')->take(10)->get());
63+
```
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Connection
2+
3+
## `config/database.php`
4+
5+
```php
6+
<?php
7+
8+
return [
9+
'default' => env('DB_CONNECTION', 'mongodb'),
10+
11+
'connections' => [
12+
'mongodb' => [
13+
'driver' => 'mongodb',
14+
'dsn' => env('MONGODB_URI', 'mongodb://localhost:27017'),
15+
'database' => env('MONGODB_DATABASE', 'laravel'),
16+
'options' => [
17+
'appName' => env('APP_NAME', 'laravel'),
18+
],
19+
],
20+
21+
'mysql' => [
22+
'driver' => 'mysql',
23+
// ...
24+
],
25+
],
26+
];
27+
```
28+
29+
## `.env`
30+
31+
```
32+
DB_CONNECTION=mongodb
33+
MONGODB_URI="mongodb+srv://user:pass@cluster0.mongodb.net/?retryWrites=true&w=majority"
34+
MONGODB_DATABASE=laravel
35+
```
36+
37+
Pool sizing, timeouts, and TLS all go into the URI (use `mongodb+srv://` for Atlas).
38+
39+
## Service-provider registration
40+
41+
Auto-registered via package discovery. If opted out, add to `bootstrap/providers.php`:
42+
43+
```php
44+
return [
45+
MongoDB\Laravel\MongoDBServiceProvider::class,
46+
];
47+
```
48+
49+
## Using the connection from code
50+
51+
```php
52+
use Illuminate\Support\Facades\DB;
53+
54+
DB::connection('mongodb')->table('logs')->insert(['msg' => 'hi']);
55+
56+
$db = DB::connection('mongodb')->getDatabase(); // MongoDB\Database
57+
$client = DB::connection('mongodb')->getClient(); // MongoDB\Client
58+
// deprecated: getMongoDB() → getDatabase(), getMongoClient() → getClient()
59+
60+
$collection = DB::connection('mongodb')->getCollection('logs');
61+
// note: ->collection() does not exist; use ->table() for the query builder
62+
```
63+
64+
## Connection pooling and timeouts
65+
66+
```
67+
mongodb+srv://.../db?maxPoolSize=50&minPoolSize=5&serverSelectionTimeoutMS=5000&socketTimeoutMS=30000
68+
```

0 commit comments

Comments
 (0)