From acc
Generates PSR-18 compliant HTTP client for PHP 8.4 using cURL with request sending, exception handling, and unit tests. For external API integrations, microservices, and HTTP service calls.
How this skill is triggered — by the user, by Claude, or both
Slash command
/acc:create-psr18-http-clientThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Generates PSR-18 compliant HTTP client implementations for external API communication.
Generates PSR-18 compliant HTTP client implementations for external API communication.
<?php
declare(strict_types=1);
namespace App\Infrastructure\Http\Client;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
final readonly class CurlHttpClient implements ClientInterface
{
public function __construct(
private array $options = [],
) {
}
public function sendRequest(RequestInterface $request): ResponseInterface
{
$ch = curl_init();
try {
$this->configureCurl($ch, $request);
$response = curl_exec($ch);
if ($response === false) {
throw new NetworkException(
$request,
curl_error($ch),
curl_errno($ch),
);
}
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerString = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
return $this->buildResponse($statusCode, $headerString, $body);
} finally {
curl_close($ch);
}
}
private function configureCurl($ch, RequestInterface $request): void
{
$options = [
CURLOPT_URL => (string) $request->getUri(),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_TIMEOUT => $this->options['timeout'] ?? 30,
CURLOPT_CONNECTTIMEOUT => $this->options['connect_timeout'] ?? 10,
CURLOPT_CUSTOMREQUEST => $request->getMethod(),
];
// Set headers
$headers = [];
foreach ($request->getHeaders() as $name => $values) {
$headers[] = $name . ': ' . implode(', ', $values);
}
$options[CURLOPT_HTTPHEADER] = $headers;
// Set body
$body = (string) $request->getBody();
if ($body !== '') {
$options[CURLOPT_POSTFIELDS] = $body;
}
// SSL options
if (isset($this->options['verify_ssl']) && !$this->options['verify_ssl']) {
$options[CURLOPT_SSL_VERIFYPEER] = false;
$options[CURLOPT_SSL_VERIFYHOST] = 0;
}
curl_setopt_array($ch, $options);
}
private function buildResponse(int $statusCode, string $headerString, string $body): ResponseInterface
{
$headers = $this->parseHeaders($headerString);
return (new Response($statusCode))
->withBody(new Stream($body))
->withHeaders($headers);
}
private function parseHeaders(string $headerString): array
{
$headers = [];
$lines = explode("\r\n", trim($headerString));
foreach ($lines as $line) {
if (str_contains($line, ':')) {
[$name, $value] = explode(':', $line, 2);
$headers[trim($name)] = [trim($value)];
}
}
return $headers;
}
}
<?php
declare(strict_types=1);
namespace App\Infrastructure\Http\Client;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Client\RequestExceptionInterface;
use Psr\Http\Message\RequestInterface;
final class ClientException extends \RuntimeException implements ClientExceptionInterface
{
}
final class NetworkException extends \RuntimeException implements NetworkExceptionInterface
{
public function __construct(
private readonly RequestInterface $request,
string $message = '',
int $code = 0,
?\Throwable $previous = null,
) {
parent::__construct($message, $code, $previous);
}
public function getRequest(): RequestInterface
{
return $this->request;
}
}
final class RequestException extends \RuntimeException implements RequestExceptionInterface
{
public function __construct(
private readonly RequestInterface $request,
string $message = '',
int $code = 0,
?\Throwable $previous = null,
) {
parent::__construct($message, $code, $previous);
}
public function getRequest(): RequestInterface
{
return $this->request;
}
}
<?php
declare(strict_types=1);
namespace App\Infrastructure\Http\Client;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
final readonly class LoggingHttpClient implements ClientInterface
{
public function __construct(
private ClientInterface $client,
private LoggerInterface $logger,
) {
}
public function sendRequest(RequestInterface $request): ResponseInterface
{
$context = [
'method' => $request->getMethod(),
'uri' => (string) $request->getUri(),
];
$this->logger->info('HTTP request starting', $context);
$startTime = microtime(true);
try {
$response = $this->client->sendRequest($request);
$duration = microtime(true) - $startTime;
$this->logger->info('HTTP request completed', [
...$context,
'status' => $response->getStatusCode(),
'duration_ms' => round($duration * 1000, 2),
]);
return $response;
} catch (\Throwable $e) {
$this->logger->error('HTTP request failed', [
...$context,
'error' => $e->getMessage(),
]);
throw $e;
}
}
}
<?php
use App\Infrastructure\Http\Client\CurlHttpClient;
use App\Infrastructure\Http\Client\LoggingHttpClient;
use App\Infrastructure\Http\Factory\HttpFactory;
$factory = new HttpFactory();
$client = new LoggingHttpClient(
new CurlHttpClient(['timeout' => 30]),
$logger,
);
// Send GET request
$request = $factory->createRequest('GET', 'https://api.example.com/users')
->withHeader('Authorization', 'Bearer token');
$response = $client->sendRequest($request);
$data = json_decode((string) $response->getBody(), true);
// Send POST request
$request = $factory->createRequest('POST', 'https://api.example.com/users')
->withHeader('Content-Type', 'application/json')
->withBody($factory->createStream(json_encode(['name' => 'John'])));
$response = $client->sendRequest($request);
{
"require": {
"psr/http-client": "^1.0",
"psr/http-message": "^2.0"
}
}
npx claudepluginhub dykyi-roman/awesome-claude-code --plugin accGenerates immutable PSR-7 HTTP message classes (Request, Response, ServerRequest, Stream, Uri) for PHP 8.4 with unit tests. For custom HTTP frameworks or lightweight handling.
Configures Laravel HTTP client with timeouts, retries, backoff, logging, and concurrency for resilient outbound API calls. Use for predictable, observable requests.
Configures named, typed, and keyed HTTP clients with IHttpClientFactory in .NET 10, including DelegatingHandlers, resilience pipelines, and testing patterns.