Skip to content

Commit c32600d

Browse files
feat: add HarRecorder PSR-18 client decorator for recording HTTP traffic (#245)
Implement a PSR-18 HTTP client decorator that records request/response pairs to HAR format. This enables generating test fixtures by recording real API interactions. Example usage: ``` $recorder = new HarRecorder($actualHttpClient); $response = $recorder->sendRequest($request); $har = $recorder->getHar(); ``` Features: - Records timing information for each request - Supports custom creator name/version - Provides entry count and reset functionality - Fully serializable HAR output --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent e211e93 commit c32600d

File tree

3 files changed

+404
-0
lines changed

3 files changed

+404
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"jms/serializer": "^3.0",
1818
"doctrine/annotations": "^2.0",
1919
"guzzlehttp/psr7": "^2.0",
20+
"psr/http-client": "^1.0",
2021
"deviantintegral/jms-serializer-uri-handler": "^1.1",
2122
"deviantintegral/null-date-time": "^1.0",
2223
"symfony/console": "^7||^8"

src/HarRecorder.php

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Deviantintegral\Har;
6+
7+
use Psr\Http\Client\ClientInterface;
8+
use Psr\Http\Message\RequestInterface;
9+
use Psr\Http\Message\ResponseInterface;
10+
11+
/**
12+
* A PSR-18 HTTP client decorator that records request/response traffic to HAR format.
13+
*
14+
* Use case: Generate test fixtures by recording real API interactions.
15+
*
16+
* Example usage:
17+
* $recorder = new HarRecorder($actualHttpClient);
18+
* $response = $recorder->sendRequest($request); // Makes real request
19+
* $har = $recorder->getHar(); // Get recorded traffic
20+
*/
21+
final class HarRecorder implements ClientInterface
22+
{
23+
/**
24+
* @var Entry[]
25+
*/
26+
private array $entries = [];
27+
28+
private Creator $creator;
29+
30+
/**
31+
* @param ClientInterface $client The underlying HTTP client to delegate requests to
32+
* @param string $creatorName Name of the application creating the HAR
33+
* @param string $creatorVersion Version of the application
34+
*/
35+
public function __construct(
36+
private readonly ClientInterface $client,
37+
string $creatorName = 'deviantintegral/har',
38+
string $creatorVersion = '1.0',
39+
) {
40+
$this->creator = (new Creator())
41+
->setName($creatorName)
42+
->setVersion($creatorVersion);
43+
}
44+
45+
/**
46+
* Send an HTTP request and record the request/response pair.
47+
*
48+
* @throws \Psr\Http\Client\ClientExceptionInterface
49+
*/
50+
public function sendRequest(RequestInterface $request): ResponseInterface
51+
{
52+
$startTime = hrtime(true);
53+
$startDateTime = new \DateTime();
54+
55+
$response = $this->client->sendRequest($request);
56+
57+
$endTime = hrtime(true);
58+
/** @infection-ignore-all Equivalent mutant: 1 part per million difference is not testable */
59+
$totalTimeMs = ($endTime - $startTime) / 1_000_000;
60+
61+
$entry = $this->createEntry($request, $response, $startDateTime, $totalTimeMs);
62+
$this->entries[] = $entry;
63+
64+
return $response;
65+
}
66+
67+
/**
68+
* Get the recorded traffic as a HAR object.
69+
*/
70+
public function getHar(): Har
71+
{
72+
$log = (new Log())
73+
->setVersion('1.2')
74+
->setCreator($this->creator)
75+
->setEntries($this->entries);
76+
77+
return (new Har())->setLog($log);
78+
}
79+
80+
/**
81+
* Reset and clear all recorded entries.
82+
*/
83+
public function reset(): void
84+
{
85+
$this->entries = [];
86+
}
87+
88+
/**
89+
* Create a HAR entry from the request/response pair.
90+
*/
91+
private function createEntry(
92+
RequestInterface $request,
93+
ResponseInterface $response,
94+
\DateTime $startDateTime,
95+
float $totalTimeMs,
96+
): Entry {
97+
$harRequest = Request::fromPsr7Request($request);
98+
$harResponse = Response::fromPsr7Response($response);
99+
100+
$timings = (new Timings())
101+
->setSend(0)
102+
->setWait($totalTimeMs)
103+
->setReceive(0);
104+
105+
return (new Entry())
106+
->setStartedDateTime($startDateTime)
107+
->setTime($totalTimeMs)
108+
->setRequest($harRequest)
109+
->setResponse($harResponse)
110+
->setCache(new Cache())
111+
->setTimings($timings);
112+
}
113+
}

0 commit comments

Comments
 (0)