Open Journal Systems  3.3.0
CallbackFilter.php
1 <?php
2 
3 namespace Clue\StreamFilter;
4 
5 use php_user_filter;
6 use InvalidArgumentException;
7 use ReflectionFunction;
8 use Exception;
9 
16 class CallbackFilter extends php_user_filter
17 {
18  private $callback;
19  private $closed = true;
20  private $supportsClose = false;
21 
22  public function onCreate()
23  {
24  $this->closed = false;
25 
26  if (!is_callable($this->params)) {
27  throw new InvalidArgumentException('No valid callback parameter given to stream_filter_(append|prepend)');
28  }
29  $this->callback = $this->params;
30 
31  // callback supports end event if it accepts invocation without arguments
32  $ref = new ReflectionFunction($this->callback);
33  $this->supportsClose = ($ref->getNumberOfRequiredParameters() === 0);
34 
35  return true;
36  }
37 
38  public function onClose()
39  {
40  $this->closed = true;
41 
42  // callback supports closing and is not already closed
43  if ($this->supportsClose) {
44  $this->supportsClose = false;
45  // invoke without argument to signal end and discard resulting buffer
46  try {
47  call_user_func($this->callback);
48  } catch (Exception $ignored) {
49  // this might be called during engine shutdown, so it's not safe
50  // to raise any errors or exceptions here
51  // trigger_error('Error closing filter: ' . $ignored->getMessage(), E_USER_WARNING);
52  }
53  }
54 
55  $this->callback = null;
56  }
57 
58  public function filter($in, $out, &$consumed, $closing)
59  {
60  // concatenate whole buffer from input brigade
61  $data = '';
62  while ($bucket = stream_bucket_make_writeable($in)) {
63  $consumed += $bucket->datalen;
64  $data .= $bucket->data;
65  }
66 
67  // skip processing callback that already ended
68  if ($this->closed) {
69  return PSFS_FEED_ME;
70  }
71 
72  // only invoke filter function if buffer is not empty
73  // this may skip flushing a closing filter
74  if ($data !== '') {
75  try {
76  $data = call_user_func($this->callback, $data);
77  } catch (Exception $e) {
78  // exception should mark filter as closed
79  $this->onClose();
80  trigger_error('Error invoking filter: ' . $e->getMessage(), E_USER_WARNING);
81 
82  return PSFS_ERR_FATAL;
83  }
84  }
85 
86  // mark filter as closed after processing closing chunk
87  if ($closing) {
88  $this->closed = true;
89 
90  // callback supports closing and is not already closed
91  if ($this->supportsClose) {
92  $this->supportsClose = false;
93 
94  // invoke without argument to signal end and append resulting buffer
95  try {
96  $data .= call_user_func($this->callback);
97  } catch (Exception $e) {
98  trigger_error('Error ending filter: ' . $e->getMessage(), E_USER_WARNING);
99 
100  return PSFS_ERR_FATAL;
101  }
102  }
103  }
104 
105  if ($data !== '') {
106  // create a new bucket for writing the resulting buffer to the output brigade
107  // reusing an existing bucket turned out to be bugged in some environments (ancient PHP versions and HHVM)
108  $bucket = @stream_bucket_new($this->stream, $data);
109 
110  // legacy PHP versions (PHP < 5.4) do not support passing data from the event signal handler
111  // because closing the stream invalidates the stream and its stream bucket brigade before
112  // invoking the filter close handler.
113  if ($bucket !== false) {
114  stream_bucket_append($out, $bucket);
115  }
116  }
117 
118  return PSFS_PASS_ON;
119  }
120 }
Clue\StreamFilter\CallbackFilter
Definition: CallbackFilter.php:16
Clue\StreamFilter
Definition: CallbackFilter.php:3
Clue\StreamFilter\CallbackFilter\filter
filter($in, $out, &$consumed, $closing)
Definition: CallbackFilter.php:58
Clue\StreamFilter\CallbackFilter\onClose
onClose()
Definition: CallbackFilter.php:38
Clue\StreamFilter\CallbackFilter\onCreate
onCreate()
Definition: CallbackFilter.php:22