Skip to content

feat(arr): inject async HTTP transport via ArrTransportInterface (F2b/B1/P1)#17

Merged
detain merged 1 commit into
masterfrom
fix/shared-f2b
Jun 28, 2026
Merged

feat(arr): inject async HTTP transport via ArrTransportInterface (F2b/B1/P1)#17
detain merged 1 commit into
masterfrom
fix/shared-f2b

Conversation

@detain

@detain detain commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Step F2b/B1/P1 — inject async transport via ArrTransportInterface

Findings: B1 (HIGH), P1 (HIGH), CQ1, F2 from findings/plan_phlix-shared.md (Phase 2). Depends on F2a (AbstractArrClient extraction, already merged #16).

The four Arr/* clients did a blocking synchronous curl_exec() (30s timeout) inside a Workerman/Webman resident-memory worker, contradicting the package's "zero I/O" charter and stalling all coroutines on that worker when an *arr instance is slow. This PR moves that blocking cURL behind an injectable transport seam (lib side only).

Changes

  • New src/Arr/Transport/ArrTransportInterface.phprequest(string $method, string $url, array $headers, ?string $body): array{status:int, body:string}. Implementations return status + raw body (they do NOT throw on non-2xx; the client maps status codes).
  • New default src/Arr/Transport/CurlArrTransport.php — the prior blocking cURL behaviour, moved out of AbstractArrClient::request(). Documented as CLI/test only.
  • AbstractArrClient — appended an OPTIONAL ?ArrTransportInterface $transport = null ctor param (BC-safe). All requests dispatch through the transport; when null it falls back to new CurlArrTransport($timeout) so existing direct instantiation is unchanged. No curl_exec runs when a transport is injected.
  • ArrClientFactory — appended optional ?ArrTransportInterface $transport, propagated to every created client.
  • ArrClientInterface unchanged — transport is a constructor concern, NOT a new interface method, so this is additive / minor, not breaking.
  • composer.json — reconciled the "zero I/O" framing: description now states the only bundled network code is the blocking CurlArrTransport (CLI/test only) and event-loop consumers MUST inject an async transport. ext-curl moved requirerequire-dev + suggest (needed only for the default transport); added a suggest for workerman/http-client.
  • CHANGELOG [Unreleased] note (additive/minor; no version bump in this PR).

How verified (full gate, all green)

  • composer testOK (354 tests, 2854 assertions) — includes new tests/Arr/Transport/InjectedTransportTest.php; existing tests/Arr/* pass unchanged with the default transport.
  • composer stan — PHPStan Level 9, no baseline: No errors.
  • composer cs — phpcs PSR-12 clean (src + verified new test files).
  • composer psalm — No errors found.

Success conditions met

  • A deterministic in-memory FakeArrTransport makes tests/Arr/* paths fully deterministic (closes CQ5).
  • InjectedTransportTest::testInjectedTransportShortCircuitsCurl asserts that injecting a fake transport short-circuits cURL entirely — the client is pointed at an unroutable URL (http://0.0.0.0:1) and still succeeds via the canned response, proving no curl_exec is reached.
  • Control test testDefaultTransportPerformsRealCurl proves the default CurlArrTransport DOES perform real cURL (raises a transport-level "cURL error" against the same unroutable URL) — so the fake genuinely replaces cURL.
  • Status-error mapping (401/404/4xx) and POST body encoding still work through the injected seam.

Coupling / consumer follow-up (separate PRs — the real "stop blocking the event loop" fix)

This PR is the lib seam only. The actual fix lands in the consumers:

  • phlix-server must add a workerman/http-client-backed WorkermanArrTransport and inject it (via ArrClientFactory / direct construction in CustomFormatSyncer, Application, etc.) so *arr calls stop stalling the worker.
  • phlix-hub must audit its RequestManager / HubServicesProvider *arr usage and inject the same async transport.
  • workerman/http-client belongs in the consumer composer files, NOT this dependency-light lib.

Per plan: minor bump + re-pin consumers after merge. Do not merge here — the Phase Coordinator owns the git cycle.

🤖 Generated with Claude Code

…/B1/P1)

Decouple the four *arr clients from blocking cURL so event-loop consumers can
inject a non-blocking transport. Closes findings B1/P1/CQ1/F2 (lib side).

- New Arr\Transport\ArrTransportInterface: request(method,url,headers,?body)
  returns array{status:int, body:string}.
- New default Arr\Transport\CurlArrTransport implementing the prior blocking
  cURL behaviour (cURL call moved out of AbstractArrClient). Documented as
  CLI/test only.
- AbstractArrClient: appended OPTIONAL ?ArrTransportInterface $transport = null
  ctor param (BC-safe); all I/O flows through the transport; null falls back to
  CurlArrTransport so direct instantiation is unchanged. When a transport is
  injected, no curl_exec is reached.
- ArrClientFactory: accepts + propagates an optional transport to every client.
- composer.json: keep "zero I/O by charter" honest — ext-curl moved to suggest/
  require-dev (only the bundled CurlArrTransport needs it); suggest
  workerman/http-client for event-loop consumers.
- Tests: deterministic in-memory FakeArrTransport + InjectedTransportTest
  asserting the injected transport short-circuits cURL entirely (canned response
  against an unroutable URL), POST body encoding, status-error mapping, and a
  control test proving the default transport performs real cURL. Existing
  tests/Arr/* still pass with the default transport.

No required method added to ArrClientInterface (transport is a ctor concern) —
additive/minor, not breaking.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@codecov

codecov Bot commented Jun 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.33333% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.89%. Comparing base (6336d08) to head (c850a20).

Files with missing lines Patch % Lines
src/Arr/Transport/CurlArrTransport.php 66.66% 12 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master      #17      +/-   ##
============================================
+ Coverage     75.38%   79.89%   +4.51%     
- Complexity      345      354       +9     
============================================
  Files            39       40       +1     
  Lines           967      985      +18     
============================================
+ Hits            729      787      +58     
+ Misses          238      198      -40     
Flag Coverage Δ
phpunit 79.89% <73.33%> (+4.51%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codacy-production

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 2 medium · 1 minor

Alerts:
⚠ 3 issues (≤ 0 issues of at least minor severity)

Results:
3 new issues

Category Results
Complexity 2 medium
Comprehensibility 1 minor

View in Codacy

🟢 Metrics 19 complexity · 0 duplication

Metric Results
Complexity 19
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@detain detain merged commit 5fba1d6 into master Jun 28, 2026
7 of 9 checks passed
@detain detain deleted the fix/shared-f2b branch June 28, 2026 16:52
detain added a commit that referenced this pull request Jun 28, 2026
Bump Version::VERSION to 0.11.0 and promote the [Unreleased] CHANGELOG
section to [0.11.0] - 2026-06-28, rolling up the four changes merged
since v0.10.1:

- #14 S1: harden RelayHttpRequest with path/method gate + forbidden-header
  helpers (isForbiddenHeader/withoutForbiddenHeaders/assertSafe)
- #15 JwtClaims: strict-aud variant (fromPayloadStrict) + verification/
  round-trip docs (S4+B4)
- #16 refactor(arr): extract AbstractArrClient to dedup the four *arr
  clients (F2a)
- #17 feat(arr): inject async HTTP transport via ArrTransportInterface
  (F2b/B1/P1)

composer.json carries no hardcoded version field (tags drive version),
so it is left unchanged. The git tag is applied by the coordinator
after merge.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant