@famgia/omnify-laravel 0.0.88 → 0.0.90
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-2QSKZS63.js} +188 -12
- package/dist/chunk-2QSKZS63.js.map +1 -0
- package/dist/index.cjs +190 -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 +186 -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/README.md.stub +95 -0
- 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/react-form-guide.md.stub +259 -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/cursor/omnify.mdc.stub +58 -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,364 @@
|
|
|
1
|
+
# Naming Conventions Guide
|
|
2
|
+
|
|
3
|
+
> **Related:** [README](./README.md) | [Testing Guide](./testing-guide.md)
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Consistent naming is critical for maintainability. This guide defines naming patterns for all backend code.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## PHP Imports
|
|
12
|
+
|
|
13
|
+
**Always use `use` statements. Never use FQCN (Fully Qualified Class Name) inline.**
|
|
14
|
+
|
|
15
|
+
```php
|
|
16
|
+
// ❌ WRONG: Inline FQCN
|
|
17
|
+
public function store(): \Illuminate\Http\JsonResponse
|
|
18
|
+
{
|
|
19
|
+
return new \App\Http\Resources\UserResource($user);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ✅ CORRECT: Import at top, use short names
|
|
23
|
+
use Illuminate\Http\JsonResponse;
|
|
24
|
+
use App\Http\Resources\UserResource;
|
|
25
|
+
|
|
26
|
+
public function store(): JsonResponse
|
|
27
|
+
{
|
|
28
|
+
return new UserResource($user);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
| Rule | Description |
|
|
33
|
+
| ------------------ | -------------------------------- |
|
|
34
|
+
| Import all classes | Use `use` at top of file |
|
|
35
|
+
| Short class names | Never `\Full\Path\Class` inline |
|
|
36
|
+
| Group imports | Framework, then App, then Others |
|
|
37
|
+
| No unused imports | Remove unused `use` statements |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## File & Class Naming
|
|
42
|
+
|
|
43
|
+
### Pattern: `{Model}{Type}.php`
|
|
44
|
+
|
|
45
|
+
| Type | Pattern | Example |
|
|
46
|
+
| ---------- | ------------------------ | ------------------------- |
|
|
47
|
+
| Controller | `{Model}Controller` | `UserController.php` |
|
|
48
|
+
| Request | `{Model}{Action}Request` | `UserStoreRequest.php` |
|
|
49
|
+
| Resource | `{Model}Resource` | `UserResource.php` |
|
|
50
|
+
| Model | `{Model}` (singular) | `User.php` |
|
|
51
|
+
| Service | `{Model}Service` | `OrderService.php` |
|
|
52
|
+
| Action | `{Verb}{Noun}Action` | `CreateInvoiceAction.php` |
|
|
53
|
+
| Job | `{Verb}{Noun}Job` | `SendWelcomeEmailJob.php` |
|
|
54
|
+
| Event | `{Model}{PastTense}` | `UserRegistered.php` |
|
|
55
|
+
| Observer | `{Model}Observer` | `UserObserver.php` |
|
|
56
|
+
| Policy | `{Model}Policy` | `UserPolicy.php` |
|
|
57
|
+
| Test | `{Model}ControllerTest` | `UserControllerTest.php` |
|
|
58
|
+
|
|
59
|
+
### Request Naming
|
|
60
|
+
|
|
61
|
+
| Action | Pattern | Example |
|
|
62
|
+
| ------ | ---------------------- | ----------------------- |
|
|
63
|
+
| Create | `{Model}StoreRequest` | `UserStoreRequest.php` |
|
|
64
|
+
| Update | `{Model}UpdateRequest` | `UserUpdateRequest.php` |
|
|
65
|
+
|
|
66
|
+
> **Note:** Use `Store` (not `Create`) and `Update` (not `Edit`) to match Laravel CRUD conventions.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Method Naming
|
|
71
|
+
|
|
72
|
+
### Controller Methods (RESTful)
|
|
73
|
+
|
|
74
|
+
| HTTP Method | Controller Method | Route | Description |
|
|
75
|
+
| ----------- | ----------------- | ----------------- | --------------- |
|
|
76
|
+
| GET | `index()` | `/api/users` | List all |
|
|
77
|
+
| POST | `store()` | `/api/users` | Create new |
|
|
78
|
+
| GET | `show()` | `/api/users/{id}` | Get single |
|
|
79
|
+
| PUT/PATCH | `update()` | `/api/users/{id}` | Update existing |
|
|
80
|
+
| DELETE | `destroy()` | `/api/users/{id}` | Delete |
|
|
81
|
+
|
|
82
|
+
### Service Methods
|
|
83
|
+
|
|
84
|
+
| Pattern | Example |
|
|
85
|
+
| --------------------- | ------------------------- |
|
|
86
|
+
| `{verb}{Noun}` | `processPayment()` |
|
|
87
|
+
| `{verb}{Noun}{State}` | `calculateTotalWithTax()` |
|
|
88
|
+
|
|
89
|
+
```php
|
|
90
|
+
// ✅ Good
|
|
91
|
+
public function processPayment(Order $order): Payment
|
|
92
|
+
public function calculateDiscount(Cart $cart): float
|
|
93
|
+
public function sendNotification(User $user): void
|
|
94
|
+
|
|
95
|
+
// ❌ Bad
|
|
96
|
+
public function payment(Order $order) // Missing verb
|
|
97
|
+
public function doStuff() // Too vague
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Model Methods
|
|
101
|
+
|
|
102
|
+
| Type | Pattern | Example |
|
|
103
|
+
| -------- | --------------------- | ----------------------- |
|
|
104
|
+
| Scope | `scope{Name}` | `scopeActive($query)` |
|
|
105
|
+
| Accessor | `{attribute}` (PHP 8) | `fullName(): Attribute` |
|
|
106
|
+
| Mutator | `{attribute}` (PHP 8) | `password(): Attribute` |
|
|
107
|
+
| Relation | `{relation}` (noun) | `posts()`, `author()` |
|
|
108
|
+
|
|
109
|
+
```php
|
|
110
|
+
// Scope - filter queries
|
|
111
|
+
public function scopeActive(Builder $query): Builder
|
|
112
|
+
{
|
|
113
|
+
return $query->where('status', 'active');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Accessor (PHP 8+)
|
|
117
|
+
protected function fullName(): Attribute
|
|
118
|
+
{
|
|
119
|
+
return Attribute::get(fn () => "{$this->first_name} {$this->last_name}");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Relationship
|
|
123
|
+
public function posts(): HasMany
|
|
124
|
+
{
|
|
125
|
+
return $this->hasMany(Post::class);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Test Naming (PEST)
|
|
132
|
+
|
|
133
|
+
Use `describe()` to group tests by endpoint, `it()` for individual test cases.
|
|
134
|
+
|
|
135
|
+
### Naming Rules
|
|
136
|
+
|
|
137
|
+
| Category | Prefix | Pattern | Example |
|
|
138
|
+
| --------------------- | ------- | ---------------------------------------- | ----------------------------------------------------- |
|
|
139
|
+
| **正常系 (Normal)** | `正常:` | `it('正常: {verb} {what}')` | `it('正常: creates user with valid data')` |
|
|
140
|
+
| **異常系 (Abnormal)** | `異常:` | `it('異常: fails to {action} {reason}')` | `it('異常: fails to create user with invalid email')` |
|
|
141
|
+
| **異常系 (404/401)** | `異常:` | `it('異常: returns {status} {reason}')` | `it('異常: returns 404 when user not found')` |
|
|
142
|
+
|
|
143
|
+
### 正常系 (Normal Cases) - Success Behavior
|
|
144
|
+
|
|
145
|
+
**Pattern:** `it('正常: {verb} {what}')`
|
|
146
|
+
|
|
147
|
+
**Common verbs:** `returns`, `creates`, `updates`, `deletes`, `filters`, `sorts`, `paginates`
|
|
148
|
+
|
|
149
|
+
```php
|
|
150
|
+
// GET (index)
|
|
151
|
+
it('正常: returns paginated users')
|
|
152
|
+
it('正常: returns users filtered by search')
|
|
153
|
+
it('正常: returns users sorted by created_at')
|
|
154
|
+
|
|
155
|
+
// POST (store)
|
|
156
|
+
it('正常: creates user with valid data')
|
|
157
|
+
|
|
158
|
+
// GET (show)
|
|
159
|
+
it('正常: returns user by id')
|
|
160
|
+
|
|
161
|
+
// PUT (update)
|
|
162
|
+
it('正常: updates user with valid data')
|
|
163
|
+
it('正常: updates user with partial data')
|
|
164
|
+
|
|
165
|
+
// DELETE (destroy)
|
|
166
|
+
it('正常: deletes user')
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 異常系 (Abnormal Cases) - Failure Behavior
|
|
170
|
+
|
|
171
|
+
**Pattern:** `it('異常: fails to {action} {reason}')` or `it('異常: returns {status} {reason}')`
|
|
172
|
+
|
|
173
|
+
```php
|
|
174
|
+
// Validation errors (422) - use "fails to"
|
|
175
|
+
it('異常: fails to create user with missing email')
|
|
176
|
+
it('異常: fails to create user with invalid email format')
|
|
177
|
+
it('異常: fails to create user with duplicate email')
|
|
178
|
+
it('異常: fails to create user with short password')
|
|
179
|
+
it('異常: fails to create user with invalid kana format') // Japanese
|
|
180
|
+
it('異常: fails to update user with invalid data')
|
|
181
|
+
|
|
182
|
+
// Not found (404) - use "returns 404"
|
|
183
|
+
it('異常: returns 404 when user not found')
|
|
184
|
+
it('異常: returns 404 when updating nonexistent user')
|
|
185
|
+
it('異常: returns 404 when deleting nonexistent user')
|
|
186
|
+
|
|
187
|
+
// Authentication (401) - use "returns 401"
|
|
188
|
+
it('異常: returns 401 when not authenticated')
|
|
189
|
+
|
|
190
|
+
// Authorization (403) - use "returns 403"
|
|
191
|
+
it('異常: returns 403 when user cannot access resource')
|
|
192
|
+
it('異常: returns 403 when deleting without admin role')
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Complete Example
|
|
196
|
+
|
|
197
|
+
```php
|
|
198
|
+
describe('POST /api/users', function () {
|
|
199
|
+
// ================================================================
|
|
200
|
+
// 正常系 (Normal Cases)
|
|
201
|
+
// ================================================================
|
|
202
|
+
it('正常: creates user with valid data', function () { ... });
|
|
203
|
+
|
|
204
|
+
// ================================================================
|
|
205
|
+
// 異常系 (Abnormal Cases)
|
|
206
|
+
// ================================================================
|
|
207
|
+
|
|
208
|
+
// Required fields
|
|
209
|
+
it('異常: fails to create user with missing email', function () { ... });
|
|
210
|
+
it('異常: fails to create user with missing password', function () { ... });
|
|
211
|
+
|
|
212
|
+
// Format validation
|
|
213
|
+
it('異常: fails to create user with invalid email format', function () { ... });
|
|
214
|
+
it('異常: fails to create user with short password', function () { ... });
|
|
215
|
+
|
|
216
|
+
// Unique constraint
|
|
217
|
+
it('異常: fails to create user with duplicate email', function () { ... });
|
|
218
|
+
|
|
219
|
+
// Japanese field validation
|
|
220
|
+
it('異常: fails to create user with invalid kana format', function () { ... });
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('GET /api/users/{id}', function () {
|
|
224
|
+
// 正常系
|
|
225
|
+
it('正常: returns user by id', function () { ... });
|
|
226
|
+
|
|
227
|
+
// 異常系
|
|
228
|
+
it('異常: returns 404 when user not found', function () { ... });
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Database Naming
|
|
235
|
+
|
|
236
|
+
### Tables
|
|
237
|
+
|
|
238
|
+
| Pattern | Example |
|
|
239
|
+
| ------------------------ | ----------- |
|
|
240
|
+
| Plural, snake_case | `users` |
|
|
241
|
+
| Pivot: singular_singular | `post_tag` |
|
|
242
|
+
| (alphabetical order) | `role_user` |
|
|
243
|
+
|
|
244
|
+
### Columns
|
|
245
|
+
|
|
246
|
+
| Type | Pattern | Example |
|
|
247
|
+
| ------------- | ------------------ | -------------------- |
|
|
248
|
+
| Primary key | `id` | `id` |
|
|
249
|
+
| Foreign key | `{table}_id` | `user_id` |
|
|
250
|
+
| Boolean | `is_{state}` | `is_active` |
|
|
251
|
+
| Timestamp | `{action}_at` | `verified_at` |
|
|
252
|
+
| Japanese name | `name_{part}` | `name_lastname` |
|
|
253
|
+
| Japanese kana | `name_kana_{part}` | `name_kana_lastname` |
|
|
254
|
+
|
|
255
|
+
### Japanese Name Fields (JapaneseName type)
|
|
256
|
+
|
|
257
|
+
| Field | Description | Max Length |
|
|
258
|
+
| --------------------- | ---------------- | ---------- |
|
|
259
|
+
| `name_lastname` | Family name (姓) | 50 |
|
|
260
|
+
| `name_firstname` | Given name (名) | 50 |
|
|
261
|
+
| `name_kana_lastname` | Family name kana | 100 |
|
|
262
|
+
| `name_kana_firstname` | Given name kana | 100 |
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Route Naming
|
|
267
|
+
|
|
268
|
+
### API Routes
|
|
269
|
+
|
|
270
|
+
| Pattern | Example |
|
|
271
|
+
| ------------------ | -------------------------- |
|
|
272
|
+
| Plural resource | `/api/users` |
|
|
273
|
+
| Nested resource | `/api/users/{user}/posts` |
|
|
274
|
+
| Action on resource | `/api/orders/{order}/ship` |
|
|
275
|
+
|
|
276
|
+
```php
|
|
277
|
+
// routes/api.php
|
|
278
|
+
Route::apiResource('users', UserController::class);
|
|
279
|
+
Route::apiResource('posts', PostController::class);
|
|
280
|
+
|
|
281
|
+
// Nested
|
|
282
|
+
Route::apiResource('users.posts', UserPostController::class);
|
|
283
|
+
|
|
284
|
+
// Custom actions
|
|
285
|
+
Route::post('/orders/{order}/ship', [OrderController::class, 'ship']);
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Variable Naming
|
|
291
|
+
|
|
292
|
+
### PHP Variables
|
|
293
|
+
|
|
294
|
+
| Type | Pattern | Example |
|
|
295
|
+
| -------------- | ------------ | ------------------ |
|
|
296
|
+
| Model instance | `$camelCase` | `$user`, `$post` |
|
|
297
|
+
| Collection | `$plural` | `$users`, `$posts` |
|
|
298
|
+
| Boolean | `$is{State}` | `$isActive` |
|
|
299
|
+
| Query builder | `$query` | `$query` |
|
|
300
|
+
|
|
301
|
+
```php
|
|
302
|
+
// ✅ Good
|
|
303
|
+
$user = User::find($id);
|
|
304
|
+
$users = User::all();
|
|
305
|
+
$isActive = $user->status === 'active';
|
|
306
|
+
|
|
307
|
+
// ❌ Bad
|
|
308
|
+
$u = User::find($id); // Too short
|
|
309
|
+
$userData = User::find($id); // Redundant "Data"
|
|
310
|
+
$active = true; // Missing "is" prefix for boolean
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## OpenAPI/Swagger Naming
|
|
316
|
+
|
|
317
|
+
### Tag Names
|
|
318
|
+
|
|
319
|
+
| Pattern | Example |
|
|
320
|
+
| ------------------ | ------- |
|
|
321
|
+
| Plural, PascalCase | `Users` |
|
|
322
|
+
|
|
323
|
+
### Parameter Names
|
|
324
|
+
|
|
325
|
+
| Type | Pattern | Example |
|
|
326
|
+
| ----- | ------------- | ------------- |
|
|
327
|
+
| Query | `Query{Name}` | `QuerySearch` |
|
|
328
|
+
| Path | `Path{Name}` | `PathId` |
|
|
329
|
+
|
|
330
|
+
### Response Names
|
|
331
|
+
|
|
332
|
+
| Pattern | Example |
|
|
333
|
+
| --------------- | ----------------- |
|
|
334
|
+
| `{Description}` | `Success` |
|
|
335
|
+
| `{HttpStatus}` | `NotFound` |
|
|
336
|
+
| `{Error}Error` | `ValidationError` |
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Summary Table
|
|
341
|
+
|
|
342
|
+
| Type | Convention | Example |
|
|
343
|
+
| -------------------- | ---------------------------------------- | ----------------------------------------------------- |
|
|
344
|
+
| **Imports** | | |
|
|
345
|
+
| PHP imports | `use` at top, short name inline | `use JsonResponse;` → `: JsonResponse` |
|
|
346
|
+
| **Files** | | |
|
|
347
|
+
| Controller | `{Model}Controller` | `UserController.php` |
|
|
348
|
+
| Request | `{Model}{Action}Request` | `UserStoreRequest.php` |
|
|
349
|
+
| Resource | `{Model}Resource` | `UserResource.php` |
|
|
350
|
+
| Test | `{Model}ControllerTest` | `UserControllerTest.php` |
|
|
351
|
+
| **Methods** | | |
|
|
352
|
+
| Controller | RESTful verbs | `index`, `store`, `show` |
|
|
353
|
+
| Service | `{verb}{Noun}` | `processPayment()` |
|
|
354
|
+
| Scope | `scope{Name}` | `scopeActive()` |
|
|
355
|
+
| **Test Naming** | | |
|
|
356
|
+
| 正常系 (Normal) | `it('正常: {verb} {what}')` | `it('正常: creates user with valid data')` |
|
|
357
|
+
| 異常系 (Abnormal) | `it('異常: fails to {action} {reason}')` | `it('異常: fails to create user with invalid email')` |
|
|
358
|
+
| 異常系 (404/401/403) | `it('異常: returns {status} {reason}')` | `it('異常: returns 404 when user not found')` |
|
|
359
|
+
| **Database** | | |
|
|
360
|
+
| Table | plural, snake_case | `users`, `order_items` |
|
|
361
|
+
| Column | snake_case | `user_id`, `created_at` |
|
|
362
|
+
| Boolean column | `is_{state}` | `is_verified` |
|
|
363
|
+
| **Routes** | | |
|
|
364
|
+
| API | plural, kebab-case | `/api/users`, `/api/order-items` |
|
|
@@ -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.
|