Erebot  latest
A modular IRC bot for PHP 5.3+
Timer.php
1 <?php
2 /*
3  This file is part of Erebot, a modular IRC bot written in PHP.
4 
5  Copyright © 2010 François Poirotte
6 
7  Erebot is free software: you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation, either version 3 of the License, or
10  (at your option) any later version.
11 
12  Erebot is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with Erebot. If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 namespace Erebot;
22 
27 class Timer implements \Erebot\TimerInterface
28 {
30  protected $handle;
31 
33  protected $resource;
34 
36  protected $callback;
37 
39  protected $delay;
40 
42  protected $repeat;
43 
45  protected $args;
46 
48  static protected $binary = null;
49 
51  static protected $windowsStrategy = 0;
52 
79  public function __construct(
80  \Erebot\CallableInterface $callback,
81  $delay,
82  $repeat,
83  $args = array()
84  ) {
85  if (self::$binary === null) {
86  if (defined('PHP_BINARY')) {
87  $binary = PHP_BINARY;
88  } else {
89  $binary = PHP_BINDIR . DIRECTORY_SEPARATOR . 'php' .
90  ((!strncasecmp(PHP_OS, 'WIN', 3)) ? '.exe' : '');
91  }
92 
93  if (!strncasecmp(PHP_OS, 'WIN', 3)) {
94  self::$windowsStrategy = 1 + (
95  (int) version_compare(PHP_VERSION, '5.3.0', '>=')
96  );
97  }
98  self::$binary = '"' . $binary . '"';
99 
100  if (defined('HHVM_VERSION')) {
101  self::$binary .= ' --php';
102  }
103  }
104 
105  $this->delay = $delay;
106  $this->handle = null;
107  $this->resource = null;
108  $this->setCallback($callback);
109  $this->setRepetition($repeat);
110  $this->setArgs($args);
111  }
112 
114  public function __destruct()
115  {
116  $this->cleanup();
117  }
118 
123  protected function cleanup()
124  {
125  if ($this->resource) {
126  proc_terminate($this->resource);
127  }
128 
129  if (is_resource($this->handle)) {
130  fclose($this->handle);
131  }
132 
133  $this->handle = null;
134  $this->resource = null;
135  }
136 
137  public function setCallback(\Erebot\CallableInterface $callback)
138  {
139  $this->callback = $callback;
140  }
141 
142  public function getCallback()
143  {
144  return $this->callback;
145  }
146 
147  public function setArgs(array $args)
148  {
149  $this->args = $args;
150  }
151 
152  public function getArgs()
153  {
154  return $this->args;
155  }
156 
157  public function getDelay()
158  {
159  return $this->delay;
160  }
161 
162  public function getRepetition()
163  {
164  return $this->repeat;
165  }
166 
167  public function setRepetition($repeat)
168  {
169  // If repeat = false, then repeat = 1 (once)
170  // If repeat = true, then repeat = -1 (forever)
171  if (is_bool($repeat)) {
172  $repeat = (-intval($repeat)) * 2 + 1;
173  }
174 
175  // If repeat = null, return current value with no modification.
176  // If repeat > 0, the timer will be triggered 'repeat' times.
177  if (!is_int($repeat) && $repeat !== null) {
178  throw new \InvalidArgumentException('Invalid repetition');
179  }
180 
181  $this->repeat = $repeat;
182  }
183 
184  public function getStream()
185  {
186  return $this->handle;
187  }
188 
189  public function reset()
190  {
191  if ($this->repeat > 0) {
192  $this->repeat--;
193  } elseif (!$this->repeat) {
194  return false;
195  }
196 
197  $this->cleanup();
198 
199  if (self::$windowsStrategy == 1) {
200  // We create a temporary file to which the subprocess will write to.
201  // This makes it possible to wait for the delay to pass by using
202  // select() on this file descriptor.
203  // Simpler approaches don't work on Windows because the underlying
204  // php_select() implementation doesn't seem to support pipes.
205  // Note: this does not work anymore (tested with PHP 5.3.16),
206  // hence the second strategy below (for PHP >= 5.3.0).
207  $this->handle = tmpfile();
208  $descriptors = $this->handle;
209  } elseif (self::$windowsStrategy == 2) {
210  // Create a pair of interconnected sockets to implement the timer.
211  // Windows' firewall will throw a popup (once),
212  // but it's still better than no timers at all!
213  $pair = stream_socket_pair(
214  STREAM_PF_INET,
215  STREAM_SOCK_STREAM,
216  0
217  );
218  $descriptors = $pair[0];
219  $this->handle = $pair[1];
220  } else {
221  // On other OSes, we just use a pipe to communicate.
222  $descriptors = array('pipe', 'w');
223  }
224 
225  // Build the command that will be executed by the subprocess.
226  $command = self::$binary . ' -n -d detect_unicode=Off ' .
227  '-d display_errors=Off -d display_startup_errors=Off ' .
228  '-r "usleep('. ((int) ($this->delay * 1000000)). '); ' .
229  'var_dump(42); ' . // Required to make the subprocess send
230  // a completion notification back to us.
231  // We add the name of the callback (useful when debugging).
232  '// '.addslashes(\Erebot\CallableWrapper::represent($this->callback)).'"';
233 
234  $this->resource = proc_open(
235  $command,
236  array(1 => $descriptors),
237  $pipes,
238  null,
239  null,
240  array('bypass_shell' => true)
241  );
242 
243  if (self::$windowsStrategy == 1) {
244  // Required to remove the "read-ready" flag from the fd.
245  // The call will always return false since no data has
246  // been written to the temporary file yet.
247  fgets($this->handle);
248  } elseif (self::$windowsStrategy == 2) {
249  // Close the second socket as we have no real use for it.
250  fclose($pair[0]);
251  } else {
252  $this->handle = $pipes[1];
253  }
254 
255  return true;
256  }
257 
258  public function activate()
259  {
260  $this->cleanup();
261  $args = array_merge(array(&$this), $this->args);
262  return (bool) call_user_func_array($this->callback, $args);
263  }
264 }
Definition: CLI.php:21
__construct(\Erebot\CallableInterface $callback, $delay, $repeat, $args=array())
Definition: Timer.php:79
setRepetition($repeat)
Definition: Timer.php:167
An implementation of timers.
Definition: Timer.php:27
Interface for something that can be called.
getCallback()
Definition: Timer.php:142
__destruct()
Destroys the timer.
Definition: Timer.php:114
$delay
Delay after which the timer will expire.
Definition: Timer.php:39
setArgs(array $args)
Definition: Timer.php:147
getRepetition()
Definition: Timer.php:162
$callback
Function or method to call when the timer expires.
Definition: Timer.php:36
setCallback(\Erebot\CallableInterface $callback)
Definition: Timer.php:137
$resource
Internal resource used to implement timers.
Definition: Timer.php:33
Interface for a timer implementation.
$repeat
Number of times the timer will be reset.
Definition: Timer.php:42
$args
Additional arguments to call the callback function with.
Definition: Timer.php:45
$handle
A file descriptor which is used to implement timers.
Definition: Timer.php:30