Passed
Push — 1.x ( ecbb08...2b6309 )
by Kevin
02:09
created

HttpOptions::asAjax()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 5
rs 10
1
<?php
2
3
namespace Zenstruck\Browser;
4
5
/**
6
 * @author Kevin Bond <[email protected]>
7
 */
8
class HttpOptions
9
{
10
    private const EMPTY_JSON_TRIGGER = '__JSON__';
11
    private const DEFAULT_OPTIONS = [
12
        // request headers
13
        'headers' => [],
14
15
        // query parameters
16
        'query' => [],
17
18
        // files to include
19
        'files' => [],
20
21
        // server variables
22
        'server' => [],
23
24
        // request body
25
        'body' => null,
26
27
        // if set, will json_encode and use as the body and
28
        // set the Content-Type/Accept request headers to application/json
29
        'json' => null,
30
31
        // if true will set the X-Requested-With request header to XMLHttpRequest
32
        'ajax' => false,
33
    ];
34
35
    private array $options;
36
37
    final public function __construct(array $options = [])
38
    {
39
        $this->options = \array_merge(self::DEFAULT_OPTIONS, $options);
40
    }
41
42
    /**
43
     * @param self|array $options
44
     *
45
     * @return static
46
     */
47
    final public static function create($options = []): self
48
    {
49
        if ($options instanceof static) {
50
            return $options;
51
        }
52
53
        return new static($options);
54
    }
55
56
    /**
57
     * @return static
58
     */
59
    final public static function json($body = null): self
60
    {
61
        return static::create()->asJson($body);
62
    }
63
64
    /**
65
     * @return static
66
     */
67
    final public static function ajax(): self
68
    {
69
        return static::create()->asAjax();
70
    }
71
72
    /**
73
     * @return static
74
     */
75
    final public static function jsonAjax($body = null): self
76
    {
77
        return static::json($body)->asAjax();
78
    }
79
80
    /**
81
     * @param self|array $options
82
     *
83
     * @return static
84
     */
85
    final public function merge($options = []): self
86
    {
87
        $other = self::create($options);
88
89
        // merge array options
90
        $this->options['headers'] = \array_merge($this->options['headers'], $other->options['headers']);
91
        $this->options['query'] = \array_merge($this->options['query'], $other->options['query']);
92
        $this->options['files'] = \array_merge($this->options['files'], $other->options['files']);
93
        $this->options['server'] = \array_merge($this->options['server'], $other->options['server']);
94
95
        // override value options only if different from default
96
        if ($other->options['body'] !== self::DEFAULT_OPTIONS['body']) {
97
            $this->options['body'] = $other->options['body'];
98
        }
99
100
        if ($other->options['json'] !== self::DEFAULT_OPTIONS['json']) {
101
            $this->options['json'] = $other->options['json'];
102
        }
103
104
        if ($other->options['ajax'] !== self::DEFAULT_OPTIONS['ajax']) {
105
            $this->options['ajax'] = $other->options['ajax'];
106
        }
107
108
        return $this;
109
    }
110
111
    /**
112
     * @return static
113
     */
114
    final public function withHeader(string $header, string $value): self
115
    {
116
        $this->options['headers'][$header] = $value;
117
118
        return $this;
119
    }
120
121
    /**
122
     * @return static
123
     */
124
    final public function withHeaders(array $headers): self
125
    {
126
        $this->options['headers'] = $headers;
127
128
        return $this;
129
    }
130
131
    /**
132
     * @return static
133
     */
134
    final public function withQuery(array $query): self
135
    {
136
        $this->options['query'] = $query;
137
138
        return $this;
139
    }
140
141
    /**
142
     * @return static
143
     */
144
    final public function withServer(array $server): self
145
    {
146
        $this->options['server'] = $server;
147
148
        return $this;
149
    }
150
151
    /**
152
     * @return static
153
     */
154
    final public function withFiles(array $files): self
155
    {
156
        $this->options['files'] = $files;
157
158
        return $this;
159
    }
160
161
    /**
162
     * @param string|array|null $body
163
     *
164
     * @return static
165
     */
166
    final public function withBody($body): self
167
    {
168
        $this->options['body'] = $body;
169
170
        return $this;
171
    }
172
173
    /**
174
     * @param mixed $body Any value that can be json encoded
175
     *
176
     * @return static
177
     */
178
    final public function asJson($body = null): self
179
    {
180
        $this->options['json'] = $body ?? self::EMPTY_JSON_TRIGGER;
181
182
        return $this;
183
    }
184
185
    /**
186
     * @return static
187
     */
188
    final public function asAjax(): self
189
    {
190
        $this->options['ajax'] = true;
191
192
        return $this;
193
    }
194
195
    final public function addQueryToUrl(string $url): string
196
    {
197
        $parts = \parse_url($url);
198
199
        if (isset($parts['query'])) {
200
            \parse_str($parts['query'], $query);
201
        } else {
202
            $query = [];
203
        }
204
205
        // merge query on url with the query option
206
        $parts['query'] = \http_build_query(\array_merge($query, $this->options['query']));
207
208
        $scheme = isset($parts['scheme']) ? $parts['scheme'].'://' : '';
209
        $host = $parts['host'] ?? '';
210
        $port = isset($parts['port']) ? ':'.$parts['port'] : '';
211
        $user = $parts['user'] ?? '';
212
        $pass = isset($parts['pass']) ? ':'.$parts['pass'] : '';
213
        $pass = ($user || $pass) ? "{$pass}@" : '';
214
        $path = $parts['path'] ?? '';
215
        $query = isset($parts['query']) && $parts['query'] ? '?'.$parts['query'] : '';
216
        $fragment = isset($parts['fragment']) ? '#'.$parts['fragment'] : '';
217
218
        return $scheme.$user.$pass.$host.$port.$path.$query.$fragment;
219
    }
220
221
    /**
222
     * @internal
223
     */
224
    final public function parameters(): array
225
    {
226
        // when body is array, use as request parameters
227
        return \is_array($this->options['body']) ? $this->options['body'] : [];
228
    }
229
230
    /**
231
     * @internal
232
     */
233
    final public function files(): array
234
    {
235
        return $this->options['files'];
236
    }
237
238
    /**
239
     * @co-author Kévin Dunglas <[email protected]>
240
     *
241
     * @internal
242
     */
243
    final public function server(): array
244
    {
245
        $server = $this->options['server'];
246
        $headers = \array_combine(
247
            \array_map(
248
                static fn($header) => \mb_strtoupper(\str_replace('-', '_', $header)),
249
                \array_keys($this->options['headers'])
250
            ),
251
            $this->options['headers']
252
        );
253
254
        if (null !== $this->options['json'] && !\array_key_exists('ACCEPT', $headers)) {
255
            $headers['ACCEPT'] = 'application/json';
256
        }
257
258
        if (null !== $this->options['json'] && !\array_key_exists('CONTENT_TYPE', $headers)) {
259
            $headers['CONTENT_TYPE'] = 'application/json';
260
        }
261
262
        if (false !== $this->options['ajax'] && !\array_key_exists('X_REQUESTED_WITH', $headers)) {
263
            $headers['X_REQUESTED_WITH'] = 'XMLHttpRequest';
264
        }
265
266
        foreach ($headers as $header => $value) {
267
            // content type header cannot have HTTP_ prefix
268
            if ('CONTENT_TYPE' !== $header) {
269
                $header = "HTTP_{$header}";
270
            }
271
272
            $server[$header] = $value;
273
        }
274
275
        return $server;
276
    }
277
278
    /**
279
     * @internal
280
     */
281
    final public function body(): ?string
282
    {
283
        if (\is_array($this->options['body'])) {
284
            // when body is array, it's used as the request parameters
285
            return null;
286
        }
287
288
        if (null === $this->options['json']) {
289
            return $this->options['body'];
290
        }
291
292
        if (self::EMPTY_JSON_TRIGGER === $this->options['json']) {
293
            return null;
294
        }
295
296
        return \json_encode($this->options['json'], \JSON_THROW_ON_ERROR);
297
    }
298
}
299