@famgia/omnify-laravel 0.0.88 → 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,450 @@
|
|
|
1
|
+
# Form Request Guide
|
|
2
|
+
|
|
3
|
+
> **Related:** [README](./README.md) | [Controller Guide](./controller-guide.md)
|
|
4
|
+
|
|
5
|
+
## Why FormRequest?
|
|
6
|
+
|
|
7
|
+
| Without FormRequest | With FormRequest |
|
|
8
|
+
| ------------------------ | -------------------- |
|
|
9
|
+
| Validation in controller | Separated validation |
|
|
10
|
+
| Fat controllers | Thin controllers |
|
|
11
|
+
| Duplicate validation | Reusable rules |
|
|
12
|
+
| Hard to test | Easy to test |
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Request Templates
|
|
17
|
+
|
|
18
|
+
### Store Request
|
|
19
|
+
|
|
20
|
+
```php
|
|
21
|
+
<?php
|
|
22
|
+
|
|
23
|
+
namespace App\Http\Requests;
|
|
24
|
+
|
|
25
|
+
use Illuminate\Foundation\Http\FormRequest;
|
|
26
|
+
|
|
27
|
+
class UserStoreRequest extends FormRequest
|
|
28
|
+
{
|
|
29
|
+
/**
|
|
30
|
+
* Determine if the user is authorized to make this request.
|
|
31
|
+
*/
|
|
32
|
+
public function authorize(): bool
|
|
33
|
+
{
|
|
34
|
+
return true; // Or: return $this->user()->can('create', User::class);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get the validation rules that apply to the request.
|
|
39
|
+
*
|
|
40
|
+
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
|
41
|
+
*/
|
|
42
|
+
public function rules(): array
|
|
43
|
+
{
|
|
44
|
+
return [
|
|
45
|
+
'name' => ['required', 'string', 'max:255'],
|
|
46
|
+
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
|
47
|
+
'password' => ['required', 'string', 'min:8'],
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get custom attributes for validator errors.
|
|
53
|
+
*/
|
|
54
|
+
public function attributes(): array
|
|
55
|
+
{
|
|
56
|
+
return [
|
|
57
|
+
'name' => '名前',
|
|
58
|
+
'email' => 'メールアドレス',
|
|
59
|
+
'password' => 'パスワード',
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the error messages for the defined validation rules.
|
|
65
|
+
*/
|
|
66
|
+
public function messages(): array
|
|
67
|
+
{
|
|
68
|
+
return [
|
|
69
|
+
'email.unique' => 'このメールアドレスは既に使用されています。',
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Update Request
|
|
76
|
+
|
|
77
|
+
```php
|
|
78
|
+
<?php
|
|
79
|
+
|
|
80
|
+
namespace App\Http\Requests;
|
|
81
|
+
|
|
82
|
+
use Illuminate\Foundation\Http\FormRequest;
|
|
83
|
+
use Illuminate\Validation\Rule;
|
|
84
|
+
|
|
85
|
+
class UserUpdateRequest extends FormRequest
|
|
86
|
+
{
|
|
87
|
+
public function authorize(): bool
|
|
88
|
+
{
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public function rules(): array
|
|
93
|
+
{
|
|
94
|
+
return [
|
|
95
|
+
'name' => ['sometimes', 'string', 'max:255'],
|
|
96
|
+
'email' => [
|
|
97
|
+
'sometimes',
|
|
98
|
+
'string',
|
|
99
|
+
'email',
|
|
100
|
+
'max:255',
|
|
101
|
+
Rule::unique('users')->ignore($this->user), // Ignore current user
|
|
102
|
+
],
|
|
103
|
+
'password' => ['sometimes', 'string', 'min:8'],
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Common Validation Rules
|
|
112
|
+
|
|
113
|
+
### String Fields
|
|
114
|
+
|
|
115
|
+
```php
|
|
116
|
+
'name' => ['required', 'string', 'max:255'],
|
|
117
|
+
'title' => ['required', 'string', 'min:3', 'max:100'],
|
|
118
|
+
'slug' => ['required', 'string', 'alpha_dash', 'max:100'],
|
|
119
|
+
'description' => ['nullable', 'string', 'max:1000'],
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Email
|
|
123
|
+
|
|
124
|
+
```php
|
|
125
|
+
'email' => ['required', 'email', 'max:255', 'unique:users'],
|
|
126
|
+
'email' => ['required', 'email', Rule::unique('users')->ignore($this->user)],
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Password
|
|
130
|
+
|
|
131
|
+
```php
|
|
132
|
+
use Illuminate\Validation\Rules\Password;
|
|
133
|
+
|
|
134
|
+
'password' => ['required', 'confirmed', Password::defaults()],
|
|
135
|
+
'password' => ['required', Password::min(8)->mixedCase()->numbers()->symbols()],
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Numbers
|
|
139
|
+
|
|
140
|
+
```php
|
|
141
|
+
'age' => ['required', 'integer', 'min:0', 'max:150'],
|
|
142
|
+
'price' => ['required', 'numeric', 'min:0', 'max:999999.99'],
|
|
143
|
+
'quantity' => ['required', 'integer', 'min:1'],
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Dates
|
|
147
|
+
|
|
148
|
+
```php
|
|
149
|
+
'birth_date' => ['required', 'date', 'before:today'],
|
|
150
|
+
'scheduled_at' => ['required', 'date', 'after:now'],
|
|
151
|
+
'start_date' => ['required', 'date'],
|
|
152
|
+
'end_date' => ['required', 'date', 'after:start_date'],
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Enums
|
|
156
|
+
|
|
157
|
+
```php
|
|
158
|
+
'status' => ['required', 'in:draft,published,archived'],
|
|
159
|
+
'status' => ['required', Rule::enum(PostStatus::class)],
|
|
160
|
+
'role' => ['required', Rule::in(['admin', 'user', 'guest'])],
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Files
|
|
164
|
+
|
|
165
|
+
```php
|
|
166
|
+
'avatar' => ['nullable', 'image', 'max:2048'], // 2MB max
|
|
167
|
+
'document' => ['required', 'file', 'mimes:pdf,doc,docx', 'max:10240'],
|
|
168
|
+
'images' => ['nullable', 'array', 'max:5'],
|
|
169
|
+
'images.*' => ['image', 'max:2048'],
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Arrays
|
|
173
|
+
|
|
174
|
+
```php
|
|
175
|
+
'tags' => ['nullable', 'array', 'max:10'],
|
|
176
|
+
'tags.*' => ['string', 'max:50'],
|
|
177
|
+
'items' => ['required', 'array', 'min:1'],
|
|
178
|
+
'items.*.product_id' => ['required', 'exists:products,id'],
|
|
179
|
+
'items.*.quantity' => ['required', 'integer', 'min:1'],
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Relationships
|
|
183
|
+
|
|
184
|
+
```php
|
|
185
|
+
'user_id' => ['required', 'exists:users,id'],
|
|
186
|
+
'category_id' => ['required', 'exists:categories,id'],
|
|
187
|
+
'tag_ids' => ['nullable', 'array'],
|
|
188
|
+
'tag_ids.*' => ['exists:tags,id'],
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Conditional Rules
|
|
192
|
+
|
|
193
|
+
```php
|
|
194
|
+
public function rules(): array
|
|
195
|
+
{
|
|
196
|
+
return [
|
|
197
|
+
'type' => ['required', 'in:individual,company'],
|
|
198
|
+
'company_name' => ['required_if:type,company', 'string', 'max:255'],
|
|
199
|
+
'tax_id' => ['required_if:type,company', 'string', 'max:20'],
|
|
200
|
+
];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Or using Rule::when()
|
|
204
|
+
'company_name' => [
|
|
205
|
+
Rule::when($this->type === 'company', ['required', 'string', 'max:255']),
|
|
206
|
+
],
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Authorization
|
|
212
|
+
|
|
213
|
+
### Simple Authorization
|
|
214
|
+
|
|
215
|
+
```php
|
|
216
|
+
public function authorize(): bool
|
|
217
|
+
{
|
|
218
|
+
return $this->user() !== null; // Must be logged in
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Policy-Based Authorization
|
|
223
|
+
|
|
224
|
+
```php
|
|
225
|
+
public function authorize(): bool
|
|
226
|
+
{
|
|
227
|
+
$post = $this->route('post'); // Get route parameter
|
|
228
|
+
return $this->user()->can('update', $post);
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Role-Based Authorization
|
|
233
|
+
|
|
234
|
+
```php
|
|
235
|
+
public function authorize(): bool
|
|
236
|
+
{
|
|
237
|
+
return $this->user()->hasRole('admin');
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Data Preparation
|
|
244
|
+
|
|
245
|
+
### Modify Input Before Validation
|
|
246
|
+
|
|
247
|
+
```php
|
|
248
|
+
protected function prepareForValidation(): void
|
|
249
|
+
{
|
|
250
|
+
$this->merge([
|
|
251
|
+
'slug' => Str::slug($this->title),
|
|
252
|
+
'email' => Str::lower($this->email),
|
|
253
|
+
]);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Modify Validated Data
|
|
258
|
+
|
|
259
|
+
```php
|
|
260
|
+
public function validated($key = null, $default = null): array
|
|
261
|
+
{
|
|
262
|
+
$validated = parent::validated($key, $default);
|
|
263
|
+
|
|
264
|
+
// Hash password if present
|
|
265
|
+
if (isset($validated['password'])) {
|
|
266
|
+
$validated['password'] = bcrypt($validated['password']);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return $validated;
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Add Data After Validation
|
|
274
|
+
|
|
275
|
+
```php
|
|
276
|
+
// In controller
|
|
277
|
+
public function store(PostStoreRequest $request): PostResource
|
|
278
|
+
{
|
|
279
|
+
$data = array_merge($request->validated(), [
|
|
280
|
+
'user_id' => $request->user()->id,
|
|
281
|
+
'published_at' => now(),
|
|
282
|
+
]);
|
|
283
|
+
|
|
284
|
+
$post = Post::create($data);
|
|
285
|
+
return new PostResource($post);
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Custom Validation Rules
|
|
292
|
+
|
|
293
|
+
### Inline Rule
|
|
294
|
+
|
|
295
|
+
```php
|
|
296
|
+
use Illuminate\Validation\Validator;
|
|
297
|
+
|
|
298
|
+
public function withValidator(Validator $validator): void
|
|
299
|
+
{
|
|
300
|
+
$validator->after(function (Validator $validator) {
|
|
301
|
+
if ($this->hasInvalidCoupon()) {
|
|
302
|
+
$validator->errors()->add('coupon', 'Invalid or expired coupon.');
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private function hasInvalidCoupon(): bool
|
|
308
|
+
{
|
|
309
|
+
if (!$this->coupon_code) return false;
|
|
310
|
+
|
|
311
|
+
return !Coupon::where('code', $this->coupon_code)
|
|
312
|
+
->where('expires_at', '>', now())
|
|
313
|
+
->exists();
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Custom Rule Class
|
|
318
|
+
|
|
319
|
+
```php
|
|
320
|
+
// app/Rules/ValidCoupon.php
|
|
321
|
+
<?php
|
|
322
|
+
|
|
323
|
+
namespace App\Rules;
|
|
324
|
+
|
|
325
|
+
use App\Models\Coupon;
|
|
326
|
+
use Closure;
|
|
327
|
+
use Illuminate\Contracts\Validation\ValidationRule;
|
|
328
|
+
|
|
329
|
+
class ValidCoupon implements ValidationRule
|
|
330
|
+
{
|
|
331
|
+
public function validate(string $attribute, mixed $value, Closure $fail): void
|
|
332
|
+
{
|
|
333
|
+
$coupon = Coupon::where('code', $value)
|
|
334
|
+
->where('expires_at', '>', now())
|
|
335
|
+
->first();
|
|
336
|
+
|
|
337
|
+
if (!$coupon) {
|
|
338
|
+
$fail('The coupon is invalid or expired.');
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Usage in FormRequest
|
|
344
|
+
'coupon_code' => ['nullable', 'string', new ValidCoupon],
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Error Response Format
|
|
350
|
+
|
|
351
|
+
Laravel automatically returns validation errors in this format:
|
|
352
|
+
|
|
353
|
+
```json
|
|
354
|
+
{
|
|
355
|
+
"message": "The email field must be a valid email address.",
|
|
356
|
+
"errors": {
|
|
357
|
+
"email": [
|
|
358
|
+
"The email field must be a valid email address."
|
|
359
|
+
],
|
|
360
|
+
"password": [
|
|
361
|
+
"The password field must be at least 8 characters."
|
|
362
|
+
]
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
HTTP Status: `422 Unprocessable Entity`
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Testing Requests
|
|
372
|
+
|
|
373
|
+
### Test Validation Rules
|
|
374
|
+
|
|
375
|
+
```php
|
|
376
|
+
public function test_name_is_required(): void
|
|
377
|
+
{
|
|
378
|
+
$response = $this->postJson('/api/users', [
|
|
379
|
+
'email' => 'test@example.com',
|
|
380
|
+
'password' => 'password123',
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
$response->assertStatus(422)
|
|
384
|
+
->assertJsonValidationErrors(['name']);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
public function test_email_must_be_unique(): void
|
|
388
|
+
{
|
|
389
|
+
User::factory()->create(['email' => 'existing@example.com']);
|
|
390
|
+
|
|
391
|
+
$response = $this->postJson('/api/users', [
|
|
392
|
+
'name' => 'John',
|
|
393
|
+
'email' => 'existing@example.com',
|
|
394
|
+
'password' => 'password123',
|
|
395
|
+
]);
|
|
396
|
+
|
|
397
|
+
$response->assertStatus(422)
|
|
398
|
+
->assertJsonValidationErrors(['email']);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
public function test_valid_data_creates_user(): void
|
|
402
|
+
{
|
|
403
|
+
$response = $this->postJson('/api/users', [
|
|
404
|
+
'name' => 'John Doe',
|
|
405
|
+
'email' => 'john@example.com',
|
|
406
|
+
'password' => 'password123',
|
|
407
|
+
]);
|
|
408
|
+
|
|
409
|
+
$response->assertStatus(201)
|
|
410
|
+
->assertJsonStructure(['data' => ['id', 'name', 'email']]);
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Best Practices
|
|
417
|
+
|
|
418
|
+
### ✅ DO
|
|
419
|
+
|
|
420
|
+
```php
|
|
421
|
+
// Separate Store and Update requests
|
|
422
|
+
class UserStoreRequest extends FormRequest { ... }
|
|
423
|
+
class UserUpdateRequest extends FormRequest { ... }
|
|
424
|
+
|
|
425
|
+
// Use 'sometimes' for optional updates
|
|
426
|
+
'name' => ['sometimes', 'string', 'max:255'],
|
|
427
|
+
|
|
428
|
+
// Use Rule class for complex rules
|
|
429
|
+
Rule::unique('users')->ignore($this->user)
|
|
430
|
+
Rule::enum(PostStatus::class)
|
|
431
|
+
|
|
432
|
+
// Prepare data before validation
|
|
433
|
+
protected function prepareForValidation(): void { ... }
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### ❌ DON'T
|
|
437
|
+
|
|
438
|
+
```php
|
|
439
|
+
// Don't validate in controller
|
|
440
|
+
public function store(Request $request) {
|
|
441
|
+
$request->validate([...]); // Use FormRequest instead
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Don't use same request for store and update
|
|
445
|
+
class UserRequest extends FormRequest { ... } // Split into Store/Update
|
|
446
|
+
|
|
447
|
+
// Don't hardcode messages (use lang files for i18n)
|
|
448
|
+
'name.required' => 'Name is required', // OK for simple apps
|
|
449
|
+
// Use resources/lang/{locale}/validation.php for i18n
|
|
450
|
+
```
|