1
|
|
|
<?php |
2
|
|
|
namespace GuzzleHttp\Handler; |
3
|
|
|
|
4
|
|
|
use GuzzleHttp\Exception\RequestException; |
5
|
|
|
use GuzzleHttp\Exception\ConnectException; |
6
|
|
|
use GuzzleHttp\Promise\FulfilledPromise; |
7
|
|
|
use GuzzleHttp\Promise\RejectedPromise; |
8
|
|
|
use GuzzleHttp\Promise\PromiseInterface; |
9
|
|
|
use GuzzleHttp\Psr7; |
10
|
|
|
use GuzzleHttp\TransferStats; |
11
|
|
|
use Psr\Http\Message\RequestInterface; |
12
|
|
|
use Psr\Http\Message\ResponseInterface; |
13
|
|
|
use Psr\Http\Message\StreamInterface; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* HTTP handler that uses PHP's HTTP stream wrapper. |
17
|
|
|
*/ |
18
|
|
|
class StreamHandler |
19
|
|
|
{ |
20
|
|
|
private $lastHeaders = []; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Sends an HTTP request. |
24
|
|
|
* |
25
|
|
|
* @param RequestInterface $request Request to send. |
26
|
|
|
* @param array $options Request transfer options. |
27
|
|
|
* |
28
|
|
|
* @return PromiseInterface |
29
|
|
|
*/ |
30
|
|
|
public function __invoke(RequestInterface $request, array $options) |
31
|
|
|
{ |
32
|
|
|
// Sleep if there is a delay specified. |
33
|
|
|
if (isset($options['delay'])) { |
34
|
|
|
usleep($options['delay'] * 1000); |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
$startTime = isset($options['on_stats']) ? microtime(true) : null; |
38
|
|
|
|
39
|
|
|
try { |
40
|
|
|
// Does not support the expect header. |
41
|
|
|
$request = $request->withoutHeader('Expect'); |
42
|
|
|
|
43
|
|
|
// Append a content-length header if body size is zero to match |
44
|
|
|
// cURL's behavior. |
45
|
|
|
if (0 === $request->getBody()->getSize()) { |
46
|
|
|
$request = $request->withHeader('Content-Length', 0); |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
return $this->createResponse( |
50
|
|
|
$request, |
51
|
|
|
$options, |
52
|
|
|
$this->createStream($request, $options), |
53
|
|
|
$startTime |
54
|
|
|
); |
55
|
|
|
} catch (\InvalidArgumentException $e) { |
56
|
|
|
throw $e; |
57
|
|
|
} catch (\Exception $e) { |
58
|
|
|
// Determine if the error was a networking error. |
59
|
|
|
$message = $e->getMessage(); |
60
|
|
|
// This list can probably get more comprehensive. |
61
|
|
|
if (strpos($message, 'getaddrinfo') // DNS lookup failed |
62
|
|
|
|| strpos($message, 'Connection refused') |
63
|
|
|
|| strpos($message, "couldn't connect to host") // error on HHVM |
64
|
|
|
) { |
65
|
|
|
$e = new ConnectException($e->getMessage(), $request, $e); |
66
|
|
|
} |
67
|
|
|
$e = RequestException::wrapException($request, $e); |
68
|
|
|
$this->invokeStats($options, $request, $startTime, null, $e); |
69
|
|
|
|
70
|
|
|
return \GuzzleHttp\Promise\rejection_for($e); |
|
|
|
|
71
|
|
|
} |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
private function invokeStats( |
75
|
|
|
array $options, |
76
|
|
|
RequestInterface $request, |
77
|
|
|
$startTime, |
78
|
|
|
ResponseInterface $response = null, |
79
|
|
|
$error = null |
80
|
|
|
) { |
81
|
|
|
if (isset($options['on_stats'])) { |
82
|
|
|
$stats = new TransferStats( |
83
|
|
|
$request, |
84
|
|
|
$response, |
85
|
|
|
microtime(true) - $startTime, |
86
|
|
|
$error, |
87
|
|
|
[] |
88
|
|
|
); |
89
|
|
|
call_user_func($options['on_stats'], $stats); |
90
|
|
|
} |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
private function createResponse( |
94
|
|
|
RequestInterface $request, |
95
|
|
|
array $options, |
96
|
|
|
$stream, |
97
|
|
|
$startTime |
98
|
|
|
) { |
99
|
|
|
$hdrs = $this->lastHeaders; |
100
|
|
|
$this->lastHeaders = []; |
101
|
|
|
$parts = explode(' ', array_shift($hdrs), 3); |
102
|
|
|
$ver = explode('/', $parts[0])[1]; |
103
|
|
|
$status = $parts[1]; |
104
|
|
|
$reason = isset($parts[2]) ? $parts[2] : null; |
105
|
|
|
$headers = \GuzzleHttp\headers_from_lines($hdrs); |
106
|
|
|
list ($stream, $headers) = $this->checkDecode($options, $headers, $stream); |
107
|
|
|
$stream = Psr7\stream_for($stream); |
|
|
|
|
108
|
|
|
$sink = $stream; |
109
|
|
|
|
110
|
|
|
if (strcasecmp('HEAD', $request->getMethod())) { |
111
|
|
|
$sink = $this->createSink($stream, $options); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
$response = new Psr7\Response($status, $headers, $sink, $ver, $reason); |
|
|
|
|
115
|
|
|
|
116
|
|
|
if (isset($options['on_headers'])) { |
117
|
|
|
try { |
118
|
|
|
$options['on_headers']($response); |
119
|
|
|
} catch (\Exception $e) { |
120
|
|
|
$msg = 'An error was encountered during the on_headers event'; |
121
|
|
|
$ex = new RequestException($msg, $request, $response, $e); |
122
|
|
|
return \GuzzleHttp\Promise\rejection_for($ex); |
|
|
|
|
123
|
|
|
} |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
// Do not drain when the request is a HEAD request because they have |
127
|
|
|
// no body. |
128
|
|
|
if ($sink !== $stream) { |
129
|
|
|
$this->drain( |
130
|
|
|
$stream, |
131
|
|
|
$sink, |
132
|
|
|
$response->getHeaderLine('Content-Length') |
133
|
|
|
); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
$this->invokeStats($options, $request, $startTime, $response, null); |
137
|
|
|
|
138
|
|
|
return new FulfilledPromise($response); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
private function createSink(StreamInterface $stream, array $options) |
142
|
|
|
{ |
143
|
|
|
if (!empty($options['stream'])) { |
144
|
|
|
return $stream; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
$sink = isset($options['sink']) |
148
|
|
|
? $options['sink'] |
149
|
|
|
: fopen('php://temp', 'r+'); |
150
|
|
|
|
151
|
|
|
return is_string($sink) |
152
|
|
|
? new Psr7\LazyOpenStream($sink, 'w+') |
153
|
|
|
: Psr7\stream_for($sink); |
|
|
|
|
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
private function checkDecode(array $options, array $headers, $stream) |
157
|
|
|
{ |
158
|
|
|
// Automatically decode responses when instructed. |
159
|
|
|
if (!empty($options['decode_content'])) { |
160
|
|
|
$normalizedKeys = \GuzzleHttp\normalize_header_keys($headers); |
161
|
|
|
if (isset($normalizedKeys['content-encoding'])) { |
162
|
|
|
$encoding = $headers[$normalizedKeys['content-encoding']]; |
163
|
|
|
if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { |
164
|
|
|
$stream = new Psr7\InflateStream( |
165
|
|
|
Psr7\stream_for($stream) |
|
|
|
|
166
|
|
|
); |
167
|
|
|
$headers['x-encoded-content-encoding'] |
168
|
|
|
= $headers[$normalizedKeys['content-encoding']]; |
169
|
|
|
// Remove content-encoding header |
170
|
|
|
unset($headers[$normalizedKeys['content-encoding']]); |
171
|
|
|
// Fix content-length header |
172
|
|
|
if (isset($normalizedKeys['content-length'])) { |
173
|
|
|
$headers['x-encoded-content-length'] |
174
|
|
|
= $headers[$normalizedKeys['content-length']]; |
175
|
|
|
|
176
|
|
|
$length = (int) $stream->getSize(); |
177
|
|
|
if ($length === 0) { |
178
|
|
|
unset($headers[$normalizedKeys['content-length']]); |
179
|
|
|
} else { |
180
|
|
|
$headers[$normalizedKeys['content-length']] = [$length]; |
181
|
|
|
} |
182
|
|
|
} |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
return [$stream, $headers]; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Drains the source stream into the "sink" client option. |
192
|
|
|
* |
193
|
|
|
* @param StreamInterface $source |
194
|
|
|
* @param StreamInterface $sink |
195
|
|
|
* @param string $contentLength Header specifying the amount of |
196
|
|
|
* data to read. |
197
|
|
|
* |
198
|
|
|
* @return StreamInterface |
199
|
|
|
* @throws \RuntimeException when the sink option is invalid. |
200
|
|
|
*/ |
201
|
|
|
private function drain( |
202
|
|
|
StreamInterface $source, |
203
|
|
|
StreamInterface $sink, |
204
|
|
|
$contentLength |
205
|
|
|
) { |
206
|
|
|
// If a content-length header is provided, then stop reading once |
207
|
|
|
// that number of bytes has been read. This can prevent infinitely |
208
|
|
|
// reading from a stream when dealing with servers that do not honor |
209
|
|
|
// Connection: Close headers. |
210
|
|
|
Psr7\copy_to_stream( |
|
|
|
|
211
|
|
|
$source, |
212
|
|
|
$sink, |
213
|
|
|
(strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1 |
214
|
|
|
); |
215
|
|
|
|
216
|
|
|
$sink->seek(0); |
217
|
|
|
$source->close(); |
218
|
|
|
|
219
|
|
|
return $sink; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* Create a resource and check to ensure it was created successfully |
224
|
|
|
* |
225
|
|
|
* @param callable $callback Callable that returns stream resource |
226
|
|
|
* |
227
|
|
|
* @return resource |
228
|
|
|
* @throws \RuntimeException on error |
229
|
|
|
*/ |
230
|
|
|
private function createResource(callable $callback) |
231
|
|
|
{ |
232
|
|
|
$errors = null; |
233
|
|
|
set_error_handler(function ($_, $msg, $file, $line) use (&$errors) { |
234
|
|
|
$errors[] = [ |
235
|
|
|
'message' => $msg, |
236
|
|
|
'file' => $file, |
237
|
|
|
'line' => $line |
238
|
|
|
]; |
239
|
|
|
return true; |
240
|
|
|
}); |
241
|
|
|
|
242
|
|
|
$resource = $callback(); |
243
|
|
|
restore_error_handler(); |
244
|
|
|
|
245
|
|
|
if (!$resource) { |
246
|
|
|
$message = 'Error creating resource: '; |
247
|
|
|
foreach ($errors as $err) { |
248
|
|
|
foreach ($err as $key => $value) { |
249
|
|
|
$message .= "[$key] $value" . PHP_EOL; |
250
|
|
|
} |
251
|
|
|
} |
252
|
|
|
throw new \RuntimeException(trim($message)); |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
return $resource; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
private function createStream(RequestInterface $request, array $options) |
259
|
|
|
{ |
260
|
|
|
static $methods; |
261
|
|
|
if (!$methods) { |
262
|
|
|
$methods = array_flip(get_class_methods(__CLASS__)); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
// HTTP/1.1 streams using the PHP stream wrapper require a |
266
|
|
|
// Connection: close header |
267
|
|
|
if ($request->getProtocolVersion() == '1.1' |
268
|
|
|
&& !$request->hasHeader('Connection') |
269
|
|
|
) { |
270
|
|
|
$request = $request->withHeader('Connection', 'close'); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
// Ensure SSL is verified by default |
274
|
|
|
if (!isset($options['verify'])) { |
275
|
|
|
$options['verify'] = true; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
$params = []; |
279
|
|
|
$context = $this->getDefaultContext($request, $options); |
|
|
|
|
280
|
|
|
|
281
|
|
|
if (isset($options['on_headers']) && !is_callable($options['on_headers'])) { |
282
|
|
|
throw new \InvalidArgumentException('on_headers must be callable'); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
if (!empty($options)) { |
286
|
|
|
foreach ($options as $key => $value) { |
287
|
|
|
$method = "add_{$key}"; |
288
|
|
|
if (isset($methods[$method])) { |
289
|
|
|
$this->{$method}($request, $context, $value, $params); |
290
|
|
|
} |
291
|
|
|
} |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
if (isset($options['stream_context'])) { |
295
|
|
|
if (!is_array($options['stream_context'])) { |
296
|
|
|
throw new \InvalidArgumentException('stream_context must be an array'); |
297
|
|
|
} |
298
|
|
|
$context = array_replace_recursive( |
299
|
|
|
$context, |
300
|
|
|
$options['stream_context'] |
301
|
|
|
); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
// Microsoft NTLM authentication only supported with curl handler |
305
|
|
|
if (isset($options['auth']) |
306
|
|
|
&& is_array($options['auth']) |
307
|
|
|
&& isset($options['auth'][2]) |
308
|
|
|
&& 'ntlm' == $options['auth'][2] |
309
|
|
|
) { |
310
|
|
|
|
311
|
|
|
throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler'); |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
$uri = $this->resolveHost($request, $options); |
315
|
|
|
|
316
|
|
|
$context = $this->createResource( |
317
|
|
|
function () use ($context, $params) { |
318
|
|
|
return stream_context_create($context, $params); |
319
|
|
|
} |
320
|
|
|
); |
321
|
|
|
|
322
|
|
|
return $this->createResource( |
323
|
|
|
function () use ($uri, &$http_response_header, $context, $options) { |
324
|
|
|
$resource = fopen((string) $uri, 'r', null, $context); |
|
|
|
|
325
|
|
|
$this->lastHeaders = $http_response_header; |
326
|
|
|
|
327
|
|
|
if (isset($options['read_timeout'])) { |
328
|
|
|
$readTimeout = $options['read_timeout']; |
329
|
|
|
$sec = (int) $readTimeout; |
330
|
|
|
$usec = ($readTimeout - $sec) * 100000; |
331
|
|
|
stream_set_timeout($resource, $sec, $usec); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
return $resource; |
335
|
|
|
} |
336
|
|
|
); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
private function resolveHost(RequestInterface $request, array $options) |
340
|
|
|
{ |
341
|
|
|
$uri = $request->getUri(); |
342
|
|
|
|
343
|
|
|
if (isset($options['force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) { |
344
|
|
|
if ('v4' === $options['force_ip_resolve']) { |
345
|
|
|
$records = dns_get_record($uri->getHost(), DNS_A); |
346
|
|
|
if (!isset($records[0]['ip'])) { |
347
|
|
|
throw new ConnectException(sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request); |
348
|
|
|
} |
349
|
|
|
$uri = $uri->withHost($records[0]['ip']); |
350
|
|
|
} elseif ('v6' === $options['force_ip_resolve']) { |
351
|
|
|
$records = dns_get_record($uri->getHost(), DNS_AAAA); |
352
|
|
|
if (!isset($records[0]['ipv6'])) { |
353
|
|
|
throw new ConnectException(sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request); |
354
|
|
|
} |
355
|
|
|
$uri = $uri->withHost('[' . $records[0]['ipv6'] . ']'); |
356
|
|
|
} |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
return $uri; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
private function getDefaultContext(RequestInterface $request) |
363
|
|
|
{ |
364
|
|
|
$headers = ''; |
365
|
|
|
foreach ($request->getHeaders() as $name => $value) { |
366
|
|
|
foreach ($value as $val) { |
367
|
|
|
$headers .= "$name: $val\r\n"; |
368
|
|
|
} |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
$context = [ |
372
|
|
|
'http' => [ |
373
|
|
|
'method' => $request->getMethod(), |
374
|
|
|
'header' => $headers, |
375
|
|
|
'protocol_version' => $request->getProtocolVersion(), |
376
|
|
|
'ignore_errors' => true, |
377
|
|
|
'follow_location' => 0, |
378
|
|
|
], |
379
|
|
|
]; |
380
|
|
|
|
381
|
|
|
$body = (string) $request->getBody(); |
382
|
|
|
|
383
|
|
|
if (!empty($body)) { |
384
|
|
|
$context['http']['content'] = $body; |
385
|
|
|
// Prevent the HTTP handler from adding a Content-Type header. |
386
|
|
|
if (!$request->hasHeader('Content-Type')) { |
387
|
|
|
$context['http']['header'] .= "Content-Type:\r\n"; |
388
|
|
|
} |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
$context['http']['header'] = rtrim($context['http']['header']); |
392
|
|
|
|
393
|
|
|
return $context; |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
private function add_proxy(RequestInterface $request, &$options, $value, &$params) |
397
|
|
|
{ |
398
|
|
|
if (!is_array($value)) { |
399
|
|
|
$options['http']['proxy'] = $value; |
400
|
|
|
} else { |
401
|
|
|
$scheme = $request->getUri()->getScheme(); |
402
|
|
|
if (isset($value[$scheme])) { |
403
|
|
|
if (!isset($value['no']) |
404
|
|
|
|| !\GuzzleHttp\is_host_in_noproxy( |
405
|
|
|
$request->getUri()->getHost(), |
406
|
|
|
$value['no'] |
407
|
|
|
) |
408
|
|
|
) { |
409
|
|
|
$options['http']['proxy'] = $value[$scheme]; |
410
|
|
|
} |
411
|
|
|
} |
412
|
|
|
} |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
private function add_timeout(RequestInterface $request, &$options, $value, &$params) |
416
|
|
|
{ |
417
|
|
|
if ($value > 0) { |
418
|
|
|
$options['http']['timeout'] = $value; |
419
|
|
|
} |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
private function add_verify(RequestInterface $request, &$options, $value, &$params) |
423
|
|
|
{ |
424
|
|
|
if ($value === true) { |
425
|
|
|
// PHP 5.6 or greater will find the system cert by default. When |
426
|
|
|
// < 5.6, use the Guzzle bundled cacert. |
427
|
|
|
if (PHP_VERSION_ID < 50600) { |
428
|
|
|
$options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle(); |
429
|
|
|
} |
430
|
|
|
} elseif (is_string($value)) { |
431
|
|
|
$options['ssl']['cafile'] = $value; |
432
|
|
|
if (!file_exists($value)) { |
433
|
|
|
throw new \RuntimeException("SSL CA bundle not found: $value"); |
434
|
|
|
} |
435
|
|
|
} elseif ($value === false) { |
436
|
|
|
$options['ssl']['verify_peer'] = false; |
437
|
|
|
$options['ssl']['verify_peer_name'] = false; |
438
|
|
|
return; |
439
|
|
|
} else { |
440
|
|
|
throw new \InvalidArgumentException('Invalid verify request option'); |
441
|
|
|
} |
442
|
|
|
|
443
|
|
|
$options['ssl']['verify_peer'] = true; |
444
|
|
|
$options['ssl']['verify_peer_name'] = true; |
445
|
|
|
$options['ssl']['allow_self_signed'] = false; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
private function add_cert(RequestInterface $request, &$options, $value, &$params) |
449
|
|
|
{ |
450
|
|
|
if (is_array($value)) { |
451
|
|
|
$options['ssl']['passphrase'] = $value[1]; |
452
|
|
|
$value = $value[0]; |
453
|
|
|
} |
454
|
|
|
|
455
|
|
|
if (!file_exists($value)) { |
456
|
|
|
throw new \RuntimeException("SSL certificate not found: {$value}"); |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
$options['ssl']['local_cert'] = $value; |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
private function add_progress(RequestInterface $request, &$options, $value, &$params) |
463
|
|
|
{ |
464
|
|
|
$this->addNotification( |
465
|
|
|
$params, |
466
|
|
|
function ($code, $a, $b, $c, $transferred, $total) use ($value) { |
467
|
|
|
if ($code == STREAM_NOTIFY_PROGRESS) { |
468
|
|
|
$value($total, $transferred, null, null); |
469
|
|
|
} |
470
|
|
|
} |
471
|
|
|
); |
472
|
|
|
} |
473
|
|
|
|
474
|
|
|
private function add_debug(RequestInterface $request, &$options, $value, &$params) |
475
|
|
|
{ |
476
|
|
|
if ($value === false) { |
477
|
|
|
return; |
478
|
|
|
} |
479
|
|
|
|
480
|
|
|
static $map = [ |
481
|
|
|
STREAM_NOTIFY_CONNECT => 'CONNECT', |
482
|
|
|
STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', |
483
|
|
|
STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', |
484
|
|
|
STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', |
485
|
|
|
STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', |
486
|
|
|
STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', |
487
|
|
|
STREAM_NOTIFY_PROGRESS => 'PROGRESS', |
488
|
|
|
STREAM_NOTIFY_FAILURE => 'FAILURE', |
489
|
|
|
STREAM_NOTIFY_COMPLETED => 'COMPLETED', |
490
|
|
|
STREAM_NOTIFY_RESOLVE => 'RESOLVE', |
491
|
|
|
]; |
492
|
|
|
static $args = ['severity', 'message', 'message_code', |
493
|
|
|
'bytes_transferred', 'bytes_max']; |
494
|
|
|
|
495
|
|
|
$value = \GuzzleHttp\debug_resource($value); |
496
|
|
|
$ident = $request->getMethod() . ' ' . $request->getUri()->withFragment(''); |
497
|
|
|
$this->addNotification( |
498
|
|
|
$params, |
499
|
|
|
function () use ($ident, $value, $map, $args) { |
500
|
|
|
$passed = func_get_args(); |
501
|
|
|
$code = array_shift($passed); |
502
|
|
|
fprintf($value, '<%s> [%s] ', $ident, $map[$code]); |
503
|
|
|
foreach (array_filter($passed) as $i => $v) { |
504
|
|
|
fwrite($value, $args[$i] . ': "' . $v . '" '); |
505
|
|
|
} |
506
|
|
|
fwrite($value, "\n"); |
507
|
|
|
} |
508
|
|
|
); |
509
|
|
|
} |
510
|
|
|
|
511
|
|
|
private function addNotification(array &$params, callable $notify) |
512
|
|
|
{ |
513
|
|
|
// Wrap the existing function if needed. |
514
|
|
|
if (!isset($params['notification'])) { |
515
|
|
|
$params['notification'] = $notify; |
516
|
|
|
} else { |
517
|
|
|
$params['notification'] = $this->callArray([ |
518
|
|
|
$params['notification'], |
519
|
|
|
$notify |
520
|
|
|
]); |
521
|
|
|
} |
522
|
|
|
} |
523
|
|
|
|
524
|
|
|
private function callArray(array $functions) |
525
|
|
|
{ |
526
|
|
|
return function () use ($functions) { |
527
|
|
|
$args = func_get_args(); |
528
|
|
|
foreach ($functions as $fn) { |
529
|
|
|
call_user_func_array($fn, $args); |
530
|
|
|
} |
531
|
|
|
}; |
532
|
|
|
} |
533
|
|
|
} |
534
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.