Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.55% covered (success)
93.55%
29 / 31
77.78% covered (success)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractHandler
93.55% covered (success)
93.55%
29 / 31
77.78% covered (success)
77.78%
7 / 9
26.18
0.00% covered (danger)
0.00%
0 / 1
 hasResource
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getResource
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasUri
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUri
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHttpVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUriObject
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 resource
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 send
n/a
0 / 0
n/a
0 / 0
0
 reset
n/a
0 / 0
n/a
0 / 0
0
 disconnect
n/a
0 / 0
n/a
0 / 0
0
 collectRequestHeaders
88.89% covered (success)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
8.09
 resolveRequestBody
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
11.04
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Http\Client\Handler;
16
17use Pop\Http\Auth;
18use Pop\Http\Client\Request;
19use Pop\Http\Client\Response;
20use Pop\Http\Uri;
21
22/**
23 * HTTP client handler interface
24 *
25 * @category   Pop
26 * @package    Pop\Http
27 * @author     Nick Sagona, III <nick@popphp.org>
28 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
29 * @license    https://www.popphp.org/license     New BSD License
30 * @version    6.0.0
31 */
32abstract class AbstractHandler implements HandlerInterface
33{
34
35    /**
36     * URI string
37     * @var ?string
38     */
39    protected ?string $uri = null;
40
41    /**
42     * HTTP version
43     * @var string
44     */
45    protected string $httpVersion = '1.1';
46
47    /**
48     * Client resource object
49     * @var mixed
50     */
51    protected mixed $resource = null;
52
53    /**
54     * The request currently being (or last) prepared, carried so a thrown
55     * Client\Handler\Exception can implement NetworkExceptionInterface::getRequest()
56     * @var ?Request
57     */
58    protected ?Request $request = null;
59
60    /**
61     * Determine whether or not resource is available
62     *
63     * @return bool
64     */
65    public function hasResource(): bool
66    {
67        return ($this->resource !== null);
68    }
69
70    /**
71     * Get the resource
72     *
73     * @return mixed
74     */
75    public function getResource(): mixed
76    {
77        return $this->resource;
78    }
79
80    /**
81     * Determine whether or not there is a URI string
82     *
83     * @return bool
84     */
85    public function hasUri(): bool
86    {
87        return ($this->uri !== null);
88    }
89
90    /**
91     * Get the URI string
92     *
93     * @return ?string
94     */
95    public function getUri(): ?string
96    {
97        return $this->uri;
98    }
99
100    /**
101     * Get the URI string
102     *
103     * @return string
104     */
105    public function getHttpVersion(): string
106    {
107        return $this->httpVersion;
108    }
109
110    /**
111     * Get the URI as an object
112     *
113     * @return Uri
114     */
115    public function getUriObject(): Uri
116    {
117        return new Uri($this->uri);
118    }
119
120    /**
121     * Get the resource (alias method)
122     *
123     * @return mixed
124     */
125    public function resource(): mixed
126    {
127        return $this->resource;
128    }
129
130    /**
131     * Method to send the request
132     */
133    abstract public function send();
134
135    /**
136     * Method to reset the handler
137     *
138     * @return AbstractHandler
139     */
140    abstract public function reset(): AbstractHandler;
141
142    /**
143     * Close the handler connection
144     *
145     * @return void
146     */
147    abstract public function disconnect(): void;
148
149    /**
150     * Collect the flat list of header strings for a request, shared by every handler
151     *
152     * @param  \Pop\Http\Client\Request $request
153     * @return array
154     */
155    protected function collectRequestHeaders(\Pop\Http\Client\Request $request): array
156    {
157        $headers = [];
158
159        if (!$request->hasHeaders()) {
160            return $headers;
161        }
162
163        foreach ($request->getHeaderObjects() as $header => $value) {
164            if (($header === 'Content-Length') && ($request->isNoContentLength() || ($request->getMethod() === 'GET'))) {
165                continue;
166            }
167            foreach (is_array($value) ? $value : [$value] as $val) {
168                $headers[] = (string)$val;
169            }
170        }
171
172        return $headers;
173    }
174
175    /**
176     * Resolve what a request's outbound body should be, shared by every handler.
177     * Multipart requests resolve to the curl-native array shape (scalars + CURLFile),
178     * which Curl can pass directly to CURLOPT_POSTFIELDS for zero-copy file streaming;
179     * handlers without that capability (Stream) fall back to Body\Multipart::build().
180     *
181     * @param  \Pop\Http\Client\Request $request
182     * @return array
183     */
184    protected function resolveRequestBody(\Pop\Http\Client\Request $request): array
185    {
186        $queryString = null;
187        $body        = null;
188
189        if ($request->hasQuery()) {
190            $queryString = '?' . $request->getQuery()->prepare()->getDataContent();
191        }
192
193        if ($request->hasData()) {
194            if ($request->isMultipart()) {
195                $body = \Pop\Http\Body\Multipart::toArray($request->getData()->getData());
196            } else if ($request->hasDataContent()) {
197                if (($queryString === null) && ($request->getMethod() === 'GET') && (!$request->hasRequestType() || $request->isUrlEncoded())) {
198                    $queryString = '?' . $request->getDataContent();
199                } else {
200                    $body = $request->useRawData() ? $request->getData()->getData() : $request->getDataContent();
201                }
202            }
203        } else if ($request->hasBodyContent()) {
204            $request->addHeader('Content-Length', (string)$request->getBodyContentLength());
205            $body = $request->getBodyContent();
206        }
207
208        return ['queryString' => $queryString, 'body' => $body];
209    }
210
211}