-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathllms.txt
More file actions
125 lines (86 loc) · 14.1 KB
/
Copy pathllms.txt
File metadata and controls
125 lines (86 loc) · 14.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# Route Forge for ThinkPHP
> The ThinkPHP adapter of the route-forge project: exposes ThinkPHP named routes to a Vue / React / Inertia SPA through a per-tier, lazy-loadable HTTP metadata endpoint, and generates TypeScript types from the real route rule tree — no hardcoded frontend URLs, no shipping the whole route table, no annotations. Composer package: `route-forge/thinkphp`.
## What it is
Route Forge is a ThinkPHP 8 package (`route-forge/thinkphp`, PHP ^8.2, topthink/framework ^8.0, MIT). It reads ThinkPHP's own route rule tree, assigns each **explicitly named** route to a **tier** you define, and serves the metadata over HTTP so a separately-deployed frontend can build URLs and load only the routes it currently needs.
All framework-agnostic business logic (tier resolution, aliases, repository, caching, TS type generation) lives in the shared core package the Laravel adapter also uses. This package only adapts ThinkPHP's routing primitives to that core. It is a **zero-invasive** adapter: it does not subclass or rebind ThinkPHP's `Route`/`RuleGroup`/`RuleItem`.
It is the authoritative backend counterpart to the frontend SDKs `@route-forge/core`, `@route-forge/vue`, and `@route-forge/react` (separate repository). The backend owns config; the client discovers it at runtime.
## When to reach for it
- A ThinkPHP API is consumed by a SPA or mobile web app whose HTML is **not** rendered by ThinkPHP.
- The named-route set is large enough that exporting all of it to the client is wasteful; you want **on-demand, per-tier loading**.
- You want **one source of truth** for route names/params shared between backend and frontend, with **TypeScript types** generated from the real route table.
## Install
```bash
composer require route-forge/thinkphp
php think route:forge:publish # copies package config/forge.php → app config/forge.php (skips if exists; --force overwrites with backup)
```
Manual copy equivalent (no ThinkPHP `vendor:publish`): `cp vendor/route-forge/thinkphp/config/forge.php config/forge.php`.
The `ForgeService` is auto-discovered via composer `extra.think.services`. If auto-discovery is off, add `\RouteForge\ThinkPHP\ForgeService::class` to `app/service.php`. The other `route:forge:*` commands emit a warning (and, on an interactive TTY, offer to copy) when `config/forge.php` is missing.
## Core API
### Assign tiers (three interchangeable, composable channels)
```php
use think\facade\Route;
// 1. explicit on the route (rides on think Rule::__call → route option, zero-invasive)
Route::get('auth/login', 'Auth@login')->name('auth.login')->tier('public');
// 2. group passthrough (nested groups: inner overrides outer)
Route::group('manage', function () {
Route::get('users', 'ManageUser@index')->name('manage.users.index');
})->tier('manage');
// 3. config-driven batch match in config/forge.php (prefix / middleware / any|all|DNF)
```
Priority (high → low): explicit `->tier()` > group passthrough > `classifier` callback > config match > `unassigned` fallback.
### Aliases
```php
Route::get('manage/members', 'Member@index')
->name('admin.members.index')->tier('manage')
->forgeAlias('admin.users.index'); // declare all aliases in ONE call (option overwrite semantics)
```
### Endpoints
```text
GET /_forge/routes/{level} # per-tier metadata (name + URI + methods + params)
GET /_forge/routes # summary: all tiers + global config, for client auto-discovery
```
### Manager (dev-only UI)
```text
GET /_forge/manager # page: self-contained single-file HTML (inline CSS/JS, zero CDN / build / npm deps)
GET /_forge/manager/api/routes # all named routes + tier assignment (alias rows carry alias_of)
GET /_forge/manager/api/config # current levels + global settings
PUT /_forge/manager/api/config # regenerate config/forge.php
```
Two stacked guards, both required. The routes are not registered at all unless `app_debug` is on — checked via `App::isDebug()`, because think only honours `APP_DEBUG=0/1` (the string `app_debug=false` in `.env` is truthy to think's env parser, so reading raw env would treat production as development). On top of that, `manager_allowed_ips` whitelists source IPs: default `['127.0.0.1', '::1']` (loopback only; `::1` because localhost may resolve to IPv6), `'*'` allows any source, `null` or `[]` disables the check, and a scalar string is equivalent to a single-element array. Key absent → safe default (loopback only), so projects that published config before this key existed stay closed.
Saving is stricter than the Laravel adapter: the existing `config/forge.php` is backed up as `.bak-{Ymd-His}` first, then both `runtime/config.php` (think's compiled config cache, which otherwise silently overrides it) and the route metadata cache are invalidated so the edit takes effect on the next request. Generated values are plain literals (no `Env::get` wrapper) — an edit in the UI must not be shadowed by a stale `.env`; a configured `classifier` closure makes the save refuse with 422 instead of being silently flattened to null; and write failures are logged server-side rather than echoed (messages contain absolute paths).
### Commands (think console)
```bash
php think route:forge:list [--level=manage] [--json] [--unassigned] [--aliases] [--unnamed]
php think route:forge:types [--level=admin] [--json] [--out=../frontend/src/types/forge-routes.d.ts]
php think route:forge:clear [--level=manage]
php think route:forge:publish [--force] # copies default config into app config/forge.php
php think route:forge:gen [--module=a,b|*] [--path=DIR] [--namespace=NS] [--dry-run] # materialize auto-routed endpoints into explicit named routes
```
Command messages use think console's `<info>` / `<comment>` / `<error>` tags. `route:forge:list` rows carry the same colour semantics as the Laravel adapter: a row whose level is `unassigned` is painted magenta end-to-end (think has no named magenta style, so the inline `<fg=magenta>` tag is used; the signal is "this route still needs a tier"), then alias rows yellow, aliased-target names green, collision rows red — priority `unassigned` > alias > default, and table mode only (`--json` / TS products stay plain). think's own colour probe is stale on Windows — it requires the OS build to be **exactly** `10.0.10586` and `TERM` to be exactly `xterm`, so Windows 11 and Git Bash (`TERM=xterm-256color`) always resolve to "no colour" and the tags get stripped to plain text. `ConsoleColorDetector` redoes that verdict at the command layer (Windows: build `>= 10.0.10586` **and** VT mode actually enabled, plus `WT_SESSION` / `MSYSCON` / `ConEmuANSI=ON` / prefixed `TERM` recognised as ANSI-capable third-party terminals). It stays deliberately conservative: no colour when stdout is not a terminal (so `--json` / `--out` products never contain escape codes), honours `NO_COLOR` and `TERM=dumb`, and never intervenes when the user passed `--ansi` or `--no-ansi` — `--ansi` is also the escape hatch to recommend when a terminal is so unusual that PHP cannot detect the console.
**Unnamed-route visibility.** `route:forge:list --unnamed` prints the full list of routes with no name (both level-matched-but-unnamed and unmatched). A route that a config `match` rule or the `classifier` assigns to a level but that has no name is no longer silent — it surfaces through the existing `warnings` channel (STDERR in table mode, the `warnings` field under `--json`), with wording per hit source; the legacy explicit-tier sentence `Route (…) has tier [x] but no route name assigned; …` is preserved verbatim as a substring (an HTTP-method column is appended). Routes that are unnamed **and** match no level are deliberately kept **out** of `warnings` (so a "warnings non-empty ⇒ broken config" CI gate isn't tripped by irrelevant routes); they appear only in `--unnamed`. The filter used by both the command analyzer and the HTTP repository is built once in `ForgeService::makeNameFilter()` and excludes the endpoint URI prefix, so forge's own level/summary endpoints (which may carry `endpoint_middleware` matched by another level) are never reported as config errors.
**Strict-mode violations aggregate.** Under `strict_mode`, `list`/`types` report every "should be in metadata but isn't" route at once instead of failing one-at-a-time: `list` prints a red (`<error>`) list and exits **1** without the normal table (under `--json`, stdout stays pure JSON and the list goes to STDERR); `types` writes the list to STDERR, exits 1, and refuses to emit `d.ts`. The HTTP endpoint returns 500 with code `RF_BE_009` (message contains the full list), superseding the per-route `RF_BE_001`; invalid tier names and classifier failures still fail fast with their own codes (`RF_BE_002` / `004` / `006`). Without `strict_mode`, unassigned named routes land in the `unassigned` level and command exit codes/products are unaffected.
### Inline summary (server-rendered HTML fast path)
Global helper, usable in any template: `{:forge_summary()}` in `<head>` before the bundle. Emits a one-shot, self-deleting, non-enumerable `window.__ROUTE_FORGE__` accessor so `@route-forge/core` skips the first summary HTTP round-trip.
## ThinkPHP-specific differences from the Laravel adapter (be honest about these)
- `->tier()` level validation happens at **scan time**, not definition time (ThinkPHP has no macro system; `->tier()` rides on `__call` → route option).
- A route is "named" only when explicitly `->name(...)`-ed AND its name differs from the route address string; ThinkPHP otherwise uses the address string as the default identifier, which this package treats as **unnamed** (excluded from metadata).
- `url_lazy_route=true` is **not supported** (scanning would be incomplete) → endpoints/commands fail fast with a clear message.
- Resource routes (`Route::resource`) generate unnamed rules → they do not appear in metadata; write individual named routes when you need metadata.
- Repeated `->forgeAlias()` calls **overwrite** (not merge): declare all aliases in one call.
- Manager page is rendered **without a view engine**: `think\View` is only a Manager shell whose drivers live under `\think\view\driver\` (requires installing `topthink/think-view`), so `ManagerPageRenderer` reads the bundled self-contained template and returns HTML directly — no extra dependency forced on consumers. No `route:clear` cascade linkage (ThinkPHP has no such command).
- No ThinkPHP `vendor:publish`: `php think route:forge:publish` copies the default config; the other commands warn (and interactively offer to copy) when `config/forge.php` is missing.
- `route:forge:gen` (ThinkPHP-only, adoption aid): materializes auto-routed endpoints (`/{controller}/{action}`, no rule written) into explicit named routes in a generated file (single-app `route/forge.auto.php`; multi-app `app/{module}/route/forge.auto.php`). Additive only — never deletes or rewrites existing rules; dangling entries (controller/method gone) are only reported; generated routes carry no tier (land in `unassigned` with a `->tier` TODO). Action segment follows ThinkPHP's own reachability rule (`URL segment + route.action_suffix` must hit the method), so a reachable URL is the method name with the suffix stripped; methods not ending in that suffix have no reachable URL and are reported without being generated. Auto-detects single/multi app, rejects `--module` in single mode, requires explicit `--module` in multi mode, and stops with `ambiguous` when `app/controller` coexists with module controller dirs (pass `--mode=single|multi`). camelCase actions are generated verbatim with a case-sensitivity caveat (`url_case_sensitive=true` would break legacy lowercase URLs); invokable controllers and path-param endpoints are left to hand-authoring.
- Command warnings **and failure messages** go straight to `STDERR` (ThinkPHP console has no separate stderr output stream); stdout stays clean for `--json` / `--out` product piping.
- Windows console colour is broken **in the framework**, not in this package: think's `hasColorSupport()` accepts only build exactly `10.0.10586` or `TERM` exactly `xterm`, so Windows 11 / Git Bash render forge messages as plain text while the Laravel adapter is colourful. This package re-does the verdict at the command layer (see *Commands* above); do not assume ThinkPHP itself has been fixed, and `--ansi` / `--no-ansi` still win over it.
## Conventions for agents working in this repo
- RouteInfo contract conversions the adapter MUST perform: `<id>`/`<id?>` → `{id}`/`{id?}`; method `'*'` → full method set; GET gets HEAD appended (Laravel parity); `option['middleware']` tuples flattened to class strings.
- `RouteCache` TTL: think cache uses `0 = forever`, so the adapter maps common's `null` → `set(..., 0)` (never pass `null` to think `set()`, that means "default expire").
- Framework-internal route name excluded from every scan: `__think_auto_route__` (in addition to the shared `forge.routes.*` / `forge.manager.*` prefixes).
- `->tier()` / `->forgeAlias()` are `__call` magic (no real declaration): two compensations — `OptionTypoScanner` warns on misspelled `tier*`/`forge*` options in `route:forge:list`/`types`; and package-root `_ide_helper.php` (dev-only, must NOT be autoloaded/required, else collides with the real `think\route\Rule`) adds `@method` for IDE completion. If you change tier/forgeAlias signatures, keep `_ide_helper.php`, `llms.txt`, and README's IDE section in sync.
- Tests build a minimal ThinkPHP app via `tests/Support/AppFactory.php` (no skeleton dependency). Temp dirs default to `F:\tmp` (override with `RF_TEST_TMP`); CI falls back to system temp. `.env` debug uses `APP_DEBUG=0/1` only — the string `false` is truthy to ThinkPHP's env parser.
- Do NOT commit the local `repositories` (path) block in `composer.json`; it is dev-only linkage to `php-common`. Necessary committed changes to `composer.json` are limited to `autoload.files` and `extra.think.services`.
- Commit style: `type(thinkphp): 中文描述`; run the full suite green before any commit; never push without explicit instruction.
## Links
- Docs site (framework-agnostic, authoritative — SPEC / DESIGN / install guides): https://route-forge.github.io/docs/
- Shared core: `route-forge/common` (https://github.com/route-forge/php-common)
- Frontend SDK: `@route-forge/core`