- name
- piu-hyperf-sync
- description
- Deep sync a Hyperf PHP backend to PIU — extracts routes via Hyperf CLI, parses controller validation rules and response shapes, builds Eloquent model schemas, maps middleware to auth types, and creates PIU entities with full API documentation. Supports token-efficient incremental daily re-sync via manifest tracking. Use when the user says "sync hyperf", "import hyperf", "hyperf api sync", or when piu-backend-sync detects framework: "hyperf".
# PIU Hyperf Sync
Deep-syncs a Hyperf PHP backend API to PIU. Uses `php bin/hyperf.php describe:routes` as the authoritative route source, Claude's PHP comprehension for semantic extraction, and a manifest-based incremental sync for daily re-runs.
## Prerequisites
- The Hyperf project must be bootable (`php bin/hyperf.php describe:routes` must work)
- PIU must be running with MCP enabled (the `piu.ts` CLI from `piu-backend-sync` must connect)
- Git must be available for commit tracking
## CLI Scripts
This skill uses scripts from two locations:
```bash
# Route parser (this skill)
bun skills/piu-hyperf-sync/scripts/parse-routes.ts < routes.txt
# PIU MCP client (from piu-backend-sync)
bun skills/piu-backend-sync/scripts/piu.ts <command> [args...]
```
The PIU CLI path is relative to the PIU repo root. Set `PIU_ROOT` if running from elsewhere:
```bash
PIU_ROOT=/Users/gwddeveloper/opensource/piu
PIU_CLI="bun $PIU_ROOT/skills/piu-backend-sync/scripts/piu.ts"
PARSE_ROUTES="bun $PIU_ROOT/skills/piu-hyperf-sync/scripts/parse-routes.ts"
```
---
## Phase 0: Pre-flight
Before starting, verify the environment and determine sync mode.
### Step 0.1 — Verify Hyperf CLI
```bash
cd $REPO && php bin/hyperf.php describe:routes 2>&1 | head -5
```
If this fails or shows an error, STOP. The Hyperf project is not bootable. Common fixes:
- Missing composer dependencies: `composer install`
- PHP extension missing: check `php -m` for `swoole`, `pdo`, `redis`
- Config error: check `config/autoload/server.php`
### Step 0.2 — Get HEAD commit
```bash
COMMIT=$(git -C $REPO rev-parse HEAD)
echo "HEAD commit: $COMMIT"
```
### Step 0.3 — Check for existing manifest
```bash
MANIFEST="$REPO/.piu-sync/manifest.json"
if [ -f "$MANIFEST" ]; then
echo "Existing manifest found — incremental sync mode"
echo "Last sync commit: $(cat $MANIFEST | bun -e 'console.log(JSON.parse(await Bun.stdin.text()).commit)')"
# Jump to Phase 6 (Incremental Sync) unless --force
else
echo "No manifest — full sync mode"
# Continue to Phase 1
fi
```
If the user passes `--force` or says "full sync", skip the manifest check and do a full sync regardless.
---
## Phase 1: Route Extraction
Use the Hyperf CLI for authoritative route data, then parse into structured JSON.
### Step 1.1 — Extract routes
```bash
cd $REPO && php bin/hyperf.php describe:routes 2>&1 > /tmp/piu-hyperf-routes-raw.txt
cat /tmp/piu-hyperf-routes-raw.txt | $PARSE_ROUTES > /tmp/piu-hyperf-routes.json
```
This produces a JSON array:
```json
[
{
"server": "http",
"method": "POST",
"uri": "/user/task/create",
"action": "App\\Controller\\User\\UserTaskController::create",
"middleware": ["AuthToken"]
}
]
```
### Step 1.2 — Group into collections
Read `/tmp/piu-hyperf-routes.json` and group routes by first URI path segment:
| URI Prefix | Collection Name | path_prefix |
|-----------|----------------|-------------|
| `/user` | User | `/user` |
| `/admin` | Admin | `/admin` |
| `/public` | Public | `/public` |
| `/patient` | Patient | `/patient` |
| `/pre` | Pre | `/pre` |
| `/appointment` | Appointment | `/appointment` |
| `/reception` | Reception | `/reception` |
| `/lab` | Lab | `/lab` |
| `/auth` | Root | `` (empty) |
| `/tool` | Root | `` (empty) |
| `/` | Root | `` (empty) |
Top-level routes without a clear group go into "Root".
### Step 1.3 — Map middleware to auth type
For each route, determine the auth configuration from its middleware array:
| Middleware | PIU Auth Config | Env Variable |
|-----------|----------------|-------------|
| `AuthToken` | `{"type":"bearer","token":"{{staff_token}}"}` | `staff_token` |
| `AuthAdmin` | `{"type":"bearer","token":"{{admin_token}}"}` | `admin_token` |
| `AuthReception` | `{"type":"bearer","token":"{{reception_token}}"}` | `reception_token` |
| `AuthPatient` | `{"type":"bearer","token":"{{patient_token}}"}` | `patient_token` |
| `PreAuth` | `{"type":"bearer","token":"{{pre_token}}"}` | `pre_token` |
| `PreAuthWithStatus` | `{"type":"bearer","token":"{{pre_token}}"}` | `pre_token` |
| `CheckInAuth` | `{"type":"bearer","token":"{{checkin_token}}"}` | `checkin_token` |
| _(empty array)_ | `{"type":"none"}` | — |
If a route has multiple auth middleware (shouldn't happen but possible), use the first non-CORS one.
### Step 1.4 — Save route snapshot
```bash
cp /tmp/piu-hyperf-routes-raw.txt $REPO/.piu-sync/routes-snapshot.txt
```
This is used later for text-based diffing during incremental sync.
---
## Phase 2: Controller Analysis
For each unique controller file referenced by routes, read the PHP source and extract validation rules, undocumented inputs, response shapes, and side effects.
### Step 2.1 — Identify controller files
From the route JSON, extract unique controller file paths:
```
action: "App\\Controller\\User\\UserTaskController::create"
-> file: app/Controller/User/UserTaskController.php
-> method: create
```
The mapping is: replace `App\\` with `app/`, replace `\\` with `/`, append `.php`, strip `::method`.
Group routes by controller file to minimize file reads.
### Step 2.2 — Read and analyze each controller
For each controller file, read the full file and analyze every method that is mapped to a route.
#### 2.2a — Extract validation rules
Look for `$this->validation([...])` calls within each method. The rules array uses Laravel validation syntax:
```php
$this->validation([
'title' => 'required|string|max:200',
'assignee_id' => 'nullable|integer',
'priority' => 'nullable|integer|in:1,2,3,4',
'due_date' => 'nullable|date_format:Y-m-d H:i:s',
'label_ids' => 'nullable|array',
'label_ids.*' => 'integer',
]);
```
Parse each rule string into a PIU model field:
| Laravel Rule | PIU field_type | required | description notes |
|---|---|---|---|
| `required` | — | `true` | — |
| `nullable` | — | `false` | — |
| `string` | `string` | — | — |
| `integer` | `integer` | — | — |
| `numeric` | `number` | — | — |
| `boolean` | `boolean` | — | — |
| `array` | `array` | — | Check `field.*` rule for element type |
| `date` | `date` | — | — |
| `date_format:F` | `datetime` | — | Add "format: F" to description |
| `email` | `string` | — | Add "email format" to description |
| `url` | `string` | — | Add "URL format" to description |
| `in:a,b,c` | (base type) | — | Add "enum: a,b,c" to description |
| `max:N` | — | — | Add "max: N" to description |
| `min:N` | — | — | Add "min: N" to description |
| `file` / `image` | `file` | — | Body type becomes `multipart` |
| `json` | `object` | — | — |
If no `required` or `nullable` is specified, default to `required: false`.
Build a request model named `{ControllerShortName}{Method}Request` (e.g., `UserTaskCreateRequest`).
#### 2.2b — Extract undocumented inputs
Search the method body for `$this->request->input('field')` or `$this->request->input('field', default)` calls where `'field'` does NOT appear in the validation rules array.
Add these as fields in the request model with `documented: false` in the description. Example:
```
{name: "keyword", field_type: "string", required: false, description: "Undocumented — found via $this->request->input()"}
```
Also check for:
- `$this->request->all()` — means the endpoint accepts arbitrary fields
- `(int) $this->request->input('field')` — the cast reveals the type
#### 2.2c — Extract response shape
Trace what `successResponse()` or `$this->response->json()` receives. Common patterns in this codebase:
1. **Direct model**: `successResponse($model->toArray())`
-> Response model = the Eloquent model's serialized form (see Phase 3)
2. **Eager-loaded model**: `$model->load(['creator:id,first_name,last_name'])->toArray()`
-> Response includes nested relation subsets
3. **Query select**: `User::query()->select(['id', 'name', 'email'])->get()`
-> Response model = subset of fields
4. **Manual array**: `successResponse(['token' => $token, 'expires_in' => 3600])`
-> Inline response model with explicit fields
5. **Collection map**: `$items->map(fn($i) => ['id' => $i->id, 'name' => $i->name])`
-> Response model = array of mapped objects
6. **Paginated**: Methods using `->paginate()` or manual `page`/`per_page` logic
-> Response wraps data in pagination metadata
For all patterns, the outer wrapper is always:
```json
{"code": 1, "message": "OK", "data": <inner_shape>}
```
Build a response model named `{ControllerShortName}{Method}Response`.
#### 2.2d — Extract side effects
Search each method body for:
| Pattern | Side Effect |
|---------|-------------|
| `Helper::redisNotice()` or `->publish(` on Redis notice pool | Triggers notification via Redis pub/sub |
| `Helper::redisChat()` or `->publish(` on Redis chat pool | Triggers chat event via Redis pub/sub |
| `$this->push(` or `dispatch(` or `AsyncQueue` | Queues async job |
| `EventDispatcherInterface` or `$this->eventDispatcher->dispatch(` | Fires domain event |
| `AuditLogger::` or `AuditLog::create(` | Creates audit log entry |
| `EmailSender::` | Sends email notification |
Record these as a list of strings for inclusion in the endpoint documentation.
#### 2.2e — Record method line ranges
For each method analyzed, record the start and end line numbers for future method-level hashing:
```json
{
"create": {"line_start": 45, "line_end": 120},
"list": {"line_start": 122, "line_end": 180}
}
```
Use the `public function methodName(` signature as the start marker and the next `public function` or end of class as the end marker.
---
## Phase 3: Model Extraction
Auf GitHub ansehen