Issues (1474)

framework/IO/TStreamNotificationCallback.php (2 issues)

Labels
Severity
1
<?php
2
3
/**
4
 * TStreamNotificationCallback class file
5
 *
6
 * @author Brad Anderson <[email protected]>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 */
10
11
namespace Prado\IO;
12
13
use Prado\Collections\TWeakCallableCollection;
14
use Prado\Prado;
15
16
/**
17
 * TStreamNotificationCallback class.
18
 *
19
 * This class is used to listen into the connections of fopen(), file_get_contents(),
20
 * and file_put_contents() by patching into context 'notification' parameter
21
 * callback.
22
 *
23
 * This class is an invokable callback that is notified as the connection unfolds.
24
 * Of particular use is listening to the progress of data transfer with {@see \Prado\IO\TStreamNotificationCallback::onProgress()}
25
 * The {@see \Prado\IO\TStreamNotificationCallback::filterStreamContext()}
26
 * method can accept a TStreamNotificationCallback or an array of options that includes
27
 * "notification".  The array of options are the options for stream_context_create
28
 * and can include events as keys and event handlers (or an array of Event Handlers)
29
 * to patch into the "notification" callback.  Within the array of options, "notification"
30
 * can be a TStreamNotificationCallback or an array of events as keys and event
31
 * handlers as values.
32
 *
33
 * The following event handlers are available to listen to the file connection and
34
 * transfer:
35
 *   - onResolve: Raised when the Notification Code is STREAM_NOTIFY_RESOLVE.
36
 *   - onConnected: Raised when the Notification Code is STREAM_NOTIFY_CONNECT.
37
 *   - onAuthRequired: Raised when the Notification Code is STREAM_NOTIFY_AUTH_REQUIRED.
38
 *   - onAuthResult: Raised when the Notification Code is STREAM_NOTIFY_AUTH_RESULT.
39
 *   - onRedirected: Raised when the Notification Code is STREAM_NOTIFY_REDIRECTED.
40
 *   - onMimeType: Raised when the Notification Code is STREAM_NOTIFY_MIME_TYPE_IS.
41
 *       This parses the $message Mime Type and Charset and is retrievable with
42
 *       {@see \Prado\IO\TStreamNotificationCallback::getMimeType()} and {@see \Prado\IO\TStreamNotificationCallback::getCharset()}.
43
 *   - onFileSize: Raised when the Notification Code is STREAM_NOTIFY_FILE_SIZE_IS.
44
 *       This stores the file size and is retrievable with {@see \Prado\IO\TStreamNotificationCallback::getFileSize()}.
45
 *   - onProgress: Raised when the Notification Code is STREAM_NOTIFY_PROGRESS.
46
 *       This stores the bytes Transferred and is retrievable with {@see \Prado\IO\TStreamNotificationCallback::getBytesTransferred()}.
47
 *   - onCompleted: Raised when the Notification Code is STREAM_NOTIFY_COMPLETED.
48
 *		 This sets the {@see \Prado\IO\TStreamNotificationCallback::getIsCompleted()} to true (from false).
49
 *   - onFailure: Raised when the Notification Code is STREAM_NOTIFY_FAILURE.
50
 *		 This sets the {@see \Prado\IO\TStreamNotificationCallback::getIsFailure()} to true (from
51
 *       false) and stores the Message Code for retrieval with {@see \Prado\IO\TStreamNotificationCallback::getMessageCode()}.
52
 *
53
 * These events pass the {@see \Prado\IO\TStreamNotificationParameter} as the parameter. It
54
 * contains the arguments of the Stream Notification Callback and is reused in each
55
 * notification.
56
 *
57
 * @author Brad Anderson <[email protected]>
58
 * @since 4.3.0
59
 */
60
class TStreamNotificationCallback extends \Prado\TComponent
61
{
62
	public const NOTIFICATION = 'notification';
63
64
	/** @var ?TWeakCallableCollection The registered direct callbacks. */
65
	private ?TWeakCallableCollection $_callbacks = null;
66
67
	/** @var ?int The severity of the failure. */
68
	private ?int $_severity = null;
69
70
	/** @var ?string The message of the notification. */
71
	private ?string $_message = null;
72
73
	/** @var ?int The message code of the failure. */
74
	private ?int $_messageCode = null;
75
76
	/** @var ?int The bytes transferred. */
77
	private ?int $_bytesTrasferred = null;
78
79
	/** @var ?int The total bytes of the file. */
80
	private ?int $_fileSize = null;
81
82
	/** @var ?string The mime type of the file. */
83
	private ?string $_mimeType = null;
84
85
	/** @var ?string The charset in the mime type of the file. */
86
	private ?string $_charset = null;
87
88
	/** @var bool Was the completed notification code sent. */
89
	private bool $_completed = false;
90
91
	/** @var bool Was the failure notification code sent. */
92
	private bool $_failure = false;
93
94
	/**
95
	 * @var ?TStreamNotificationParameter The Parameter with the callback arguments
96
	 *   for the events.
97
	 */
98
	private ?TStreamNotificationParameter $_parameter = null;
99
100
	/**
101
	 * This converts the input TStreamNotificationCallback or array into a proper stream
102
	 * context with options and parameters (notification).  If $context is an array
103
	 * this can use or convert the "notification" into a stream context parameter.
104
	 * When "notifications" are an array, the keys are events for the TStreamNotificationCallback
105
	 * and values could be a single event handler callback or an array of event handler
106
	 * callbacks.
107
	 * ```php
108
	 *   $callback = new TStreamNotificationCallback();
109
	 *   $callback->onConnected[] = function($sender, $param) {...};
110
	 *   $context = TStreamNotificationCallback::filterStreamContext($callback);
111
	 *   $context = TStreamNotificationCallback::filterStreamContext(['notification' => $callback,
112
	 * 				'onProgress' => [$this, 'progressHandler'], 'onFileSize' =>
113
	 *					[new TEventHandler([$this, 'fileSizeHandler'], $someData), ...]);
114
	 *   // if you don't need the TStreamNotificationCallback, but get the callback as the event handler $sender
115
	 *   $context = TStreamNotificationCallback::filterStreamContext(['notification' => [
116
	 * 				'onProgress' => [$this, 'progressHandler'], 'onFileSize' =>
117
	 *					[new TEventHandler([$this, 'fileSizeHandler'], $someData), ...],
118
	 * 				'http' => [...], ...]);
119
	 *   $context = TStreamNotificationCallback::filterStreamContext([
120
	 * 				'onProgress' => [$this, 'progressHandler'], 'onFileSize' =>
121
	 *					[new TEventHandler([$this, 'fileSizeHandler'], $someData), ...],
122
	 * 				'http' => [...], 'onMimeType' => function($sender, $param) {...},
123
	 *				'onFileSize' => [$behavior, 'otherHandler']]);
124
	 *	$stream = fopen($url, 'rb', false, $context);
125
	 * ```
126
	 *
127
	 * If Event Handlers for TStreamNotificationCallback are in the array, and the
128
	 * TStreamNotificationCallback notification is not instanced, it will be created.
129
	 * If the "notification" is a callable that is not a TStreamNotificationCallback
130
	 * then the "notification" callable is wrapped in a TStreamNotificationCallback.
131
	 * @param mixed $context A Stream Context, TStreamNotificationCallback, or array
132
	 *   of options with notification.
133
	 * @return mixed The Streaming Context.
134
	 */
135
	public static function filterStreamContext(mixed $context): mixed
136
	{
137
		if (is_callable($context)) {
138
			$context = stream_context_create(null, [self::NOTIFICATION => $context]);
139
		} elseif (is_array($context)) {
140
			$notification = array_key_exists(self::NOTIFICATION, $context) ? $context[self::NOTIFICATION] : null;
141
			unset($context[self::NOTIFICATION]);
142
			if (is_array($notification)) {
143
				$notification['class'] ??= TStreamNotificationCallback::class;
144
				$notification = Prado::createComponent($notification);
145
			}
146
			$contextKeys = $context;
147
			$contextKeys = array_change_key_case($contextKeys);
148
			$contextEvents = array_intersect_key($contextKeys, ['onresolve' => true, 'onauthrequired' => true, 'onfailure' => true, 'onauthresult' => true, 'onredirect' => true, 'onconnected' => true, 'onfilesize' => true, 'onmimetype' => true, 'onprogress' => true, 'oncompleted' => true]);
149
			if (!empty($contextEvents)) {
150
				if (empty($notification)) {
151
					$notification = new TStreamNotificationCallback();
152
				} elseif (!($notification instanceof TStreamNotificationCallback)) {
153
					$notification = new TStreamNotificationCallback($notification);
154
				}
155
			}
156
			if ($notification instanceof TStreamNotificationCallback) {
157
				foreach ($context as $property => $value) {
158
					if (property_exists($notification, $property) || $notification->canSetProperty($property) || $notification->hasEvent($property)) {
159
						$notification->setSubProperty($property, $value);
160
						unset($context[$property]);
161
					}
162
				}
163
			}
164
			$param = null;
165
			if ($notification) {
166
				$param = [self::NOTIFICATION => $notification];
167
			}
168
			if (empty($context)) {
169
				$context = null;
170
			}
171
			$context = stream_context_create($context, $param);
172
		}
173
		return $context;
174
	}
175
176
	/**
177
	 * Given a Stream Context, this returns that stream context "notification" callback.
178
	 * In this context, it is likely a TStreamNotificationCallback.
179
	 * @param mixed $context The Stream Context to retrieve the "notification" callback from.
180
	 * @return mixed The "notification" callback or null if nothing.
181
	 */
182
	public static function getContextNotificationCallback(mixed $context): mixed
183
	{
184
		$params = stream_context_get_params($context);
185
186
		return $params[self::NOTIFICATION] ?? null;
187
	}
188
189
	/**
190
	 * The registers the callbacks.
191
	 * @param array $args Callable or null
192
	 */
193
	public function __construct(...$args)
194
	{
195
		if (count($args)) {
196
			$callbacks = $this->getCallbacks();
197
			$callbacks->mergeWith($args);
198
		}
199
		parent::__construct();
200
	}
201
202
	/**
203
	 * This holds any direct notification callbacks so multiple callbacks are supported.
204
	 * @return TWeakCallableCollection The direct notification callbacks.
205
	 */
206
	public function getCallbacks(): TWeakCallableCollection
207
	{
208
		if (!$this->_callbacks) {
209
			$this->_callbacks = new TWeakCallableCollection();
210
		}
211
		return $this->_callbacks;
212
	}
213
214
	/**
215
	 * @return ?int The severity of the failure.
216
	 */
217
	public function getSeverity(): ?int
218
	{
219
		return $this->_severity;
220
	}
221
222
	/**
223
	 * @return ?string The Message from the notification.
224
	 */
225
	public function getMessage(): ?string
226
	{
227
		return $this->_message;
228
	}
229
230
	/**
231
	 * @return ?int The Message Code from the failure.
232
	 */
233
	public function getMessageCode(): ?int
234
	{
235
		return $this->_messageCode;
236
	}
237
238
	/**
239
	 * @return ?int The total bytes transferred of the streaming file.
240
	 */
241
	public function getBytesTransferred(): ?int
242
	{
243
		return $this->_bytesTrasferred;
244
	}
245
246
	/**
247
	 * @return ?int The File Size of the streaming file.
248
	 */
249
	public function getFileSize(): ?int
250
	{
251
		return $this->_fileSize;
252
	}
253
254
	/**
255
	 * @return ?string The MimeType of the file.
256
	 */
257
	public function getMimeType(): ?string
258
	{
259
		return $this->_mimeType;
260
	}
261
262
	/**
263
	 * @return ?string The charset of the MimeType.
264
	 */
265
	public function getCharset(): ?string
266
	{
267
		return $this->_charset;
268
	}
269
270
	/**
271
	 * @return bool Was the Notification Code STREAM_NOTIFY_COMPLETED raised.
272
	 */
273
	public function getIsCompleted(): bool
274
	{
275
		return $this->_completed;
276
	}
277
278
	/**
279
	 * @return bool Was the Notification Code STREAM_NOTIFY_FAILURE at any point.
280
	 */
281
	public function getIsFailure(): bool
282
	{
283
		return $this->_failure;
284
	}
285
286
	/**
287
	 * @return ?TStreamNotificationParameter The event parameter object.
288
	 */
289
	public function getParameter(): ?TStreamNotificationParameter
290
	{
291
		return $this->_parameter;
292
	}
293
294
	/**
295
	 * The callback for stream notifications.
296
	 * @param int $notification_code One of the STREAM_NOTIFY_* notification constants.
297
	 * @param int $severity One of the STREAM_NOTIFY_SEVERITY_* notification constants.
298
	 * @param ?string $message Passed if a descriptive message is available for the event.
299
	 * @param int $message_code Passed if a descriptive message code is available for the event.
300
	 * @param int $bytes_transferred If applicable, the bytes_transferred will be populated.
301
	 * @param int $bytes_max If applicable, the bytes_max will be populated.
302
	 */
303
	public function __invoke(int $notification_code, int $severity, ?string $message, int $message_code, int $bytes_transferred, int $bytes_max): void
304
	{
305
		$this->_message = $message;
306
		if (!$this->_parameter) {
307
			$this->_parameter = new TStreamNotificationParameter($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max);
308
		} else {
309
			$this->_parameter->setNotificationCode($notification_code);
310
			$this->_parameter->setSeverity($severity);
311
			$this->_parameter->setMessage($message);
312
			$this->_parameter->setMessageCode($message_code);
313
			$this->_parameter->setBytesTransferred($bytes_transferred);
314
			$this->_parameter->setBytesMax($bytes_max);
315
		}
316
		switch ($notification_code) {
317
			case STREAM_NOTIFY_RESOLVE: // value: 1
318
				$this->onResolve($this->_parameter);
319
				break;
320
321
			case STREAM_NOTIFY_CONNECT: // value: 2
322
				$this->onConnected($this->_parameter);
323
				break;
324
325
			case STREAM_NOTIFY_AUTH_REQUIRED: // value: 3
326
				$this->onAuthRequired($this->_parameter);
327
				break;
328
329
			case STREAM_NOTIFY_AUTH_RESULT: // value: 10
330
				$this->onAuthResult($this->_parameter);
331
				break;
332
333
			case STREAM_NOTIFY_REDIRECTED: // value: 6
334
				$this->onRedirected($this->_parameter);
335
				break;
336
337
			case STREAM_NOTIFY_MIME_TYPE_IS: // value: 4
338
				if (strpos($message, ';') !== false) {
0 ignored issues
show
It seems like $message can also be of type null; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

338
				if (strpos(/** @scrutinizer ignore-type */ $message, ';') !== false) {
Loading history...
339
					$mimeData = explode(';', $message, 2);
0 ignored issues
show
It seems like $message can also be of type null; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

339
					$mimeData = explode(';', /** @scrutinizer ignore-type */ $message, 2);
Loading history...
340
					$message = $mimeData[0];
341
					if (strpos($mimeData[1] ?? '', '=') !== false) {
342
						$this->_charset = explode('=', $mimeData[1])[1];
343
					}
344
				}
345
				$this->_mimeType = $message;
346
				$this->onMimeType($this->_parameter);
347
				break;
348
349
			case STREAM_NOTIFY_FILE_SIZE_IS: // value: 5
350
				$this->_bytesTrasferred = $bytes_transferred;
351
				$this->_fileSize = $bytes_max;
352
				$this->onFileSize($this->_parameter);
353
				break;
354
355
			case STREAM_NOTIFY_PROGRESS: // value: 7
356
				$this->_bytesTrasferred = $bytes_transferred;
357
				$this->_fileSize = $bytes_max;
358
				$this->onProgress($this->_parameter);
359
				break;
360
361
			case STREAM_NOTIFY_COMPLETED: // value: 8
362
				$this->_completed = true;
363
				$this->onCompleted($this->_parameter);
364
				break;
365
366
			case STREAM_NOTIFY_FAILURE: // value: 9
367
				$this->_failure = true;
368
				$this->_severity = $severity;
369
				$this->_messageCode = $message_code;
370
				$this->onFailure($this->_parameter);
371
				break;
372
		}
373
		if ($this->_callbacks && $this->_callbacks->getCount()) {
374
			foreach ($this->_callbacks as $callback) {
375
				$callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max);
376
			}
377
		}
378
	}
379
380
	/**
381
	 * Raised when the Notification Code is STREAM_NOTIFY_RESOLVE.
382
	 * @param ?TStreamNotificationParameter $param
383
	 */
384
	public function onResolve(?TStreamNotificationParameter $param): array
385
	{
386
		return $this->raiseEvent('onResolve', $this, $param);
387
	}
388
389
	/**
390
	 * Raised when the Notification Code is STREAM_NOTIFY_CONNECT.
391
	 * @param ?TStreamNotificationParameter $param
392
	 */
393
	public function onConnected(?TStreamNotificationParameter $param): array
394
	{
395
		return $this->raiseEvent('onConnected', $this, $param);
396
	}
397
398
	/**
399
	 * Raised when the Notification Code is STREAM_NOTIFY_AUTH_REQUIRED.
400
	 * @param ?TStreamNotificationParameter $param
401
	 */
402
	public function onAuthRequired(?TStreamNotificationParameter $param): array
403
	{
404
		return $this->raiseEvent('onAuthRequired', $this, $param);
405
	}
406
407
	/**
408
	 * Raised when the Notification Code is STREAM_NOTIFY_AUTH_RESULT.
409
	 * @param ?TStreamNotificationParameter $param
410
	 */
411
	public function onAuthResult(?TStreamNotificationParameter $param): array
412
	{
413
		return $this->raiseEvent('onAuthResult', $this, $param);
414
	}
415
416
	/**
417
	 * Raised when the Notification Code is STREAM_NOTIFY_REDIRECTED.
418
	 * @param ?TStreamNotificationParameter $param
419
	 */
420
	public function onRedirected(?TStreamNotificationParameter $param): array
421
	{
422
		return $this->raiseEvent('onRedirected', $this, $param);
423
	}
424
425
	/**
426
	 * Raised when the Notification Code is STREAM_NOTIFY_MIME_TYPE_IS.
427
	 * @param ?TStreamNotificationParameter $param
428
	 */
429
	public function onMimeType(?TStreamNotificationParameter $param): array
430
	{
431
		return $this->raiseEvent('onMimeType', $this, $param);
432
	}
433
434
	/**
435
	 * Raised when the Notification Code is STREAM_NOTIFY_FILE_SIZE_IS.
436
	 * @param ?TStreamNotificationParameter $param
437
	 */
438
	public function onFileSize(?TStreamNotificationParameter $param): array
439
	{
440
		return $this->raiseEvent('onFileSize', $this, $param);
441
	}
442
443
	/**
444
	 * Raised when the Notification Code is STREAM_NOTIFY_PROGRESS.
445
	 * @param ?TStreamNotificationParameter $param
446
	 */
447
	public function onProgress(?TStreamNotificationParameter $param): array
448
	{
449
		return $this->raiseEvent('onProgress', $this, $param);
450
	}
451
452
	/**
453
	 * Raised when the Notification Code is STREAM_NOTIFY_COMPLETED.
454
	 * @param ?TStreamNotificationParameter $param
455
	 */
456
	public function onCompleted(?TStreamNotificationParameter $param): array
457
	{
458
		return $this->raiseEvent('onCompleted', $this, $param);
459
	}
460
461
	/**
462
	 * Raised when the Notification Code is STREAM_NOTIFY_FAILURE.
463
	 * @param ?TStreamNotificationParameter $param
464
	 */
465
	public function onFailure(?TStreamNotificationParameter $param): array
466
	{
467
		return $this->raiseEvent('onFailure', $this, $param);
468
	}
469
}
470