Lines 79.41% 27 / 34
Methods 62.50% 5 / 8
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 createRequest 100.00% 2 / 2 100.00% 1 / 1 2
 createResponse 100.00% 2 / 2 100.00% 1 / 1 1
 createServerRequest 100.00% 5 / 5 100.00% 1 / 1 2
 createStream 85.71% 6 / 7 0.00% 0 / 1 3.03
 createStreamFromFile 50.00% 4 / 8 0.00% 0 / 1 4.12
 createStreamFromResource 66.66% 4 / 6 0.00% 0 / 1 3.33
 createUploadedFile 100.00% 3 / 3 100.00% 1 / 1 2
 createUri 100.00% 1 / 1 100.00% 1 / 1 1
25final class HttpFactory implements
26    RequestFactoryInterface,
27    \Psr\Http\Message\ResponseFactoryInterface,
28    \Psr\Http\Message\ServerRequestFactoryInterface,
29    \Psr\Http\Message\StreamFactoryInterface,
30    \Psr\Http\Message\UploadedFileFactoryInterface,
31    \Psr\Http\Message\UriFactoryInterface
32{
33    // ─── RequestFactoryInterface ────────────────────────────────────────
34
35    /**
36     * Create a new request.
37     *
38     * @param string $method HTTP method (e.g., GET, POST)
39     * @param string|UriInterface $uri URI string or object
40     * @return RequestInterface
41     */
42    public function createRequest(string $method, $uri): RequestInterface
43    {
44        $uriString = $uri instanceof UriInterface ? $uri : Uri::fromString($uri);
45        return new Request($method, $uriString);
46    }
47
48    // ─── ResponseFactoryInterface ───────────────────────────────────────
49
50    /**
51     * Create a new response.
52     *
53     * @param int $code HTTP status code
54     * @param string $reasonPhrase Reason phrase; empty string uses the default for the status code
55     * @return ResponseInterface
56     */
57    public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
58    {
59        return new Response()
60            ->withStatus($code, $reasonPhrase);
61    }
62
63    // ─── ServerRequestFactoryInterface ──────────────────────────────────
64
65    /**
66     * Create a new server request.
67     *
68     * @param string $method HTTP method (e.g., GET, POST)
69     * @param string|UriInterface $uri URI string or object
70     * @param array $serverParams Server parameters ($_SERVER)
71     * @return ServerRequestInterface
72     */
73    public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
74    {
75        return ServerRequest::create(
76            $method,
77            $uri instanceof UriInterface ? $uri : (string) $uri,
78            server: $serverParams
79        );
80    }
81
82    // ─── StreamFactoryInterface ─────────────────────────────────────────
83
84    /**
85     * Create a new stream from a string.
86     *
87     * The stream is created with a temporary resource.
88     *
89     * @param string $content String content with which to populate the stream
90     * @return StreamInterface
91     */
92    public function createStream(string $content = ''): StreamInterface
93    {
94        $resource = fopen('php://temp', 'w+');
95        if ($resource === false) {
96            throw new \RuntimeException('Unable to create temporary stream');
97        }
98        if ($content !== '') {
99            fwrite($resource, $content);
100            rewind($resource);
101        }
102        return Stream::fromResource($resource);
103    }
104
105    /**
106     * Create a stream from a file.
107     *
108     * @param string $filename Filesystem path or stream URI
109     * @param string $mode Mode with which to open the file (see fopen)
110     * @return StreamInterface
111     * @throws \RuntimeException If the file cannot be opened
112     * @throws \InvalidArgumentException If the mode is invalid
113     */
114    public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
115    {
116        // Mirror PHP's fopen mode grammar: a base letter (r/w/a/x/c) followed
117        // by any combination of the +, b, e, and t flags (e.g. rb, r+b, wb).
118        if (!preg_match('/^[rwaxc][+bet]*$/', $mode)) {
119            throw new \InvalidArgumentException("Invalid file mode: $mode");
120        }
121
122        $resource = @fopen($filename, $mode);
123        if ($resource === false) {
124            $error = error_get_last();
125            $reason = $error['message'] ?? 'unknown error';
126            throw new \RuntimeException("Unable to open file: $filename ($reason)");
127        }
128        return Stream::fromResource($resource);
129    }
130
131    /**
132     * Create a stream from an existing resource.
133     *
134     * The stream MUST be readable and may be writable.
135     *
136     * @param resource $resource PHP stream resource
137     * @return StreamInterface
138     * @throws \InvalidArgumentException If the resource is not a valid readable stream
139     */
140    public function createStreamFromResource($resource): StreamInterface
141    {
142        if (!is_resource($resource)) {
143            throw new \InvalidArgumentException('Invalid stream resource provided');
144        }
145
146        $stream = Stream::fromResource($resource);
147        if (!$stream->isReadable()) {
148            throw new \InvalidArgumentException('Stream resource must be readable');
149        }
150
151        return $stream;
152    }
153
154    // ─── UploadedFileFactoryInterface ───────────────────────────────────
155
156    /**
157     * Create a new uploaded file.
158     *
159     * @param StreamInterface $stream Stream containing the uploaded file data
160     * @param int|null $size File size in bytes
161     * @param int $error Upload error code (UPLOAD_ERR_* constant)
162     * @param string|null $clientFilename Original client filename
163     * @param string|null $clientMediaType Original client media type
164     * @return UploadedFileInterface
165     * @throws \InvalidArgumentException If the stream is not readable (enforced
166     *     by the UploadedFile constructor)
167     */
168    public function createUploadedFile(
169        StreamInterface $stream,
170        ?int $size = null,
171        int $error = \UPLOAD_ERR_OK,
172        ?string $clientFilename = null,
173        ?string $clientMediaType = null
174    ): UploadedFileInterface {
175        // If a size is not provided it is determined by
176        // checking the size of the stream.
177        if ($size === null) {
178            $size = $stream->getSize();
179        }
180
181        return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
182    }
183
184    // ─── UriFactoryInterface ────────────────────────────────────────────
185
186    /**
187     * Create a new URI from a string.
188     *
189     * @param string $uri URI string (e.g., https://example.com/path?query=1)
190     * @return UriInterface
191     * @throws \InvalidArgumentException If the URI cannot be parsed
192     */
193    public function createUri(string $uri = ''): UriInterface
194    {
195        return Uri::fromString($uri);
196    }
197}