@famgia/omnify-laravel 0.0.87 → 0.0.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-YVVAJA3T.js → chunk-V7LWJ6OM.js} +178 -12
- package/dist/chunk-V7LWJ6OM.js.map +1 -0
- package/dist/index.cjs +180 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -1
- package/dist/index.d.ts +48 -1
- package/dist/index.js +5 -1
- package/dist/plugin.cjs +176 -11
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.js +1 -1
- package/package.json +5 -5
- package/scripts/postinstall.js +29 -36
- package/stubs/ai-guides/claude-agents/architect.md.stub +150 -0
- package/stubs/ai-guides/claude-agents/developer.md.stub +190 -0
- package/stubs/ai-guides/claude-agents/reviewer.md.stub +134 -0
- package/stubs/ai-guides/claude-agents/tester.md.stub +196 -0
- package/stubs/ai-guides/claude-checklists/backend.md.stub +112 -0
- package/stubs/ai-guides/claude-omnify/antdesign-guide.md.stub +401 -0
- package/stubs/ai-guides/claude-omnify/config-guide.md.stub +253 -0
- package/stubs/ai-guides/claude-omnify/japan-guide.md.stub +186 -0
- package/stubs/ai-guides/claude-omnify/laravel-guide.md.stub +61 -0
- package/stubs/ai-guides/claude-omnify/schema-guide.md.stub +115 -0
- package/stubs/ai-guides/claude-omnify/typescript-guide.md.stub +310 -0
- package/stubs/ai-guides/claude-rules/naming.md.stub +364 -0
- package/stubs/ai-guides/claude-rules/performance.md.stub +251 -0
- package/stubs/ai-guides/claude-rules/security.md.stub +159 -0
- package/stubs/ai-guides/claude-workflows/bug-fix.md.stub +201 -0
- package/stubs/ai-guides/claude-workflows/code-review.md.stub +164 -0
- package/stubs/ai-guides/claude-workflows/new-feature.md.stub +327 -0
- package/stubs/ai-guides/cursor/laravel-controller.mdc.stub +391 -0
- package/stubs/ai-guides/cursor/laravel-request.mdc.stub +112 -0
- package/stubs/ai-guides/cursor/laravel-resource.mdc.stub +73 -0
- package/stubs/ai-guides/cursor/laravel-review.mdc.stub +69 -0
- package/stubs/ai-guides/cursor/laravel-testing.mdc.stub +138 -0
- package/stubs/ai-guides/cursor/laravel.mdc.stub +82 -0
- package/stubs/ai-guides/laravel/README.md.stub +59 -0
- package/stubs/ai-guides/laravel/architecture.md.stub +424 -0
- package/stubs/ai-guides/laravel/controller.md.stub +484 -0
- package/stubs/ai-guides/laravel/datetime.md.stub +334 -0
- package/stubs/ai-guides/laravel/openapi.md.stub +369 -0
- package/stubs/ai-guides/laravel/request.md.stub +450 -0
- package/stubs/ai-guides/laravel/resource.md.stub +516 -0
- package/stubs/ai-guides/laravel/service.md.stub +503 -0
- package/stubs/ai-guides/laravel/testing.md.stub +1504 -0
- package/ai-guides/laravel-guide.md +0 -461
- package/dist/chunk-YVVAJA3T.js.map +0 -1
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Performance & Quality Rules
|
|
2
|
+
|
|
3
|
+
> **Non-negotiable rules** for Laravel performance and code quality.
|
|
4
|
+
|
|
5
|
+
## 🟠 N+1 Query Problem
|
|
6
|
+
|
|
7
|
+
**Always eager load relationships.**
|
|
8
|
+
|
|
9
|
+
```php
|
|
10
|
+
// ❌ N+1 PROBLEM: 1 + N queries (N = number of posts)
|
|
11
|
+
$posts = Post::all();
|
|
12
|
+
foreach ($posts as $post) {
|
|
13
|
+
echo $post->author->name; // Query for each post!
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ✅ CORRECT: Eager loading with with()
|
|
17
|
+
$posts = Post::with('author')->get();
|
|
18
|
+
foreach ($posts as $post) {
|
|
19
|
+
echo $post->author->name; // No extra queries
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ✅ CORRECT: Multiple relationships
|
|
23
|
+
$posts = Post::with(['author', 'comments', 'tags'])->get();
|
|
24
|
+
|
|
25
|
+
// ✅ CORRECT: Nested eager loading
|
|
26
|
+
$posts = Post::with('comments.author')->get();
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Enable N+1 detection in development:**
|
|
30
|
+
|
|
31
|
+
```php
|
|
32
|
+
// In AppServiceProvider boot()
|
|
33
|
+
use Illuminate\Database\Eloquent\Model;
|
|
34
|
+
|
|
35
|
+
public function boot(): void
|
|
36
|
+
{
|
|
37
|
+
// Throw exception on lazy loading (dev only)
|
|
38
|
+
Model::preventLazyLoading(!app()->isProduction());
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 🟠 Use `whenLoaded()` in Resources
|
|
45
|
+
|
|
46
|
+
**Prevent queries in Resources.**
|
|
47
|
+
|
|
48
|
+
```php
|
|
49
|
+
// ❌ ERROR: Triggers query if not eager loaded
|
|
50
|
+
class PostResource extends JsonResource
|
|
51
|
+
{
|
|
52
|
+
public function toArray($request): array
|
|
53
|
+
{
|
|
54
|
+
return [
|
|
55
|
+
'author' => new UserResource($this->author), // N+1!
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ✅ CORRECT: Only include if already loaded
|
|
61
|
+
class PostResource extends JsonResource
|
|
62
|
+
{
|
|
63
|
+
public function toArray($request): array
|
|
64
|
+
{
|
|
65
|
+
return [
|
|
66
|
+
'author' => new UserResource($this->whenLoaded('author')),
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 🟠 Pagination for List Endpoints
|
|
75
|
+
|
|
76
|
+
**Never return all records.**
|
|
77
|
+
|
|
78
|
+
```php
|
|
79
|
+
// ❌ ERROR: Returns all records (memory + performance)
|
|
80
|
+
public function index()
|
|
81
|
+
{
|
|
82
|
+
return UserResource::collection(User::all()); // 1M users = crash
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ✅ CORRECT: Always paginate
|
|
86
|
+
public function index(Request $request)
|
|
87
|
+
{
|
|
88
|
+
$perPage = min($request->input('per_page', 15), 100); // Max 100
|
|
89
|
+
return UserResource::collection(User::paginate($perPage));
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 🟠 Select Only Needed Columns
|
|
96
|
+
|
|
97
|
+
**Don't SELECT * when you need few fields.**
|
|
98
|
+
|
|
99
|
+
```php
|
|
100
|
+
// ❌ INEFFICIENT: Fetches all columns
|
|
101
|
+
$users = User::all();
|
|
102
|
+
$names = $users->pluck('name');
|
|
103
|
+
|
|
104
|
+
// ✅ EFFICIENT: Select only needed columns
|
|
105
|
+
$names = User::pluck('name');
|
|
106
|
+
|
|
107
|
+
// ✅ EFFICIENT: For multiple columns
|
|
108
|
+
$users = User::select(['id', 'name', 'email'])->get();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 🟡 Code Quality Rules
|
|
114
|
+
|
|
115
|
+
### Never Validate in Controller
|
|
116
|
+
|
|
117
|
+
```php
|
|
118
|
+
// ❌ BAD: Validation in controller
|
|
119
|
+
public function store(Request $request)
|
|
120
|
+
{
|
|
121
|
+
$validated = $request->validate([...]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ✅ CORRECT: Use FormRequest
|
|
125
|
+
public function store(UserStoreRequest $request)
|
|
126
|
+
{
|
|
127
|
+
$user = User::create($request->validated());
|
|
128
|
+
return new UserResource($user);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Always Use Resource for Responses
|
|
133
|
+
|
|
134
|
+
```php
|
|
135
|
+
// ❌ BAD: Exposes all fields, inconsistent format
|
|
136
|
+
return $user;
|
|
137
|
+
return response()->json($user);
|
|
138
|
+
|
|
139
|
+
// ✅ CORRECT: Use Resource
|
|
140
|
+
return new UserResource($user);
|
|
141
|
+
return UserResource::collection($users);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Use Route Model Binding
|
|
145
|
+
|
|
146
|
+
```php
|
|
147
|
+
// ❌ BAD: Manual find
|
|
148
|
+
public function show(int $id)
|
|
149
|
+
{
|
|
150
|
+
$user = User::findOrFail($id);
|
|
151
|
+
return new UserResource($user);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ✅ CORRECT: Route model binding
|
|
155
|
+
public function show(User $user)
|
|
156
|
+
{
|
|
157
|
+
return new UserResource($user);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Use Transactions for Multiple Operations
|
|
162
|
+
|
|
163
|
+
```php
|
|
164
|
+
// ❌ BAD: Partial failure possible
|
|
165
|
+
$order = Order::create([...]);
|
|
166
|
+
$order->items()->createMany([...]);
|
|
167
|
+
|
|
168
|
+
// ✅ CORRECT: Transaction ensures atomicity
|
|
169
|
+
DB::transaction(function () use ($data) {
|
|
170
|
+
$order = Order::create([...]);
|
|
171
|
+
$order->items()->createMany([...]);
|
|
172
|
+
return $order;
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Never Use env() Outside Config
|
|
177
|
+
|
|
178
|
+
```php
|
|
179
|
+
// ❌ BAD: env() won't work with config:cache
|
|
180
|
+
$apiKey = env('API_KEY');
|
|
181
|
+
|
|
182
|
+
// ✅ CORRECT: Use config()
|
|
183
|
+
$apiKey = config('services.api_key');
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 🔵 Date/Time Rules
|
|
189
|
+
|
|
190
|
+
### Always Store UTC
|
|
191
|
+
|
|
192
|
+
```php
|
|
193
|
+
// config/app.php
|
|
194
|
+
'timezone' => 'UTC', // NEVER change this
|
|
195
|
+
|
|
196
|
+
// ❌ BAD: Store local time
|
|
197
|
+
$event->scheduled_at = Carbon::now('Asia/Tokyo');
|
|
198
|
+
|
|
199
|
+
// ✅ CORRECT: Store UTC
|
|
200
|
+
$event->scheduled_at = Carbon::now(); // UTC
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Always Return ISO 8601 in API
|
|
204
|
+
|
|
205
|
+
```php
|
|
206
|
+
// ❌ BAD: Local format
|
|
207
|
+
'created_at' => $this->created_at->format('Y-m-d H:i:s'),
|
|
208
|
+
|
|
209
|
+
// ✅ CORRECT: ISO 8601 UTC
|
|
210
|
+
'created_at' => $this->created_at?->toISOString(),
|
|
211
|
+
// Output: "2024-01-15T10:30:00.000000Z"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Quick Reference
|
|
217
|
+
|
|
218
|
+
| Category | ❌ Never Do | ✅ Always Do |
|
|
219
|
+
| --------------- | ----------------------------------- | ------------------------ |
|
|
220
|
+
| **Performance** | `Model::all()` without limit | `Model::paginate()` |
|
|
221
|
+
| | Access relation without `with()` | Eager load with `with()` |
|
|
222
|
+
| | `$this->relation` in Resource | `$this->whenLoaded()` |
|
|
223
|
+
| **Quality** | Validate in Controller | Use FormRequest |
|
|
224
|
+
| | Return Model directly | Return Resource |
|
|
225
|
+
| | `findOrFail($id)` | Route model binding |
|
|
226
|
+
| | Multiple DB ops without transaction | `DB::transaction()` |
|
|
227
|
+
| **Config** | `env()` in code | `config()` |
|
|
228
|
+
| **Dates** | Local timezone | UTC everywhere |
|
|
229
|
+
| | `format('Y-m-d')` | `->toISOString()` |
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Development Safeguards
|
|
234
|
+
|
|
235
|
+
Add to `AppServiceProvider::boot()`:
|
|
236
|
+
|
|
237
|
+
```php
|
|
238
|
+
public function boot(): void
|
|
239
|
+
{
|
|
240
|
+
// Prevent N+1 queries (throws exception on lazy load)
|
|
241
|
+
Model::preventLazyLoading(!app()->isProduction());
|
|
242
|
+
|
|
243
|
+
// Prevent accessing missing attributes
|
|
244
|
+
Model::preventAccessingMissingAttributes(!app()->isProduction());
|
|
245
|
+
|
|
246
|
+
// Prevent silently discarding attributes
|
|
247
|
+
Model::preventSilentlyDiscardingAttributes(!app()->isProduction());
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
These will throw exceptions during development, helping you catch issues early.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Security Rules
|
|
2
|
+
|
|
3
|
+
> **Non-negotiable rules** for Laravel security. Violations = vulnerabilities.
|
|
4
|
+
|
|
5
|
+
## 🔴 Mass Assignment Vulnerability
|
|
6
|
+
|
|
7
|
+
**ALWAYS define `$fillable` in Models.**
|
|
8
|
+
|
|
9
|
+
```php
|
|
10
|
+
// ❌ CRITICAL ERROR: No $fillable = mass assignment vulnerability
|
|
11
|
+
class User extends Model
|
|
12
|
+
{
|
|
13
|
+
// Missing $fillable!
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ❌ DANGEROUS: Using $request->all()
|
|
17
|
+
User::create($request->all()); // Attacker can set is_admin=true
|
|
18
|
+
|
|
19
|
+
// ✅ CORRECT: Define $fillable explicitly
|
|
20
|
+
class User extends Model
|
|
21
|
+
{
|
|
22
|
+
protected $fillable = [
|
|
23
|
+
'name_lastname',
|
|
24
|
+
'name_firstname',
|
|
25
|
+
'email',
|
|
26
|
+
'password',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
// $guarded is alternative but $fillable is preferred (explicit)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ✅ CORRECT: Use validated data only
|
|
33
|
+
User::create($request->validated());
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Rule | Description |
|
|
37
|
+
| --------------------------------------------- | ------------------------------------------------------ |
|
|
38
|
+
| **Always define `$fillable`** | Explicitly list assignable fields |
|
|
39
|
+
| **Never use `$request->all()`** | Use `$request->validated()` or `$request->only([...])` |
|
|
40
|
+
| **Prefer `$fillable` over `$guarded`** | Whitelist is safer than blacklist |
|
|
41
|
+
| **Never put sensitive fields in `$fillable`** | `is_admin`, `role`, `balance` must NOT be fillable |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 🔴 SQL Injection Prevention
|
|
46
|
+
|
|
47
|
+
**NEVER use raw user input in queries.**
|
|
48
|
+
|
|
49
|
+
```php
|
|
50
|
+
// ❌ CRITICAL ERROR: SQL Injection vulnerability
|
|
51
|
+
$email = $request->input('email');
|
|
52
|
+
DB::select("SELECT * FROM users WHERE email = '$email'"); // DANGEROUS!
|
|
53
|
+
|
|
54
|
+
// ❌ DANGEROUS: String interpolation in whereRaw
|
|
55
|
+
User::whereRaw("email = '$email'")->get(); // DANGEROUS!
|
|
56
|
+
|
|
57
|
+
// ✅ CORRECT: Use parameter binding
|
|
58
|
+
DB::select("SELECT * FROM users WHERE email = ?", [$email]);
|
|
59
|
+
|
|
60
|
+
// ✅ CORRECT: Use Query Builder (auto-escapes)
|
|
61
|
+
User::where('email', $email)->get();
|
|
62
|
+
|
|
63
|
+
// ✅ CORRECT: Parameter binding in whereRaw
|
|
64
|
+
User::whereRaw('email = ?', [$email])->get();
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
| Rule | Description |
|
|
68
|
+
| -------------------------------- | ---------------------------------- |
|
|
69
|
+
| **Use Query Builder** | Eloquent auto-escapes values |
|
|
70
|
+
| **Use parameter binding** | `?` placeholders with array values |
|
|
71
|
+
| **Never concatenate user input** | No string interpolation in SQL |
|
|
72
|
+
| **Validate sort fields** | Whitelist allowed sort columns |
|
|
73
|
+
|
|
74
|
+
```php
|
|
75
|
+
// ✅ CORRECT: Whitelist sort fields to prevent SQL injection
|
|
76
|
+
$allowedSorts = ['id', 'name', 'email', 'created_at'];
|
|
77
|
+
$sortBy = in_array($request->sort_by, $allowedSorts)
|
|
78
|
+
? $request->sort_by
|
|
79
|
+
: 'id';
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 🔴 XSS Prevention
|
|
85
|
+
|
|
86
|
+
**Always escape output in Blade templates.**
|
|
87
|
+
|
|
88
|
+
```php
|
|
89
|
+
// ❌ DANGEROUS: Raw HTML output
|
|
90
|
+
{!! $user->bio !!} // XSS if bio contains <script>
|
|
91
|
+
|
|
92
|
+
// ✅ CORRECT: Escaped output (default)
|
|
93
|
+
{{ $user->bio }} // Auto-escapes HTML entities
|
|
94
|
+
|
|
95
|
+
// ✅ CORRECT: Only use {!! !!} for trusted HTML
|
|
96
|
+
{!! $trustedHtml !!} // Only for admin-generated content
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 🔴 CSRF Protection
|
|
102
|
+
|
|
103
|
+
**Never disable CSRF for web routes.**
|
|
104
|
+
|
|
105
|
+
```php
|
|
106
|
+
// ❌ DANGEROUS: Disabling CSRF
|
|
107
|
+
// In VerifyCsrfToken middleware
|
|
108
|
+
protected $except = ['*']; // NEVER do this!
|
|
109
|
+
|
|
110
|
+
// ✅ CORRECT: CSRF is enabled by default for web routes
|
|
111
|
+
// API routes use Sanctum tokens instead
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 🔴 Sensitive Data Exposure
|
|
117
|
+
|
|
118
|
+
**Hide sensitive fields from JSON responses.**
|
|
119
|
+
|
|
120
|
+
```php
|
|
121
|
+
// ❌ ERROR: Password exposed in API response
|
|
122
|
+
return response()->json($user); // Includes password!
|
|
123
|
+
|
|
124
|
+
// ✅ CORRECT: Use $hidden in Model
|
|
125
|
+
class User extends Model
|
|
126
|
+
{
|
|
127
|
+
protected $hidden = [
|
|
128
|
+
'password',
|
|
129
|
+
'remember_token',
|
|
130
|
+
'two_factor_secret',
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ✅ CORRECT: Use Resource to control output
|
|
135
|
+
class UserResource extends JsonResource
|
|
136
|
+
{
|
|
137
|
+
public function toArray($request): array
|
|
138
|
+
{
|
|
139
|
+
return [
|
|
140
|
+
'id' => $this->id,
|
|
141
|
+
'name' => $this->name,
|
|
142
|
+
// password NOT included
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Quick Reference
|
|
151
|
+
|
|
152
|
+
| ❌ Never Do | ✅ Always Do |
|
|
153
|
+
| ----------------------- | --------------------------------- |
|
|
154
|
+
| `$request->all()` | `$request->validated()` |
|
|
155
|
+
| Raw SQL with user input | Query Builder / parameter binding |
|
|
156
|
+
| Missing `$fillable` | Define `$fillable` explicitly |
|
|
157
|
+
| Missing `$hidden` | Hide sensitive fields |
|
|
158
|
+
| Disable CSRF | Keep CSRF enabled |
|
|
159
|
+
| `{!! $userInput !!}` | `{{ $userInput }}` |
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Bug Fix Workflow
|
|
2
|
+
|
|
3
|
+
> Step-by-step guide for fixing bugs.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
flowchart LR
|
|
9
|
+
Reproduce --> Locate --> Fix --> Test --> PR
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
| Step | Action | Output |
|
|
13
|
+
| ---- | --------------------- | ------------------------------ |
|
|
14
|
+
| 1 | Reproduce the bug | Clear reproduction steps |
|
|
15
|
+
| 2 | Locate the cause | File(s) and line(s) identified |
|
|
16
|
+
| 3 | Write failing test | Test that reproduces bug |
|
|
17
|
+
| 4 | Fix the bug | Code changes |
|
|
18
|
+
| 5 | Verify test passes | `./artisan test` |
|
|
19
|
+
| 6 | Check for regressions | All tests pass |
|
|
20
|
+
| 7 | Create PR | Pull request |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Step 1: Reproduce
|
|
25
|
+
|
|
26
|
+
Before fixing, confirm you can reproduce:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Check logs
|
|
30
|
+
tail -f backend/storage/logs/laravel.log
|
|
31
|
+
|
|
32
|
+
# Test the endpoint
|
|
33
|
+
curl -X GET https://api.boilerplate.app/api/users
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Document:**
|
|
37
|
+
- Steps to reproduce
|
|
38
|
+
- Expected behavior
|
|
39
|
+
- Actual behavior
|
|
40
|
+
- Error message/stack trace
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Step 2: Locate the Cause
|
|
45
|
+
|
|
46
|
+
### Common Locations
|
|
47
|
+
|
|
48
|
+
| Symptom | Check |
|
|
49
|
+
| ------------------- | -------------------------------- |
|
|
50
|
+
| 500 error | `storage/logs/laravel.log` |
|
|
51
|
+
| 422 validation | `*Request.php` rules |
|
|
52
|
+
| Wrong data returned | `*Resource.php` |
|
|
53
|
+
| 404 not found | `routes/api.php` + model binding |
|
|
54
|
+
| N+1 queries | Missing `with()` in controller |
|
|
55
|
+
| Date format wrong | Missing `->toISOString()` |
|
|
56
|
+
|
|
57
|
+
### Debug Commands
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Check route exists
|
|
61
|
+
./artisan route:list | grep users
|
|
62
|
+
|
|
63
|
+
# Check model
|
|
64
|
+
./artisan tinker
|
|
65
|
+
>>> User::find(1)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Step 3: Write Failing Test
|
|
71
|
+
|
|
72
|
+
**ALWAYS write a test that reproduces the bug first.**
|
|
73
|
+
|
|
74
|
+
```php
|
|
75
|
+
// tests/Feature/Api/UserControllerTest.php
|
|
76
|
+
|
|
77
|
+
it('異常: bug #123 - returns 500 when name is null', function () {
|
|
78
|
+
// This should NOT happen but currently does
|
|
79
|
+
$response = $this->postJson('/api/users', [
|
|
80
|
+
'email' => 'test@example.com',
|
|
81
|
+
'password' => 'password123',
|
|
82
|
+
// name is missing - should return 422, not 500
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
$response->assertUnprocessable(); // Currently fails with 500
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Run test to confirm it fails:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
./artisan test --filter="bug #123"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Step 4: Fix the Bug
|
|
98
|
+
|
|
99
|
+
Make the minimal change to fix the issue.
|
|
100
|
+
|
|
101
|
+
**Don't:**
|
|
102
|
+
- Refactor unrelated code
|
|
103
|
+
- Add features
|
|
104
|
+
- Change coding style
|
|
105
|
+
|
|
106
|
+
**Do:**
|
|
107
|
+
- Fix only the bug
|
|
108
|
+
- Keep changes focused
|
|
109
|
+
- Follow existing patterns
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Step 5: Verify Fix
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Run the specific test
|
|
117
|
+
./artisan test --filter="bug #123"
|
|
118
|
+
|
|
119
|
+
# Run all tests for the affected controller
|
|
120
|
+
./artisan test --filter=UserControllerTest
|
|
121
|
+
|
|
122
|
+
# Run all tests
|
|
123
|
+
./artisan test
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Step 6: Check for Regressions
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# All backend tests
|
|
132
|
+
./artisan test
|
|
133
|
+
|
|
134
|
+
# Specific test file
|
|
135
|
+
./artisan test tests/Feature/Api/UserControllerTest.php
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Step 7: Create PR
|
|
141
|
+
|
|
142
|
+
### PR Title Format
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
fix: [#issue] brief description
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Example: `fix: [#123] return 422 instead of 500 when name is missing`
|
|
149
|
+
|
|
150
|
+
### PR Description
|
|
151
|
+
|
|
152
|
+
```markdown
|
|
153
|
+
## Bug
|
|
154
|
+
|
|
155
|
+
[Link to issue or description]
|
|
156
|
+
|
|
157
|
+
## Root Cause
|
|
158
|
+
|
|
159
|
+
[Explanation of what caused the bug]
|
|
160
|
+
|
|
161
|
+
## Fix
|
|
162
|
+
|
|
163
|
+
[Description of the fix]
|
|
164
|
+
|
|
165
|
+
## Test
|
|
166
|
+
|
|
167
|
+
- [ ] Added test that reproduces bug
|
|
168
|
+
- [ ] Test passes after fix
|
|
169
|
+
- [ ] All existing tests pass
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Debugging Tips
|
|
175
|
+
|
|
176
|
+
### Laravel Logs
|
|
177
|
+
|
|
178
|
+
```php
|
|
179
|
+
// Add temporary logging
|
|
180
|
+
Log::info('Debug', ['user' => $user, 'request' => $request->all()]);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Database Queries
|
|
184
|
+
|
|
185
|
+
```php
|
|
186
|
+
// Enable query log
|
|
187
|
+
DB::enableQueryLog();
|
|
188
|
+
|
|
189
|
+
// ... your code ...
|
|
190
|
+
|
|
191
|
+
// Dump queries
|
|
192
|
+
dd(DB::getQueryLog());
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Tinker
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
./artisan tinker
|
|
199
|
+
>>> User::where('email', 'test@example.com')->first()
|
|
200
|
+
>>> app(UserService::class)->someMethod()
|
|
201
|
+
```
|