Total Complexity | 42 |
Total Lines | 283 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Complex classes like Transaction often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Transaction, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
19 | class Transaction |
||
20 | { |
||
21 | private $sender; |
||
22 | private $loop; |
||
23 | |||
24 | // context: http.timeout (ini_get('default_socket_timeout'): 60) |
||
25 | private $timeout; |
||
26 | |||
27 | // context: http.follow_location (true) |
||
28 | private $followRedirects = true; |
||
29 | |||
30 | // context: http.max_redirects (10) |
||
31 | private $maxRedirects = 10; |
||
32 | |||
33 | // context: http.ignore_errors (false) |
||
34 | private $obeySuccessCode = true; |
||
35 | |||
36 | private $streaming = false; |
||
37 | |||
38 | private $maximumSize = 16777216; // 16 MiB = 2^24 bytes |
||
39 | |||
40 | public function __construct(Sender $sender, LoopInterface $loop) |
||
41 | { |
||
42 | $this->sender = $sender; |
||
43 | $this->loop = $loop; |
||
44 | } |
||
45 | |||
46 | /** |
||
47 | * @param array $options |
||
48 | * @return self returns new instance, without modifying existing instance |
||
49 | */ |
||
50 | public function withOptions(array $options) |
||
51 | { |
||
52 | $transaction = clone $this; |
||
53 | foreach ($options as $name => $value) { |
||
54 | if (property_exists($transaction, $name)) { |
||
55 | // restore default value if null is given |
||
56 | if ($value === null) { |
||
57 | $default = new self($this->sender, $this->loop); |
||
58 | $value = $default->$name; |
||
59 | } |
||
60 | |||
61 | $transaction->$name = $value; |
||
62 | } |
||
63 | } |
||
64 | |||
65 | return $transaction; |
||
66 | } |
||
67 | |||
68 | public function send(RequestInterface $request) |
||
69 | { |
||
70 | $deferred = new Deferred(function () use (&$deferred) { |
||
71 | if (isset($deferred->pending)) { |
||
72 | $deferred->pending->cancel(); |
||
73 | unset($deferred->pending); |
||
74 | } |
||
75 | }); |
||
76 | |||
77 | $deferred->numRequests = 0; |
||
|
|||
78 | |||
79 | // use timeout from options or default to PHP's default_socket_timeout (60) |
||
80 | $timeout = (float)($this->timeout !== null ? $this->timeout : ini_get("default_socket_timeout")); |
||
81 | |||
82 | $loop = $this->loop; |
||
83 | $this->next($request, $deferred)->then( |
||
84 | function (ResponseInterface $response) use ($deferred, $loop, &$timeout) { |
||
85 | if (isset($deferred->timeout)) { |
||
86 | $loop->cancelTimer($deferred->timeout); |
||
87 | unset($deferred->timeout); |
||
88 | } |
||
89 | $timeout = -1; |
||
90 | $deferred->resolve($response); |
||
91 | }, |
||
92 | function ($e) use ($deferred, $loop, &$timeout) { |
||
93 | if (isset($deferred->timeout)) { |
||
94 | $loop->cancelTimer($deferred->timeout); |
||
95 | unset($deferred->timeout); |
||
96 | } |
||
97 | $timeout = -1; |
||
98 | $deferred->reject($e); |
||
99 | } |
||
100 | ); |
||
101 | |||
102 | if ($timeout < 0) { |
||
103 | return $deferred->promise(); |
||
104 | } |
||
105 | |||
106 | $body = $request->getBody(); |
||
107 | if ($body instanceof ReadableStreamInterface && $body->isReadable()) { |
||
108 | $that = $this; |
||
109 | $body->on('close', function () use ($that, $deferred, &$timeout) { |
||
110 | if ($timeout >= 0) { |
||
111 | $that->applyTimeout($deferred, $timeout); |
||
112 | } |
||
113 | }); |
||
114 | } else { |
||
115 | $this->applyTimeout($deferred, $timeout); |
||
116 | } |
||
117 | |||
118 | return $deferred->promise(); |
||
119 | } |
||
120 | |||
121 | /** |
||
122 | * @internal |
||
123 | * @param Deferred $deferred |
||
124 | * @param number $timeout |
||
125 | * @return void |
||
126 | */ |
||
127 | public function applyTimeout(Deferred $deferred, $timeout) |
||
128 | { |
||
129 | $deferred->timeout = $this->loop->addTimer($timeout, function () use ($timeout, $deferred) { |
||
130 | $deferred->reject(new \RuntimeException( |
||
131 | 'Request timed out after ' . $timeout . ' seconds' |
||
132 | )); |
||
133 | if (isset($deferred->pending)) { |
||
134 | $deferred->pending->cancel(); |
||
135 | unset($deferred->pending); |
||
136 | } |
||
137 | }); |
||
138 | } |
||
139 | |||
140 | private function next(RequestInterface $request, Deferred $deferred) |
||
141 | { |
||
142 | $this->progress('request', array($request)); |
||
143 | |||
144 | $that = $this; |
||
145 | ++$deferred->numRequests; |
||
146 | |||
147 | $promise = $this->sender->send($request); |
||
148 | |||
149 | if (!$this->streaming) { |
||
150 | $promise = $promise->then(function ($response) use ($deferred, $that) { |
||
151 | return $that->bufferResponse($response, $deferred); |
||
152 | }); |
||
153 | } |
||
154 | |||
155 | $deferred->pending = $promise; |
||
156 | |||
157 | return $promise->then( |
||
158 | function (ResponseInterface $response) use ($request, $that, $deferred) { |
||
159 | return $that->onResponse($response, $request, $deferred); |
||
160 | } |
||
161 | ); |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * @internal |
||
166 | * @param ResponseInterface $response |
||
167 | * @return PromiseInterface Promise<ResponseInterface, Exception> |
||
168 | */ |
||
169 | public function bufferResponse(ResponseInterface $response, $deferred) |
||
170 | { |
||
171 | $stream = $response->getBody(); |
||
172 | |||
173 | $size = $stream->getSize(); |
||
174 | if ($size !== null && $size > $this->maximumSize) { |
||
175 | $stream->close(); |
||
176 | return \React\Promise\reject(new \OverflowException( |
||
177 | 'Response body size of ' . $size . ' bytes exceeds maximum of ' . $this->maximumSize . ' bytes', |
||
178 | \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 0 |
||
179 | )); |
||
180 | } |
||
181 | |||
182 | // body is not streaming => already buffered |
||
183 | if (!$stream instanceof ReadableStreamInterface) { |
||
184 | return \React\Promise\resolve($response); |
||
185 | } |
||
186 | |||
187 | // buffer stream and resolve with buffered body |
||
188 | $maximumSize = $this->maximumSize; |
||
189 | $promise = \React\Promise\Stream\buffer($stream, $maximumSize)->then( |
||
190 | function ($body) use ($response) { |
||
191 | return $response->withBody(new BufferedBody($body)); |
||
192 | }, |
||
193 | function ($e) use ($stream, $maximumSize) { |
||
194 | // try to close stream if buffering fails (or is cancelled) |
||
195 | $stream->close(); |
||
196 | |||
197 | if ($e instanceof \OverflowException) { |
||
198 | $e = new \OverflowException( |
||
199 | 'Response body size exceeds maximum of ' . $maximumSize . ' bytes', |
||
200 | \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 0 |
||
201 | ); |
||
202 | } |
||
203 | |||
204 | throw $e; |
||
205 | } |
||
206 | ); |
||
207 | |||
208 | $deferred->pending = $promise; |
||
209 | |||
210 | return $promise; |
||
211 | } |
||
212 | |||
213 | /** |
||
214 | * @internal |
||
215 | * @param ResponseInterface $response |
||
216 | * @param RequestInterface $request |
||
217 | * @throws ResponseException |
||
218 | * @return ResponseInterface|PromiseInterface |
||
219 | */ |
||
220 | public function onResponse(ResponseInterface $response, RequestInterface $request, $deferred) |
||
237 | } |
||
238 | |||
239 | /** |
||
240 | * @param ResponseInterface $response |
||
241 | * @param RequestInterface $request |
||
242 | * @return PromiseInterface |
||
243 | * @throws \RuntimeException |
||
244 | */ |
||
245 | private function onResponseRedirect(ResponseInterface $response, RequestInterface $request, Deferred $deferred) |
||
258 | } |
||
259 | |||
260 | /** |
||
261 | * @param RequestInterface $request |
||
262 | * @param UriInterface $location |
||
263 | * @return RequestInterface |
||
264 | */ |
||
265 | private function makeRedirectRequest(RequestInterface $request, UriInterface $location) |
||
282 | } |
||
283 | |||
284 | private function progress($name, array $args = array()) |
||
304 |