Completed
Pull Request — master (#106)
by Alex
01:22
created

PSR7Client::getWorker()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * High-performance PHP process supervisor and load balancer written in Go
6
 *
7
 * @author Wolfy-J
8
 */
9
10
namespace Spiral\RoadRunner;
11
12
use Psr\Http\Message\ResponseInterface;
13
use Psr\Http\Message\ServerRequestFactoryInterface;
14
use Psr\Http\Message\ServerRequestInterface;
15
use Psr\Http\Message\StreamFactoryInterface;
16
use Psr\Http\Message\UploadedFileFactoryInterface;
17
18
/**
19
 * Manages PSR-7 request and response.
20
 */
21
class PSR7Client
22
{
23
    /** @var HttpClient */
24
    private $httpClient;
25
26
    /** @var ServerRequestFactoryInterface */
27
    private $requestFactory;
28
29
    /** @var StreamFactoryInterface */
30
    private $streamFactory;
31
32
    /*** @var UploadedFileFactoryInterface */
33
    private $uploadsFactory;
34
35
    private $originalServer = [];
36
37
    /** @var array Valid values for HTTP protocol version */
38
    private static $allowedVersions = ['1.0', '1.1', '2',];
39
40
    /**
41
     * @param Worker $worker
42
     * @param ServerRequestFactoryInterface|null $requestFactory
43
     * @param StreamFactoryInterface|null $streamFactory
44
     * @param UploadedFileFactoryInterface|null $uploadsFactory
45
     */
46
    public function __construct(
47
        Worker $worker,
48
        ServerRequestFactoryInterface $requestFactory = null,
49
        StreamFactoryInterface $streamFactory = null,
50
        UploadedFileFactoryInterface $uploadsFactory = null
51
    )
52
    {
53
        $this->httpClient = new HttpClient($worker);
54
        $this->requestFactory = $requestFactory ?? new Diactoros\ServerRequestFactory();
55
        $this->streamFactory = $streamFactory ?? new Diactoros\StreamFactory();
56
        $this->uploadsFactory = $uploadsFactory ?? new Diactoros\UploadedFileFactory();
57
        $this->originalServer = $_SERVER;
58
    }
59
60
    /**
61
     * @return ServerRequestInterface|null
62
     */
63
    public function acceptRequest()
64
    {
65
        $rawRequest = $this->httpClient->acceptRequest();
66
        if ($rawRequest === null)
67
            return null;
68
69
        list($ctx, $body) = $rawRequest;
70
71
        $_SERVER = $this->configureServer($ctx);
72
73
        $request = $this->requestFactory->createServerRequest(
74
            $ctx['method'],
75
            $ctx['uri'],
76
            $_SERVER
77
        );
78
79
        parse_str($ctx['rawQuery'], $query);
80
81
        $request = $request
82
            ->withProtocolVersion(static::fetchProtocolVersion($ctx['protocol']))
0 ignored issues
show
Bug introduced by
Since fetchProtocolVersion() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of fetchProtocolVersion() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
83
            ->withCookieParams($ctx['cookies'])
84
            ->withQueryParams($query)
0 ignored issues
show
Bug introduced by
It seems like $query can also be of type null; however, Psr\Http\Message\ServerR...face::withQueryParams() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
85
            ->withUploadedFiles($this->wrapUploads($ctx['uploads']));
86
87
        foreach ($ctx['attributes'] as $name => $value) {
88
            $request = $request->withAttribute($name, $value);
89
        }
90
91
        foreach ($ctx['headers'] as $name => $value) {
92
            $request = $request->withHeader($name, $value);
93
        }
94
95
        if ($ctx['parsed']) {
96
            $request = $request->withParsedBody(json_decode($body, true));
97
        } else {
98
            if ($body !== null) {
99
                $request = $request->withBody($this->streamFactory->createStream($body));
100
            }
101
        }
102
103
        return $request;
104
    }
105
106
    /**
107
     * Send response to the application server.
108
     *
109
     * @param ResponseInterface $response
110
     */
111
    public function respond(ResponseInterface $response)
112
    {
113
        $this->httpClient->respond($response->getBody()->__toString(), $response->getStatusCode(), $response->getHeaders());
114
    }
115
116
    /**
117
     * Returns altered copy of _SERVER variable. Sets ip-address,
118
     * request-time and other values.
119
     *
120
     * @param array $ctx
121
     * @return array
122
     */
123
    protected function configureServer(array $ctx): array
124
    {
125
        $server = $this->originalServer;
126
        $server['REQUEST_TIME'] = time();
127
        $server['REQUEST_TIME_FLOAT'] = microtime(true);
128
        $server['REMOTE_ADDR'] = $ctx['attributes']['ipAddress'] ?? $ctx['remoteAddr'] ?? '127.0.0.1';
129
        $server['REMOTE_ADDR'] = $ctx['attributes']['ipAddress'] ?? $ctx['remoteAddr'] ?? '127.0.0.1';
130
131
        $server['HTTP_USER_AGENT'] = '';
132
        foreach ($ctx['headers'] as $key => $value) {
133
            $key = strtoupper(str_replace('-', '_', $key));
134
            if (\in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) {
135
                $server[$key] = implode(', ', $value);
136
            } else {
137
                $server['HTTP_' . $key] = implode(', ', $value);
138
            }
139
        }
140
141
        return $server;
142
    }
143
144
    /**
145
     * Wraps all uploaded files with UploadedFile.
146
     *
147
     * @param array $files
148
     *
149
     * @return array
150
     */
151
    private function wrapUploads($files): array
152
    {
153
        if (empty($files)) {
154
            return [];
155
        }
156
157
        $result = [];
158
        foreach ($files as $index => $f) {
159
            if (!isset($f['name'])) {
160
                $result[$index] = $this->wrapUploads($f);
161
                continue;
162
            }
163
164
            if (UPLOAD_ERR_OK === $f['error']) {
165
                $stream = $this->streamFactory->createStreamFromFile($f['tmpName']);
166
            } else {
167
                $stream = $this->streamFactory->createStream();
168
            }
169
170
            $result[$index] = $this->uploadsFactory->createUploadedFile(
171
                $stream,
172
                $f['size'],
173
                $f['error'],
174
                $f['name'],
175
                $f['mime']
176
            );
177
        }
178
179
        return $result;
180
    }
181
182
    /**
183
     * Normalize HTTP protocol version to valid values
184
     *
185
     * @param string $version
186
     * @return string
187
     */
188
    private static function fetchProtocolVersion(string $version): string
189
    {
190
        $v = substr($version, 5);
191
192
        if ($v === '2.0') {
193
            return '2';
194
        }
195
196
        // Fallback for values outside of valid protocol versions
197
        if (!in_array($v, static::$allowedVersions, true)) {
0 ignored issues
show
Bug introduced by
Since $allowedVersions is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $allowedVersions to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
198
            return '1.1';
199
        }
200
201
        return $v;
202
    }
203
}
204