Completed
Push — master ( d402f5...2264d7 )
by Anton
16s queued 13s
created

InputManager::getValue()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
9
namespace Spiral\Http\Request;
10
11
use Interop\Container\ContainerInterface;
12
use Psr\Http\Message\ServerRequestInterface as Request;
13
use Psr\Http\Message\UploadedFileInterface;
14
use Psr\Http\Message\UriInterface;
15
use Spiral\Core\Container\SingletonInterface;
16
use Spiral\Core\Exceptions\Container\ContainerException;
17
use Spiral\Core\Exceptions\ScopeException;
18
use Spiral\Http\Exceptions\InputException;
19
use Spiral\Http\Request\Bags\FilesBag;
20
use Spiral\Http\Request\Bags\HeadersBag;
21
use Spiral\Http\Request\Bags\InputBag;
22
use Spiral\Http\Request\Bags\ServerBag;
23
24
/**
25
 * Provides simplistic way to access request input data in controllers and can also be used to
26
 * populate RequestFilters.
27
 *
28
 * Attention, this class is singleton based, it reads request from current active container scope!
29
 *
30
 * Technically this class can be made as middleware, but due spiral provides container scoping
31
 * such functionality may be replaces with simple container request routing.
32
 *
33
 * @property-read HeadersBag $headers
34
 * @property-read InputBag   $data
35
 * @property-read InputBag   $query
36
 * @property-read InputBag   $cookies
37
 * @property-read FilesBag   $files
38
 * @property-read ServerBag  $server
39
 * @property-read InputBag   $attributes
40
 */
41
class InputManager implements InputInterface, SingletonInterface
42
{
43
    /**
44
     * @var InputBag[]
45
     */
46
    private $bagInstances = [];
47
48
    /**
49
     * Prefix to add for each input request.
50
     *
51
     * @see self::withPrefix();
52
     *
53
     * @var string
54
     */
55
    private $prefix = '';
56
57
    /**
58
     * Associations between bags and representing class/request method.
59
     *
60
     * @invisible
61
     * @var array
62
     */
63
    protected $bagAssociations = [
64
        'headers'    => [
65
            'class'  => HeadersBag::class,
66
            'source' => 'getHeaders'
67
        ],
68
        'data'       => [
69
            'class'  => InputBag::class,
70
            'source' => 'getParsedBody'
71
        ],
72
        'query'      => [
73
            'class'  => InputBag::class,
74
            'source' => 'getQueryParams'
75
        ],
76
        'cookies'    => [
77
            'class'  => InputBag::class,
78
            'source' => 'getCookieParams'
79
        ],
80
        'files'      => [
81
            'class'  => FilesBag::class,
82
            'source' => 'getUploadedFiles'
83
        ],
84
        'server'     => [
85
            'class'  => ServerBag::class,
86
            'source' => 'getServerParams'
87
        ],
88
        'attributes' => [
89
            'class'  => InputBag::class,
90
            'source' => 'getAttributes'
91
        ]
92
    ];
93
94
    /**
95
     * @invisible
96
     * @var Request
97
     */
98
    protected $request = null;
99
100
    /**
101
     * @invisible
102
     * @var ContainerInterface
103
     */
104
    protected $container = null;
105
106
    /**
107
     * @param ContainerInterface $container
108
     */
109
    public function __construct(ContainerInterface $container)
110
    {
111
        $this->container = $container;
112
    }
113
114
    /**
115
     * Get active instance of ServerRequestInterface and reset all bags if instance changed.
116
     *
117
     * @return Request
118
     *
119
     * @throws ScopeException
120
     */
121
    public function request(): Request
122
    {
123
        try {
124
            $request = $this->container->get(Request::class);
125
        } catch (ContainerException $e) {
126
            throw new ScopeException(
127
                "Unable to get ServerRequestInterface in active container scope",
128
                $e->getCode(),
129
                $e
130
            );
131
        }
132
133
        //Flushing input state
134
        if ($this->request !== $request) {
135
            $this->bagInstances = [];
136
            $this->request = $request;
137
        }
138
139
        return $this->request;
140
    }
141
142
    /**
143
     * Get UriInterface associated with active request.
144
     *
145
     * @return UriInterface
146
     */
147
    public function uri(): UriInterface
148
    {
149
        return $this->request()->getUri();
150
    }
151
152
    /**
153
     * Get page path (including leading slash) associated with active request.
154
     *
155
     * @return string
156
     */
157
    public function path(): string
158
    {
159
        $path = $this->uri()->getPath();
160
161
        if (empty($path)) {
162
            return '/';
163
        } elseif ($path[0] !== '/') {
164
            return '/' . $path;
165
        }
166
167
        return $path;
168
    }
169
170
    /**
171
     * Http method. Always uppercase.
172
     *
173
     * @return string
174
     */
175
    public function method(): string
176
    {
177
        return strtoupper($this->request()->getMethod());
178
    }
179
180
    /**
181
     * Check if request was made over http protocol.
182
     *
183
     * @return bool
184
     */
185
    public function isSecure(): bool
186
    {
187
        //Double check though attributes?
188
        return $this->request()->getUri()->getScheme() == 'https';
189
    }
190
191
    /**
192
     * Check if request was made using XmlHttpRequest.
193
     *
194
     * @return bool
195
     */
196
    public function isAjax(): bool
197
    {
198
        return strtolower($this->request()->getHeaderLine('X-Requested-With')) == 'xmlhttprequest';
199
    }
200
201
    /**
202
     * Client requesting json response by Accept header.
203
     *
204
     * @return bool
205
     */
206
    public function isJsonExpected(): bool
207
    {
208
        return $this->request()->getHeaderLine('Accept') == 'application/json';
209
    }
210
211
    /**
212
     * Get remove addr resolved from $_SERVER['REMOTE_ADDR']. Will return null if nothing if key not
213
     * exists. Consider using psr-7 middlewares to customize configuration.
214
     *
215
     * @return string|null
216
     */
217
    public function remoteAddress()
218
    {
219
        $serverParams = $this->request()->getServerParams();
220
221
        return isset($serverParams['REMOTE_ADDR']) ? $serverParams['REMOTE_ADDR'] : null;
222
    }
223
224
    /**
225
     * Get bag instance or create new one on demand.
226
     *
227
     * @param string $name
228
     *
229
     * @return InputBag
230
     */
231
    public function bag(string $name): InputBag
232
    {
233
        // ensure proper request association
234
        $this->request();
235
        
236
        if (isset($this->bagInstances[$name])) {
237
            return $this->bagInstances[$name];
238
        }
239
240
        if (!isset($this->bagAssociations[$name])) {
241
            throw new InputException("Undefined input bag '{$name}'");
242
        }
243
244
        $class = $this->bagAssociations[$name]['class'];
245
        $data = call_user_func([$this->request(), $this->bagAssociations[$name]['source']]);
246
247
        if (!is_array($data)) {
248
            $data = (array)$data;
249
        }
250
251
        return $this->bagInstances[$name] = new $class($data, $this->prefix);
252
    }
253
254
    /**
255
     * @param string $name
256
     *
257
     * @return InputBag
258
     */
259
    public function __get(string $name): InputBag
260
    {
261
        return $this->bag($name);
262
    }
263
264
    /**
265
     * @param string      $name
266
     * @param mixed       $default
267
     * @param bool|string $implode Implode header lines, false to return header as array.
268
     *
269
     * @return mixed
270
     */
271
    public function header(string $name, $default = null, $implode = ',')
272
    {
273
        return $this->headers->get($name, $default, $implode);
274
    }
275
276
    /**
277
     * @param string $name
278
     * @param mixed  $default
279
     *
280
     * @return mixed
281
     */
282
    public function data(string $name, $default = null)
283
    {
284
        return $this->data->get($name, $default);
285
    }
286
287
    /**
288
     * @see data()
289
     *
290
     * @param string $name
291
     * @param mixed  $default
292
     *
293
     * @return mixed
294
     */
295
    public function post(string $name, $default = null)
296
    {
297
        return $this->data($name, $default);
298
    }
299
300
    /**
301
     * @param string $name
302
     * @param mixed  $default
303
     *
304
     * @return mixed
305
     */
306
    public function query(string $name, $default = null)
307
    {
308
        return $this->query->get($name, $default);
309
    }
310
311
    /**
312
     * Reads data from data array, if not found query array will be used as fallback.
313
     *
314
     * @param string $name
315
     * @param mixed  $default
316
     *
317
     * @return mixed
318
     */
319
    public function input(string $name, $default = null)
320
    {
321
        return $this->data($name, $this->query($name, $default));
322
    }
323
324
    /**
325
     * @param string $name
326
     * @param mixed  $default
327
     *
328
     * @return mixed
329
     */
330
    public function cookie(string $name, $default = null)
331
    {
332
        return $this->cookies->get($name, $default);
333
    }
334
335
    /**
336
     * @param string $name
337
     * @param mixed  $default
338
     *
339
     * @return UploadedFileInterface|null
340
     */
341
    public function file(string $name, $default = null)
342
    {
343
        return $this->files->get($name, $default);
344
    }
345
346
    /**
347
     * @param string $name
348
     * @param mixed  $default
349
     *
350
     * @return mixed
351
     */
352
    public function server(string $name, $default = null)
353
    {
354
        return $this->server->get($name, $default);
355
    }
356
357
    /**
358
     * @param string $name
359
     * @param mixed  $default
360
     *
361
     * @return mixed
362
     */
363
    public function attribute(string $name, $default = null)
364
    {
365
        return $this->attributes->get($name, $default);
366
    }
367
368
    /**
369
     * Flushing bag instances when cloned.
370
     */
371
    public function __clone()
372
    {
373
        $this->bagInstances = [];
374
    }
375
376
    /**
377
     * {@inheritdoc}
378
     */
379
    public function getValue(string $source, string $name = null)
380
    {
381
        if (!method_exists($this, $source)) {
382
            throw new InputException("Undefined input source '{$source}'");
383
        }
384
385
        return call_user_func([$this, $source], $name);
386
    }
387
388
    /**
389
     * {@inheritdoc}
390
     *
391
     * @return self
392
     */
393
    public function withPrefix(string $prefix, bool $add = true): InputInterface
394
    {
395
        $input = clone $this;
396
397
        if ($add) {
398
            $input->prefix .= '.' . $prefix;
399
            $input->prefix = trim($input->prefix, '.');
400
        } else {
401
            $input->prefix = $prefix;
402
        }
403
404
        return $input;
405
    }
406
}
407