Skip to content

Accelerator: Spectrum sidecar client

The Accelerator module is the PHP-side client for Spectrum, the optional out-of-process acceleration sidecar. It is a hardened Hypertext Transfer Protocol (HTTP) client with a circuit breaker, JSON Web Token (JWT) capability tokens, one retry for transient faults, and a server-sent events transport for streamed job progress. The engine works without the sidecar. Use this module to inject acceleration without making it required.

Stability: experimental. Spectrum is an optional sidecar, not a frozen public application programming interface (API). The SpectrumInterface it implements is documented as experimental on Contracts / Observability. This client follows that tier. The transport, token format, and budget shape may change between minor versions.

Terminal window
composer require nextpdf/core:^3

Spectrum offloads processor-heavy work to a local sidecar process, including hardware detection, Portable Document Format (PDF) parsing, and image compression. SpectrumClient is a PHP Standards Recommendation 18 (PSR-18) client that implements the frozen NextPDF\Contracts\SpectrumInterface. It depends on a ClientInterface, a RequestFactoryInterface, and a StreamFactoryInterface, not on a hard-wired Hypertext Transfer Protocol (HTTP) stack.

The client assumes the dependency can fail. A circuit breaker opens after three consecutive failures. While it is open, isAvailable() returns false for an exponential backoff window, so a hot path does not keep calling an unavailable sidecar. The probe result is cached with a time-to-live (TTL). When you configure an app secret, every outbound request carries a Request Capability Token. The token is a short-lived HS256 JWT scoped to the endpoint’s required capabilities. Its lifetime is 120 seconds, or 30 seconds in high-control authorization mode. Transient 5xx and timeout errors are retried once. Authentication and parse errors are never retried.

SspectrumClient keeps state per instance. Circuit-breaker state and the probe cache are not shared. Each PHP FastCGI Process Manager (PHP-FPM) worker should hold its own instance. Batch results are typed. BatchResult carries BatchItem entries with a BatchItemStatus, a BatchSummary with a success rate, and an optional trace ID. HardwareReport and HardwareCapabilities describe the detected hardware tier. HardwareCapabilities::satisfies() checks a tier requirement programmatically. The DegradePolicy and AuthorizationMode enums control capability-loss behavior and token strictness. The whole module is @since 2.1.0.

TypeKey membersRole
SpectrumClientisAvailable(), probe(), getBudget(), request()PSR-18 sidecar client that implements SpectrumInterface
BatchResultgetItems(), getSummary(), filterByStatus(), traceId()Typed batch outcome
BatchItem / BatchItemStatusisOk(), isSuccessful(), isRetryable()Per-item result and status enum
BatchSummaryisFullSuccess(), successRate()Aggregate batch summary
HardwareReporthasPro(), hasEnterprise(), isApiVersionCompatible()Detected sidecar capabilities
HardwareCapabilitieshasGpu(), satisfies(), bestAvailableTier()Programmatic capability branching
DegradePolicy / AuthorizationModeenum casesDegradation behavior and token strictness

Run composer docs:generate-api-php -- --module=Accelerator to generate the full PHPDoc table.

Probe the sidecar through the circuit breaker before you rely on it.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Contracts\SpectrumInterface;
function describeAccelerator(SpectrumInterface $spectrum): string
{
if ($spectrum->isAvailable() !== true) {
return 'Accelerator unavailable; engine runs in pure-PHP mode.';
}
$report = $spectrum->probe();
return $report->hasEnterprise() ? 'Enterprise accelerator tier active.' : 'Standard accelerator tier active.';
}

Send a batch through the sidecar when it is healthy, and fall back under the degradation policy when it is not.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Accelerator\DegradePolicy;
use NextPDF\Contracts\SpectrumInterface;
use Psr\Log\LoggerInterface;
final readonly class AcceleratedCompressor
{
public function __construct(
private ?SpectrumInterface $spectrum,
private DegradePolicy $policy,
private LoggerInterface $logger,
) {}
/** @param list<array{id: string, data: string}> $images @return string Raw sidecar body. */
public function compress(array $images): string
{
if ($this->spectrum?->isAvailable() === true) {
return $this->spectrum->request('POST', '/v1/compress', json: ['images' => $images], scope: ['compress']);
}
if ($this->policy === DegradePolicy::Strict) {
throw new \RuntimeException('Accelerator required under the strict degrade policy.');
}
$this->logger->info('Spectrum unavailable; using PHP image path.');
return '';
}
}
  • isAvailable() is a point-in-time, circuit-broken check. A true result can become false before the next call. Handle a sidecar that drops between calls.
  • State for the circuit breaker and probe cache is per instance. Sharing one SpectrumClient across workers defeats the breaker. Give each worker its own instance.
  • Capability tokens are short-lived (120 s, 30 s in high-control mode). A long-running operation must obtain a fresh token, not reuse one.
  • Authentication and parse errors are never retried; only transient 5xx responses and timeouts are, and only once. Do not assume idempotent retry beyond that.
  • A null SpectrumInterface is a valid “no accelerator” state, not an error. The degrade policy decides whether that is fatal.

The client adds negligible overhead; the sidecar does the work. The circuit breaker is the main reliability control. It limits wasted round-trips when the sidecar is unavailable. The performance_budget of 1500 ms wall / 64 MB peak is the engine’s reference workload, not a sidecar service-level agreement (SLA). The reproducibility profile is structural. A batch result carries a trace ID and timestamps, so two runs differ in those fields.

The sidecar boundary is a trust boundary. When you configure an app secret, requests carry HS256 capability tokens scoped to the endpoint. Treat that secret as a credential from a secret manager, and never commit it. High-control authorization mode shortens token lifetime to 30 seconds for sensitive endpoints. The operator-supplied sidecar Uniform Resource Locator (URL) is validated when the configuration is constructed, not at first request: only http:// and https:// with a non-empty host, or unix:// with a non-empty socket path, are accepted; any other scheme (gopher://, file://, ftp://, …) or a network URL without a host fails closed at construction. This is defense-in-depth against server-side request forgery (SSRF) and unexpected egress, so a misconfigured endpoint never reaches the HTTP client. Sidecar responses are external data. Validate them and treat them as untrusted before they re-enter the engine. The legal-review-required export-control class on this page reflects that the acceleration feature carries cryptographic transport and is pending an export-control review. Consult that review before you redistribute a build that enables it.

This module makes no normative PDF-specification claim. It is an HTTP client for an internal acceleration protocol. The protocol is engine-defined, not standardized, so there are no clauses to cite here. Where sidecar work (PDF parsing, compression) has a conformance dimension, that conformance is documented on the relevant module page and validated by the oracle and golden suites in /modules/core/conformance/.