--- /dev/null
+<?php
+
+namespace atick;
+
+/**
+ * Asynchronnous resource handling, optionally (ab)using ticks
+ *
+ * Example with ticks:
+ * <code>
+ * <?php
+ * declare(ticks=1);
+ *
+ * $conn = new \pq\Connection;
+ * $conn->execAsync("SELECT * FROM foo", function ($rs) {
+ * var_dump($rs);
+ * });
+ *
+ * $ticker = new \atick\Ticker;
+ * $ticker->register();
+ * $ticker->read($conn->socket, function($fd) use ($conn) {
+ * $conn->poll();
+ * if ($conn->busy) {
+ * return false;
+ * }
+ * $conn->getResult();
+ * return true;
+ * });
+ *
+ * while (count($ticker));
+ * ?>
+ * </code>
+ *
+ * And an example without ticks:
+ * <code>
+ * <?php
+ * $conn = new \pq\Connection;
+ * $conn->execAsync("SELECT * FROM foo", function ($r) {
+ * var_dump($r);
+ * });
+ *
+ * $ticker = new \atick\Ticker;
+ * $ticker->read($conn->socket, function($fd) use ($conn) {
+ * $conn->poll();
+ * if ($conn->busy) {
+ * return false;
+ * }
+ * $conn->getResult();
+ * return true;
+ * });
+ *
+ * while($ticker());
+ * ?>
+ * </code>
+ */
+class Ticker implements \Countable
+{
+ /**
+ * @var array
+ */
+ protected $read = array();
+
+ /**
+ * @var array
+ */
+ protected $write = array();
+
+ /**
+ * Register the ticker as tick function
+ * @return \atick\Ticker
+ */
+ function register() {
+ register_tick_function(array($this, "__invoke"));
+ return $this;
+ }
+
+ /**
+ * Unregister the ticker as tick function
+ * @return \atick\Ticker
+ */
+ function unregister() {
+ unregister_tick_function(array($this, "__invoke"));
+ return $this;
+ }
+
+ /**
+ * The tick handler; calls atick\Ticker::wait(0)
+ * @return int
+ */
+ function __invoke() {
+ return $this->wait(0);
+ }
+
+ /**
+ * Wait for read/write readiness on the watched fds
+ * @param float $timeout
+ * @return int count of wached fds
+ */
+ function wait($timeout = 1) {
+ $r = $w = $e = array();
+ foreach ($this->read as $s) {
+ $r[] = $s[0];
+ }
+ foreach ($this->write as $s) {
+ $w[] = $s[0];
+ }
+ $s = (int) $timeout;
+ $u = (int) (($timeout - $s) * 1000000);
+ if (($r || $w) && stream_select($r, $w, $e, $s, $u)) {
+ foreach ($r as $s) {
+ if ($this->read[(int)$s][1]($s)) {
+ unset($this->read[(int)$s]);
+ }
+ }
+ foreach ($w as $s) {
+ if ($this->write[(int)$s][1]($s)) {
+ unset($this->write[(int)$s]);
+ }
+ }
+ }
+ return $this->count();
+ }
+
+ /**
+ * @implements \Countable
+ * @return int
+ */
+ function count() {
+ return count($this->read) + count($this->write);
+ }
+
+ /**
+ * Attach a read handler; let the callback return true, to stop watching the fd.
+ * @param resource $fd
+ * @param callable $cb
+ * @return \atick\Ticker
+ */
+ function read($fd, callable $cb) {
+ $this->read[(int)$fd] = array($fd, $cb);
+ return $this;
+ }
+
+ /**
+ * Attach a write handler; let the callback return true, to stop watching the fd.
+ * @param int $fd
+ * @param callable $cb
+ * @return \atick\Ticker
+ */
+ function write($fd, callable $cb) {
+ $this->write[(int)$fd] = array($fd, $cb);
+ return $this;
+ }
+}
--- /dev/null
+<?php
+
+namespace atick;
+
+include __DIR__."/../../setup.inc";
+
+class TickerTest extends \PHPUnit_Framework_TestCase {
+
+ /**
+ * @var Ticker
+ */
+ protected $ticker;
+
+ protected function setUp() {
+ $this->ticker = new Ticker;
+ }
+
+ public function testRegister() {
+ $this->ticker->register();
+ $this->ticker->unregister();
+ $this->ticker->register();
+ $this->ticker->unregister();
+ }
+
+ public function testTicks() {
+ $file = fopen(__FILE__, "r");
+ stream_set_blocking($file, false);
+ $read = 0;
+
+ declare(ticks=1);
+ $this->ticker->register();
+ $this->ticker->read($file, function ($file) use (&$read) {
+ do {
+ $data = fread($file, 4096);
+ $read += strlen($data);
+ } while (strlen($data));
+ return feof($file);
+ });
+
+ $dummy = "This test is do tiny, ";
+ $dummy.= "we don't have to do much.";
+
+ $this->assertEquals(filesize(__FILE__), $read);
+ }
+
+ public function testBasic() {
+ $r = $w = false;
+ $this->assertCount(0, $this->ticker);
+
+ $file = fopen(__FILE__, "r");
+ stream_set_blocking($file, false);
+
+ $this->ticker->read($file, function ($file) use (&$r) {
+ return $r;
+ });
+ $this->assertCount(1, $this->ticker);
+ $this->ticker->write($file, function ($file) use (&$w) {
+ return $w;
+ });
+ $this->assertCount(2, $this->ticker);
+
+ $this->assertSame(2, $this->ticker->wait());
+ $r = true;
+ $this->assertSame(1, $this->ticker->wait());
+ $w = true;
+ $this->assertSame(0, $this->ticker->wait());
+ }
+
+}