@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,310 @@
|
|
|
1
|
+
# Omnify TypeScript Generator Guide
|
|
2
|
+
|
|
3
|
+
This guide covers TypeScript-specific features and generated code patterns for Omnify.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Create new project (recommended)
|
|
9
|
+
npx @famgia/omnify create-laravel-project my-app
|
|
10
|
+
cd my-app
|
|
11
|
+
|
|
12
|
+
# Or initialize in existing project
|
|
13
|
+
npx @famgia/omnify init
|
|
14
|
+
|
|
15
|
+
# Generate TypeScript types
|
|
16
|
+
npx @famgia/omnify generate
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Generated Files
|
|
20
|
+
|
|
21
|
+
When you run `npx @famgia/omnify generate`, the following TypeScript files are generated:
|
|
22
|
+
|
|
23
|
+
- `base/*.ts` - Base model interfaces
|
|
24
|
+
- `enum/*.ts` - Enum types with multi-locale labels
|
|
25
|
+
- `rules/*.ts` - Ant Design compatible validation rules
|
|
26
|
+
|
|
27
|
+
## Type Generation
|
|
28
|
+
|
|
29
|
+
### Object Schema → Interface
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
# yaml-language-server: $schema=./node_modules/.omnify/combined-schema.json
|
|
33
|
+
name: User
|
|
34
|
+
properties:
|
|
35
|
+
id:
|
|
36
|
+
type: BigInt
|
|
37
|
+
required: true
|
|
38
|
+
name:
|
|
39
|
+
type: String
|
|
40
|
+
required: true
|
|
41
|
+
maxLength: 255
|
|
42
|
+
email:
|
|
43
|
+
type: String
|
|
44
|
+
required: true
|
|
45
|
+
unique: true
|
|
46
|
+
profile:
|
|
47
|
+
type: Json
|
|
48
|
+
createdAt:
|
|
49
|
+
type: DateTime
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Generated:
|
|
53
|
+
```typescript
|
|
54
|
+
export interface User {
|
|
55
|
+
id: number;
|
|
56
|
+
name: string;
|
|
57
|
+
email: string;
|
|
58
|
+
profile: Record<string, unknown> | null;
|
|
59
|
+
createdAt: Date | null;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Type Mapping
|
|
64
|
+
|
|
65
|
+
| Schema Type | TypeScript Type |
|
|
66
|
+
|-------------|-----------------|
|
|
67
|
+
| `String` | `string` |
|
|
68
|
+
| `Text` | `string` |
|
|
69
|
+
| `MediumText` | `string` |
|
|
70
|
+
| `LongText` | `string` |
|
|
71
|
+
| `TinyInt` | `number` |
|
|
72
|
+
| `Int` | `number` |
|
|
73
|
+
| `BigInt` | `number` |
|
|
74
|
+
| `Float` | `number` |
|
|
75
|
+
| `Decimal` | `number` |
|
|
76
|
+
| `Boolean` | `boolean` |
|
|
77
|
+
| `Date` | `Date` |
|
|
78
|
+
| `DateTime` | `Date` |
|
|
79
|
+
| `Timestamp` | `Date` |
|
|
80
|
+
| `Json` | `Record<string, unknown>` |
|
|
81
|
+
| `EnumRef` | Generated enum type |
|
|
82
|
+
| `Association` | Related model type / array |
|
|
83
|
+
|
|
84
|
+
## Hidden Schemas
|
|
85
|
+
|
|
86
|
+
Schemas with `options.hidden: true` are skipped for TypeScript generation:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
name: AppCache
|
|
90
|
+
options:
|
|
91
|
+
hidden: true # No TypeScript interface generated
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Use cases:
|
|
95
|
+
- System tables (cache, sessions, jobs)
|
|
96
|
+
- Tables that don't need frontend types
|
|
97
|
+
|
|
98
|
+
## Enum Generation (Multi-locale)
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
# schemas/PostStatus.yaml
|
|
102
|
+
name: PostStatus
|
|
103
|
+
kind: enum
|
|
104
|
+
displayName:
|
|
105
|
+
ja: 投稿ステータス
|
|
106
|
+
en: Post Status
|
|
107
|
+
values:
|
|
108
|
+
draft:
|
|
109
|
+
ja: 下書き
|
|
110
|
+
en: Draft
|
|
111
|
+
published:
|
|
112
|
+
ja: 公開済み
|
|
113
|
+
en: Published
|
|
114
|
+
archived:
|
|
115
|
+
ja: アーカイブ
|
|
116
|
+
en: Archived
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Generated:
|
|
120
|
+
```typescript
|
|
121
|
+
export const PostStatus = {
|
|
122
|
+
draft: 'draft',
|
|
123
|
+
published: 'published',
|
|
124
|
+
archived: 'archived',
|
|
125
|
+
} as const;
|
|
126
|
+
|
|
127
|
+
export type PostStatus = typeof PostStatus[keyof typeof PostStatus];
|
|
128
|
+
|
|
129
|
+
// Multi-locale labels
|
|
130
|
+
export const PostStatusLabels: Record<PostStatus, Record<string, string>> = {
|
|
131
|
+
draft: { ja: '下書き', en: 'Draft' },
|
|
132
|
+
published: { ja: '公開済み', en: 'Published' },
|
|
133
|
+
archived: { ja: 'アーカイブ', en: 'Archived' },
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Get label for specific locale
|
|
137
|
+
export function getPostStatusLabel(value: PostStatus, locale: string = 'en'): string {
|
|
138
|
+
return PostStatusLabels[value]?.[locale] ?? PostStatusLabels[value]?.['en'] ?? value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Helper functions
|
|
142
|
+
export const PostStatusValues = Object.values(PostStatus);
|
|
143
|
+
export function isPostStatus(value: unknown): value is PostStatus {
|
|
144
|
+
return PostStatusValues.includes(value as PostStatus);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Validation Rules (Ant Design)
|
|
149
|
+
|
|
150
|
+
Omnify generates Ant Design compatible validation rules in `rules/` directory.
|
|
151
|
+
|
|
152
|
+
**See detailed guide:** `.claude/omnify/antdesign-guide.md`
|
|
153
|
+
|
|
154
|
+
Quick example:
|
|
155
|
+
```tsx
|
|
156
|
+
import { Form, Input } from 'antd';
|
|
157
|
+
import { getUserRules, getUserPropertyDisplayName } from './types/model/rules/User.rules';
|
|
158
|
+
|
|
159
|
+
function UserForm({ locale = 'ja' }) {
|
|
160
|
+
const rules = getUserRules(locale);
|
|
161
|
+
return (
|
|
162
|
+
<Form>
|
|
163
|
+
<Form.Item name="name" label={getUserPropertyDisplayName('name', locale)} rules={rules.name}>
|
|
164
|
+
<Input />
|
|
165
|
+
</Form.Item>
|
|
166
|
+
</Form>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Association Types
|
|
172
|
+
|
|
173
|
+
### ManyToOne
|
|
174
|
+
```yaml
|
|
175
|
+
author:
|
|
176
|
+
type: Association
|
|
177
|
+
relation: ManyToOne
|
|
178
|
+
target: User
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Generated:
|
|
182
|
+
```typescript
|
|
183
|
+
export interface Post {
|
|
184
|
+
authorId: number;
|
|
185
|
+
author?: User; // Optional: loaded relation
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### OneToMany
|
|
190
|
+
```yaml
|
|
191
|
+
posts:
|
|
192
|
+
type: Association
|
|
193
|
+
relation: OneToMany
|
|
194
|
+
target: Post
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Generated:
|
|
198
|
+
```typescript
|
|
199
|
+
export interface User {
|
|
200
|
+
posts?: Post[]; // Optional: loaded relation array
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### ManyToMany
|
|
205
|
+
```yaml
|
|
206
|
+
tags:
|
|
207
|
+
type: Association
|
|
208
|
+
relation: ManyToMany
|
|
209
|
+
target: Tag
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Generated:
|
|
213
|
+
```typescript
|
|
214
|
+
export interface Post {
|
|
215
|
+
tags?: Tag[]; // Optional: loaded relation array
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Nullable Fields
|
|
220
|
+
|
|
221
|
+
Fields without `required: true` are nullable:
|
|
222
|
+
|
|
223
|
+
```yaml
|
|
224
|
+
description:
|
|
225
|
+
type: LongText # No required: true
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Generated:
|
|
229
|
+
```typescript
|
|
230
|
+
description: string | null;
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Using Generated Types
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { User, Post, PostStatus, isPostStatus } from './types/omnify-types';
|
|
237
|
+
|
|
238
|
+
// Type-safe object creation
|
|
239
|
+
const user: User = {
|
|
240
|
+
id: 1,
|
|
241
|
+
name: 'John',
|
|
242
|
+
email: 'john@example.com',
|
|
243
|
+
profile: null,
|
|
244
|
+
createdAt: new Date(),
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Enum usage
|
|
248
|
+
const status: PostStatus = PostStatus.draft;
|
|
249
|
+
|
|
250
|
+
// Type guard
|
|
251
|
+
function handleStatus(value: unknown) {
|
|
252
|
+
if (isPostStatus(value)) {
|
|
253
|
+
console.log('Valid status:', value);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Commands
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# Create new project
|
|
262
|
+
npx @famgia/omnify create-laravel-project my-app
|
|
263
|
+
|
|
264
|
+
# Generate TypeScript types
|
|
265
|
+
npx @famgia/omnify generate
|
|
266
|
+
|
|
267
|
+
# Force regenerate all files
|
|
268
|
+
npx @famgia/omnify generate --force
|
|
269
|
+
|
|
270
|
+
# Only generate TypeScript types
|
|
271
|
+
npx @famgia/omnify generate --types-only
|
|
272
|
+
|
|
273
|
+
# Validate schemas
|
|
274
|
+
npx @famgia/omnify validate
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Configuration
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// omnify.config.ts
|
|
281
|
+
import { defineConfig } from '@famgia/omnify';
|
|
282
|
+
import typescript from '@famgia/omnify-typescript/plugin';
|
|
283
|
+
|
|
284
|
+
export default defineConfig({
|
|
285
|
+
schemasDir: './schemas',
|
|
286
|
+
lockFilePath: './.omnify.lock',
|
|
287
|
+
|
|
288
|
+
plugins: [
|
|
289
|
+
typescript({
|
|
290
|
+
// Output path for TypeScript files
|
|
291
|
+
path: './resources/ts/types/models',
|
|
292
|
+
|
|
293
|
+
// Generate Ant Design validation rules
|
|
294
|
+
generateRules: true,
|
|
295
|
+
}),
|
|
296
|
+
],
|
|
297
|
+
|
|
298
|
+
locale: {
|
|
299
|
+
locales: ['ja', 'en'],
|
|
300
|
+
defaultLocale: 'ja',
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Configuration Options
|
|
306
|
+
|
|
307
|
+
| Option | Type | Default | Description |
|
|
308
|
+
|--------|------|---------|-------------|
|
|
309
|
+
| `path` | `string` | `./src/types/model` | TypeScript output directory |
|
|
310
|
+
| `generateRules` | `boolean` | `true` | Generate Ant Design validation rules |
|
|
@@ -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` |
|