start-vibing-stacks 2.16.0 → 2.17.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-vibing-stacks",
3
- "version": "2.16.0",
3
+ "version": "2.17.0",
4
4
  "description": "AI-powered multi-stack dev workflow for Claude Code. Supports PHP, Node.js, Python and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,65 +1,138 @@
1
1
  ---
2
2
  name: composer-workflow
3
- version: 1.0.0
3
+ version: 2.0.0
4
+ description: "Composer 2.8+ (Oct 2024) workflow for Laravel / PHP 8.3-8.5 projects. Covers composer.json structure, PSR-4 layout, scripts orchestration, audit command (--abandoned, --ignore-severity, exit codes 1=vulns/2=abandoned/3=both), --patch-only updates, --ignore-scripts for supply-chain hardening, allow-missing-requirements, lockfile hygiene, optimized autoloader for prod, sort-packages, plugin allowlist. Invoke when initializing a project, adding dependencies, debugging install/update conflicts, or wiring CI quality scripts."
4
5
  ---
5
6
 
6
- # Composer Workflow
7
+ # Composer Workflow (2.8+)
8
+
9
+ **Invoke when initializing a Composer project, adding deps, debugging install/update, or wiring CI scripts.**
7
10
 
8
11
  ## Requirements
9
12
 
10
- - **Composer >= 2.0**
11
- - **PHP >= 8.3**
13
+ - **Composer 2.8** (released Oct 2, 2024) — earlier versions miss audit/security features below
14
+ - **PHP 8.3** in `require` (8.4 recommended for new projects — see `php-patterns`)
15
+ - **`composer.lock` MUST be committed** for both apps and libraries
12
16
 
13
17
  ## Essential Commands
14
18
 
15
19
  ```bash
16
- composer install # Install from lock file (CI/deploy)
17
- composer update # Update dependencies (dev only)
18
- composer require package/name # Add dependency
19
- composer require --dev pkg # Add dev dependency
20
- composer dump-autoload -o # Optimize autoloader (production)
20
+ composer install # From lock used in CI/deploy
21
+ composer install --no-dev --optimize-autoloader --classmap-authoritative # Production
22
+ composer update # Refresh lock dev only, never CI
23
+ composer update --patch-only # 2.8+: only patch versions (safe weekly)
24
+ composer update vendor/pkg --with-all-dependencies # Update one + its deps
25
+
26
+ composer require vendor/pkg # Add runtime
27
+ composer require --dev vendor/pkg # Add dev-time
28
+ composer remove vendor/pkg
29
+
30
+ composer audit # Security scan — MUST be in CI
31
+ composer audit --abandoned=fail # 2.8+: also fail on abandoned packages
32
+ composer audit --ignore-severity=low # 2.8+: skip noisy lows in CI
33
+
34
+ composer outdated # List packages with newer releases
35
+ composer outdated --direct # Only top-level deps
36
+ composer outdated --major-only # Only majors (planning sessions)
37
+
38
+ composer dump-autoload --optimize # Regenerate, optimized
39
+ composer validate --strict # composer.json sanity check
40
+ composer why vendor/pkg # Who pulled this in?
41
+ composer why-not vendor/pkg "^2.0" # Why can't I install this version?
21
42
  ```
22
43
 
23
- ## composer.json Structure
44
+ ## composer.json — production-grade baseline
24
45
 
25
46
  ```json
26
47
  {
27
48
  "name": "vendor/project",
28
49
  "type": "project",
50
+ "license": "proprietary",
29
51
  "require": {
30
- "php": ">=8.3"
52
+ "php": "^8.3",
53
+ "ext-mbstring": "*",
54
+ "ext-pdo": "*"
31
55
  },
32
56
  "require-dev": {
33
- "phpunit/phpunit": "^11.0",
57
+ "phpunit/phpunit": "^12.0",
58
+ "pestphp/pest": "^4.0",
34
59
  "phpstan/phpstan": "^2.0",
35
- "friendsofphp/php-cs-fixer": "^3.0"
60
+ "phpstan/phpstan-deprecation-rules": "^2.0",
61
+ "friendsofphp/php-cs-fixer": "^3.0",
62
+ "roave/security-advisories": "dev-latest"
36
63
  },
37
64
  "autoload": {
38
- "psr-4": {
39
- "App\\": "src/"
40
- }
65
+ "psr-4": { "App\\": "src/" }
41
66
  },
42
67
  "autoload-dev": {
43
- "psr-4": {
44
- "Tests\\": "tests/"
45
- }
68
+ "psr-4": { "Tests\\": "tests/" }
46
69
  },
47
70
  "scripts": {
48
- "test": "phpunit",
49
- "lint": "phpstan analyse --level=6",
50
- "format": "php-cs-fixer fix",
51
- "check": [
52
- "@lint",
53
- "@test"
54
- ]
71
+ "test": "phpunit",
72
+ "test:cov":"XDEBUG_MODE=coverage phpunit --coverage-text --coverage-clover=coverage.xml",
73
+ "lint": "phpstan analyse --memory-limit=1G",
74
+ "fix": "php-cs-fixer fix",
75
+ "fix:check":"php-cs-fixer fix --dry-run --diff",
76
+ "audit": "composer audit --abandoned=fail",
77
+ "check": ["@lint", "@fix:check", "@audit", "@test"]
78
+ },
79
+ "scripts-descriptions": {
80
+ "check": "Run full quality gate locally — mirrors CI"
55
81
  },
56
82
  "config": {
57
83
  "sort-packages": true,
58
- "allow-plugins": {}
59
- }
84
+ "optimize-autoloader": true,
85
+ "preferred-install": "dist",
86
+ "allow-plugins": {
87
+ "pestphp/pest-plugin": true
88
+ }
89
+ },
90
+ "minimum-stability": "stable",
91
+ "prefer-stable": true
60
92
  }
61
93
  ```
62
94
 
95
+ ## Audit — exit codes (2.8+, use in CI)
96
+
97
+ | Exit | Meaning |
98
+ |---|---|
99
+ | `0` | Clean |
100
+ | `1` | Vulnerabilities found |
101
+ | `2` | Abandoned packages found (with `--abandoned=fail`) |
102
+ | `3` | Both |
103
+
104
+ ```yaml
105
+ # .github/workflows/ci.yml — fail PR on vulns OR abandoned packages
106
+ - name: Composer audit
107
+ run: composer audit --abandoned=fail --ignore-severity=low
108
+ ```
109
+
110
+ The `--ignore-severity` flag is critical for CI ergonomics — without it a single LOW advisory blocks every PR.
111
+
112
+ ## Supply-Chain Hardening (links to security-baseline 2025-A03)
113
+
114
+ ```bash
115
+ # Production install — never run third-party scripts on the build server
116
+ composer install --no-dev --no-scripts --optimize-autoloader --classmap-authoritative
117
+ ```
118
+
119
+ `config.allow-plugins` is a strict allowlist as of 2.x — Composer prompts/refuses unknown plugins. Keep the list minimal:
120
+
121
+ ```json
122
+ "config": {
123
+ "allow-plugins": {
124
+ "pestphp/pest-plugin": true,
125
+ "php-http/discovery": false
126
+ }
127
+ }
128
+ ```
129
+
130
+ Pin **Roave Security Advisories** as a `dev` dep — it makes Composer **refuse to install** any version with a known CVE:
131
+
132
+ ```bash
133
+ composer require --dev roave/security-advisories:dev-latest
134
+ ```
135
+
63
136
  ## PSR-4 Autoloading
64
137
 
65
138
  ```
@@ -71,13 +144,24 @@ src/
71
144
  ├── Models/
72
145
  │ └── User.php → App\Models\User
73
146
  └── Middleware/
74
- └── AuthMiddleware.php → App\Middleware\AuthMiddleware
147
+ └── AuthMiddleware.php → App\Middleware\AuthMiddleware
75
148
  ```
76
149
 
150
+ For Laravel projects use `App\\` → `app/` (Laravel default), not `src/`.
151
+
152
+ ## Lockfile Hygiene
153
+
154
+ - **Always commit** `composer.lock`
155
+ - After `composer require/remove/update`, run `composer validate --strict` and commit the lock with the same commit
156
+ - CI uses `composer install` (deterministic) — never `composer update`
157
+ - Dependabot/Renovate watches `composer.json`; configure auto-merge for `--patch-only` after CI green
158
+
77
159
  ## Rules
78
160
 
79
- 1. **Always commit composer.lock**
80
- 2. **Never edit vendor/**
81
- 3. **Use `composer install` in CI/production** (not update)
82
- 4. **PHP >= 8.3 in require** enforce minimum version
83
- 5. **PSR-4 autoloading** no manual includes
161
+ 1. **`composer.lock` is mandatory in git** — apps and libraries
162
+ 2. **Production = `--no-dev --no-scripts --optimize-autoloader --classmap-authoritative`** (supply-chain + perf)
163
+ 3. **CI runs `composer audit --abandoned=fail`** failing the build on vulns or abandons
164
+ 4. **Never edit `vendor/`** patch via `cweagans/composer-patches` if absolutely required
165
+ 5. **Pin `php` constraint** to your runtime (`^8.3` or `^8.4`); upgrade deliberately
166
+ 6. **Roave Security Advisories** as a dev dep on every project
167
+ 7. **Allow-plugins allowlist** kept minimal and reviewed
@@ -1,23 +1,37 @@
1
1
  ---
2
2
  name: external-api-patterns
3
- version: 1.0.0
3
+ version: 2.0.0
4
+ description: "Laravel patterns for consuming external APIs / SDKs / webhooks (2026). Two recommended approaches: (1) Laravel HTTP Client (`Http::`) wrapped in a Service for one-off integrations; (2) Saloon v3 for SDK-style integrations with multiple endpoints (Connectors + Requests + Responses, retry/auth/middleware/pagination first-class, mock client for testing, Laravel plugin). Octane-safe (no static client state), typed DTOs, typed exceptions, structured logging, idempotent webhook reception with signature verification (2025-A10 fail-closed). Invoke when adding any third-party integration or webhook handler."
4
5
  ---
5
6
 
6
- # External API Patterns — HTTP Client for Laravel + Octane
7
+ # External API Patterns — Laravel + Octane (2026)
7
8
 
8
9
  **ALWAYS invoke when consuming external APIs, webhooks, or third-party services.**
9
10
 
11
+ ## Choose your tool
12
+
13
+ | Scenario | Tool |
14
+ |---|---|
15
+ | 1–3 endpoints from one vendor, simple req/res | Laravel `Http::` Client wrapped in a Service |
16
+ | SDK-shaped integration: many endpoints, auth flows, pagination, custom responses | **Saloon v3** (Connectors + Requests + Responses) |
17
+ | Vendor publishes its own SDK | Use the SDK; still wrap in a Service for testability |
18
+
19
+ Either way: **never call `Http::` or a vendor SDK directly from a Controller**. Always go through an injected Service.
20
+
10
21
  ## Architecture
11
22
 
12
23
  ```
13
- Controller/Job
14
- → ApiService (business logic)
15
- Http::withOptions() (Laravel HTTP Client)
16
- → Response handling (DTO)
17
- Error handling (typed exceptions)
18
- Logging + monitoring
19
-
20
- NEVER call Http:: directly in controllers. Always through a Service.
24
+ Controller / Job / Listener
25
+
26
+ ApiService (business logic, transactions)
27
+
28
+ HTTP transport
29
+ ├── Http::baseUrl(...) response()->json() (Laravel Client path)
30
+ └── new Connector → ->send(new Request()) (Saloon path)
31
+
32
+ Typed DTO (readonly class)
33
+
34
+ Typed exception on failure / log + return on success
21
35
  ```
22
36
 
23
37
  ## Service Pattern
@@ -308,100 +322,157 @@ class AiModelController extends Controller
308
322
  }
309
323
  ```
310
324
 
311
- ## Frontend Consumption (React + Inertia)
325
+ ## Saloon v3 — SDK-style integrations *(2026 recommended)*
312
326
 
313
- ```tsx
314
- // resources/js/services/api.ts
315
- import axios, { AxiosError } from 'axios';
327
+ Use when the integration has **many endpoints, auth flows, pagination, or warrants its own folder**. Saloon turns "an API" into an OOP shape: a `Connector` (base URL + auth + middleware) plus one `Request` per endpoint plus optional custom `Response` classes.
316
328
 
317
- interface ApiResponse<T> {
318
- success: boolean;
319
- message: string;
320
- data: T;
321
- errors?: Record<string, string[]>;
322
- }
329
+ ### Install
323
330
 
324
- interface ApiErrorResponse {
325
- success: false;
326
- message: string;
327
- errors?: Record<string, string[]>;
328
- retry_after?: number;
329
- }
331
+ ```bash
332
+ composer require saloonphp/saloon "^3.0"
333
+ composer require saloonphp/laravel-plugin "^4.0" # Laravel integration
334
+ php artisan vendor:publish --tag=saloon-config
335
+ ```
330
336
 
331
- export async function apiCall<T>(
332
- method: 'get' | 'post' | 'put' | 'delete',
333
- url: string,
334
- data?: unknown,
335
- ): Promise<T> {
336
- try {
337
- const response = await axios({ method, url, data });
338
- const body = response.data as ApiResponse<T>;
339
- if (!body.success) throw new ApiError(body.message, response.status);
340
- return body.data;
341
- } catch (error) {
342
- if (error instanceof AxiosError) {
343
- const body = error.response?.data as ApiErrorResponse;
344
- const status = error.response?.status ?? 500;
345
-
346
- if (status === 429) {
347
- throw new RateLimitError(body?.retry_after ?? 60);
348
- }
349
- if (status === 422 && body?.errors) {
350
- throw new ValidationError(body.message, body.errors);
351
- }
352
- throw new ApiError(body?.message ?? 'Connection failed', status);
353
- }
354
- throw error;
337
+ ### Folder layout
338
+
339
+ ```
340
+ app/Http/Integrations/
341
+ └── OpenAi/
342
+ ├── OpenAiConnector.php
343
+ ├── Requests/
344
+ │ ├── ChatCompletion.php
345
+ │ └── Embeddings.php
346
+ └── Responses/
347
+ └── ChatCompletionResponse.php
348
+ ```
349
+
350
+ ### Connector
351
+
352
+ ```php
353
+ // app/Http/Integrations/OpenAi/OpenAiConnector.php
354
+ namespace App\Http\Integrations\OpenAi;
355
+
356
+ use Saloon\Http\Connector;
357
+ use Saloon\Http\Auth\TokenAuthenticator;
358
+ use Saloon\Traits\Plugins\AcceptsJson;
359
+ use Saloon\RateLimitPlugin\Traits\HasRateLimits;
360
+
361
+ class OpenAiConnector extends Connector
362
+ {
363
+ use AcceptsJson;
364
+ use HasRateLimits;
365
+
366
+ public function resolveBaseUrl(): string
367
+ {
368
+ return config('services.openai.base_url', 'https://api.openai.com/v1');
355
369
  }
356
- }
357
370
 
358
- // Typed errors
359
- export class ApiError extends Error {
360
- constructor(message: string, public status: number) { super(message); }
361
- }
362
- export class ValidationError extends ApiError {
363
- constructor(message: string, public errors: Record<string, string[]>) { super(message, 422); }
371
+ protected function defaultHeaders(): array
372
+ {
373
+ return ['User-Agent' => 'myapp/1.0'];
374
+ }
375
+
376
+ protected function defaultAuth(): TokenAuthenticator
377
+ {
378
+ return new TokenAuthenticator(config('services.openai.key'));
379
+ }
380
+
381
+ protected function defaultConfig(): array
382
+ {
383
+ return ['timeout' => 30, 'connect_timeout' => 5];
384
+ }
364
385
  }
365
- export class RateLimitError extends ApiError {
366
- constructor(public retryAfter: number) { super('Too many requests', 429); }
386
+ ```
387
+
388
+ ### Request
389
+
390
+ ```php
391
+ // app/Http/Integrations/OpenAi/Requests/ChatCompletion.php
392
+ namespace App\Http\Integrations\OpenAi\Requests;
393
+
394
+ use Saloon\Contracts\Body\HasBody;
395
+ use Saloon\Enums\Method;
396
+ use Saloon\Http\Request;
397
+ use Saloon\Traits\Body\HasJsonBody;
398
+
399
+ class ChatCompletion extends Request implements HasBody
400
+ {
401
+ use HasJsonBody;
402
+
403
+ protected Method $method = Method::POST;
404
+
405
+ public function __construct(
406
+ protected readonly string $model,
407
+ protected readonly array $messages,
408
+ protected readonly float $temperature = 0.7,
409
+ ) {}
410
+
411
+ public function resolveEndpoint(): string { return '/chat/completions'; }
412
+
413
+ protected function defaultBody(): array
414
+ {
415
+ return [
416
+ 'model' => $this->model,
417
+ 'messages' => $this->messages,
418
+ 'temperature' => $this->temperature,
419
+ ];
420
+ }
367
421
  }
368
422
  ```
369
423
 
370
- ### React Component Usage
424
+ ### Calling from a Service
371
425
 
372
- ```tsx
373
- const LABELS = {
374
- generating: __('messages.ai.generating'),
375
- error: __('messages.errors.error'),
376
- rateLimited: __('messages.errors.rate_limited'),
377
- } as const;
426
+ ```php
427
+ namespace App\Services\External;
378
428
 
379
- export default function AiChat() {
380
- const [loading, setLoading] = useState(false);
429
+ use App\Http\Integrations\OpenAi\OpenAiConnector;
430
+ use App\Http\Integrations\OpenAi\Requests\ChatCompletion;
431
+ use App\DTOs\Api\ChatCompletionResponse;
381
432
 
382
- const generate = async () => {
383
- setLoading(true);
384
- try {
385
- const result = await apiCall<{ content: string }>('post', route('api.v1.ai.generate'), {
386
- model: 'gpt-4',
387
- messages: [{ role: 'user', content: prompt }],
388
- });
389
- setResponse(result.content);
390
- } catch (error) {
391
- if (error instanceof RateLimitError) {
392
- toast.error(`${LABELS.rateLimited} (${error.retryAfter}s)`);
393
- } else if (error instanceof ValidationError) {
394
- Object.values(error.errors).flat().forEach(msg => toast.error(msg));
395
- } else {
396
- toast.error(error instanceof ApiError ? error.message : LABELS.error);
397
- }
398
- } finally {
399
- setLoading(false);
400
- }
401
- };
433
+ class OpenAiService
434
+ {
435
+ public function __construct(private readonly OpenAiConnector $api) {}
436
+
437
+ public function chat(string $model, array $messages): ChatCompletionResponse
438
+ {
439
+ $response = $this->api
440
+ ->send(new ChatCompletion($model, $messages))
441
+ ->throw();
442
+ return ChatCompletionResponse::fromArray($response->json());
443
+ }
402
444
  }
403
445
  ```
404
446
 
447
+ ### Test with Saloon's MockClient
448
+
449
+ ```php
450
+ use Saloon\Http\Faking\MockClient;
451
+ use Saloon\Http\Faking\MockResponse;
452
+ use App\Http\Integrations\OpenAi\Requests\ChatCompletion;
453
+
454
+ it('returns the assistant message', function () {
455
+ $mock = new MockClient([
456
+ ChatCompletion::class => MockResponse::make(['choices' => [['message' => ['content' => 'hi']]], 'usage' => ['prompt_tokens' => 1, 'completion_tokens' => 1]], 200),
457
+ ]);
458
+ app(OpenAiConnector::class)->withMockClient($mock);
459
+
460
+ $resp = app(OpenAiService::class)->chat('gpt-4', [['role' => 'user', 'content' => 'hi']]);
461
+ expect($resp->content)->toBe('hi');
462
+ });
463
+ ```
464
+
465
+ ### When Saloon shines vs raw `Http::`
466
+
467
+ - **Auth is non-trivial** (OAuth2 device flow, signed requests, paginated tokens)
468
+ - **Pagination** (cursor / page / link-header) — Saloon ships first-class paginators
469
+ - **Many endpoints** sharing the same base config — DRY via the Connector
470
+ - **Test coverage matters** — `MockClient` captures request shape, not just HTTP verb
471
+ - **Ratelimit awareness** — `HasRateLimits` plugin tracks per-connector budgets
472
+ - **Want the API to feel like a vendor SDK** to your call sites
473
+
474
+ For 1-2 throwaway endpoints, raw `Http::` is fine — don't over-engineer.
475
+
405
476
  ## Octane Safety
406
477
 
407
478
  ```php
@@ -513,6 +584,17 @@ Log::error('[ServiceName] API failed', [
513
584
  | `static $client` in Octane | Instance `$this->client` per request |
514
585
  | No timeout on HTTP calls | `->timeout(30)->connectTimeout(5)` |
515
586
  | No retry logic | `->retry(3, backoff, when)` |
516
- | Webhook without signature check | ALWAYS verify signatures |
587
+ | Webhook without signature check | ALWAYS verify signatures (2025-A10 fail-closed) |
517
588
  | Webhook sync processing | Dispatch job for async |
518
589
  | `dd()` / `dump()` API responses | Structured `Log::info/error` |
590
+ | Many endpoints inlined as `Http::` calls | Promote to Saloon Connector + Requests |
591
+ | Mocking Saloon by stubbing `Http::` | Use `MockClient` (matches by Request class) |
592
+
593
+ ## See Also
594
+
595
+ - `axios-laravel-api` (frontend) — the React side that consumes this Laravel API
596
+ - `api-design` / `api-security` — request shape + Sanctum patterns
597
+ - `_shared/skills/security-baseline` (2025-A03 + 2025-A10) — supply chain + fail-closed
598
+ - `_shared/skills/observability` — structured logging + redaction
599
+ - `mariadb-octane` — transaction safety when API call participates in a DB transaction
600
+ - `phpunit-testing` — Pest 4 patterns for Saloon `MockClient` and `Http::fake()`
@@ -1,9 +1,10 @@
1
1
  ---
2
2
  name: laravel-octane
3
- version: 1.0.0
3
+ version: 2.0.0
4
+ description: "Laravel Octane patterns for production 2026 — covers FrankenPHP (recommended for new projects: single binary, automatic HTTPS, HTTP/3, early hints, Brotli, ~5× FPM throughput), RoadRunner (mature, best raw RPS, gRPC/WebSocket), and Swoole (best for I/O-heavy workloads via coroutines). Long-lived worker rules: no static state, no globals, no superglobals, no config() mutations, no die/exit/dd, scoped bindings instead of singletons, persistent DB connections with Octane flush, instance-scoped memoization. Includes server selection matrix and per-server config. Invoke when adopting Octane, choosing a server, debugging worker leaks, or hardening for persistent processes."
4
5
  ---
5
6
 
6
- # Laravel Octane (RoadRunner) Patterns
7
+ # Laravel Octane Patterns (FrankenPHP / RoadRunner / Swoole)
7
8
 
8
9
  ## How Octane Works
9
10
 
@@ -11,6 +12,16 @@ Octane runs your app in a **long-lived worker process**. The application boots O
11
12
 
12
13
  **Consequence:** Any state stored in static properties, globals, or singletons persists across requests and can leak between users.
13
14
 
15
+ ## Server Selection — 2026 matrix
16
+
17
+ | Server | When to choose | Strengths | Trade-offs |
18
+ |---|---|---|---|
19
+ | **FrankenPHP** | New projects; teams that value simplicity | Single Go binary; automatic HTTPS via Caddy; **HTTP/3**, early hints, Brotli; 5× PHP-FPM in 2025 benchmarks | Smaller community; weaker under heavy I/O latency |
20
+ | **RoadRunner** | Production at scale; need consistent top-tier RPS | Battle-tested; ~111% higher RPS than FPM; gRPC + WebSocket built-in; rich plugin ecosystem | Steeper DevOps curve; YAML config |
21
+ | **Swoole** | Workloads dominated by 200–500 ms I/O waits (DB, upstream APIs) | Coroutine concurrency shines under high I/O latency | Lower RPS in light-I/O workloads; smaller adoption for new apps |
22
+
23
+ Default recommendation for new Laravel 12 projects in 2026: **FrankenPHP** (developer experience + modern HTTP). Pick **RoadRunner** if you've already standardized on it or need its specific features.
24
+
14
25
  ## Critical Rules
15
26
 
16
27
  ### DO: Dependency Injection
@@ -165,6 +176,67 @@ app()->scoped('cart', fn () => new Cart());
165
176
  // Fresh instance per request, cleared between requests
166
177
  ```
167
178
 
179
+ ## FrankenPHP Configuration *(2026 default)*
180
+
181
+ Single binary; Caddy-based HTTPS; no PHP extension required.
182
+
183
+ ```bash
184
+ composer require laravel/octane
185
+ php artisan octane:install --server=frankenphp
186
+ php artisan octane:start --server=frankenphp --workers=4 --max-requests=500
187
+ ```
188
+
189
+ `Caddyfile` (production-grade, with HTTP/3 + early hints):
190
+
191
+ ```caddyfile
192
+ {
193
+ frankenphp
194
+ order php_server before file_server
195
+ servers {
196
+ protocols h1 h2 h2c h3
197
+ }
198
+ }
199
+
200
+ api.example.com {
201
+ encode br zstd gzip
202
+ root * public/
203
+ php_server {
204
+ worker /path/to/public/frankenphp-worker.php 4
205
+ }
206
+ header ?Permissions-Policy "interest-cohort=()"
207
+ header Early-Hints rel=preload
208
+ }
209
+ ```
210
+
211
+ `public/frankenphp-worker.php` (worker mode — what gives you the 5× speedup):
212
+
213
+ ```php
214
+ <?php
215
+ ignore_user_abort(true);
216
+ require __DIR__.'/../vendor/autoload.php';
217
+ $app = require __DIR__.'/../bootstrap/app.php';
218
+ $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
219
+
220
+ $max = (int)($_SERVER['MAX_REQUESTS'] ?? 500);
221
+ for ($i = 0; $i < $max; $i++) {
222
+ $keepRunning = \frankenphp_handle_request(function () use ($kernel) {
223
+ $request = Illuminate\Http\Request::capture();
224
+ $response = $kernel->handle($request);
225
+ $response->send();
226
+ $kernel->terminate($request, $response);
227
+ });
228
+ gc_collect_cycles();
229
+ if (!$keepRunning) break;
230
+ }
231
+ ```
232
+
233
+ | Setting | Where | Notes |
234
+ |---|---|---|
235
+ | `--workers` | CLI | Defaults to CPU cores; tune to CPU/IO ratio |
236
+ | `--max-requests` | CLI | Restart worker after N requests (memory safety, like RoadRunner's `max_jobs`) |
237
+ | `protocols h3` | Caddyfile | Enable HTTP/3 (QUIC) — single biggest mobile-network win |
238
+ | `Early-Hints` header | Response | Browser preloads CSS/JS during PHP think-time |
239
+
168
240
  ## RoadRunner Configuration (rr.yaml)
169
241
 
170
242
  ```yaml