diff --git a/README.md b/README.md index 4e4ee3d0..f9fb9e76 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,10 @@ In addition to the interface there are the following implementations provided: ([github](https://github.com/m4rw3r/php-libev)). It supports the same backends as libevent. +* `PeclEvLoop`: This uses the `libev` pecl extension that is documented on + ([php.net](http://php.net/manual/en/book.ev.php)). See + ([bitbucket](https://bitbucket.org/osmanov/pecl-ev/overview)) for source. + * `ExtEventLoop`: This uses the `event` pecl extension. It supports the same backends as libevent. diff --git a/src/Factory.php b/src/Factory.php index 9a481e35..5e49f832 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -11,6 +11,8 @@ public static function create() return new LibEventLoop(); } elseif (class_exists('libev\EventLoop', false)) { return new LibEvLoop; + } elseif (class_exists('EvLoop', false)) { + return new PeclEvLoop; } elseif (class_exists('EventBase', false)) { return new ExtEventLoop; } diff --git a/src/PeclEvLoop.php b/src/PeclEvLoop.php new file mode 100644 index 00000000..d1d57102 --- /dev/null +++ b/src/PeclEvLoop.php @@ -0,0 +1,195 @@ +loop = new EvLoop(); + $this->futureTickQueue = new FutureTickQueue($this); + $this->timers = new SplObjectStorage(); + } + + public function addReadStream($stream, callable $listener) + { + $key = (int) $stream; + + if (isset($this->readStreams[$key])) { + return; + } + + $callback = $this->getStreamListenerClosure($stream, $listener); + $event = $this->loop->io($stream, Ev::READ, $callback); + $this->readStreams[$key] = $event; + } + + /** + * @param resource $stream + * @param callable $listener + * + * @return \Closure + */ + private function getStreamListenerClosure($stream, callable $listener) { + return function () use ($stream, $listener) { + call_user_func($listener, $stream, $this); + }; + } + + public function addWriteStream($stream, callable $listener) + { + $key = (int) $stream; + + if (isset($this->writeStreams[$key])) { + return; + } + + $callback = $this->getStreamListenerClosure($stream, $listener); + $event = $this->loop->io($stream, Ev::WRITE, $callback); + $this->writeStreams[$key] = $event; + } + + public function removeReadStream($stream) + { + $key = (int) $stream; + + if (!isset($this->readStreams[$key])) { + return; + } + + $this->readStreams[$key]->stop(); + unset($this->readStreams[$key]); + } + + public function removeWriteStream($stream) + { + $key = (int) $stream; + + if (!isset($this->writeStreams[$key])) { + return; + } + + $this->writeStreams[$key]->stop(); + unset($this->writeStreams[$key]); + } + + public function removeStream($stream) + { + $this->removeReadStream($stream); + $this->removeWriteStream($stream); + } + + public function addTimer($interval, callable $callback) + { + $timer = new Timer($this, $interval, $callback, false); + + $callback = function () use ($timer) { + call_user_func($timer->getCallback(), $timer); + + if ($this->isTimerActive($timer)) { + $this->cancelTimer($timer); + } + }; + + $event = $this->loop->timer($timer->getInterval(), 0.0, $callback); + $this->timers->attach($timer, $event); + + return $timer; + } + + public function addPeriodicTimer($interval, callable $callback) + { + $timer = new Timer($this, $interval, $callback, true); + + $callback = function () use ($timer) { + call_user_func($timer->getCallback(), $timer); + }; + + //reschedule callback should be NULL to utilize $offset and $interval params + $event = $this->loop->periodic($interval, $interval, NULL, $callback); + $this->timers->attach($timer, $event); + + return $timer; + } + + public function cancelTimer(TimerInterface $timer) + { + if (!isset($this->timers[$timer])) { + return; + } + + $event = $this->timers[$timer]; + $event->stop(); + $this->timers->detach($timer); + } + + public function isTimerActive(TimerInterface $timer) + { + return $this->timers->contains($timer); + } + + public function futureTick(callable $listener) + { + $this->futureTickQueue->add($listener); + } + + public function run() + { + $this->running = true; + + while ($this->running) { + $this->futureTickQueue->tick(); + + $hasPendingCallbacks = !$this->futureTickQueue->isEmpty(); + $wasJustStopped = !$this->running; + $nothingLeftToDo = !$this->readStreams && !$this->writeStreams && !$this->timers->count(); + + $flags = Ev::RUN_ONCE; + if ($wasJustStopped || $hasPendingCallbacks) { + $flags |= Ev::RUN_NOWAIT; + } elseif ($nothingLeftToDo) { + break; + } + + $this->loop->run($flags); + } + } + + public function stop() + { + $this->running = false; + } + + public function __destruct() + { + /** @var TimerInterface $timer */ + foreach($this->timers as $timer) { + $this->cancelTimer($timer); + } + + foreach($this->readStreams as $key => $stream) { + $this->removeReadStream($key); + } + + foreach($this->writeStreams as $key => $stream) { + $this->removeWriteStream($key); + } + } +} diff --git a/tests/PeclEvLoopTest.php b/tests/PeclEvLoopTest.php new file mode 100644 index 00000000..c00deb65 --- /dev/null +++ b/tests/PeclEvLoopTest.php @@ -0,0 +1,17 @@ +markTestSkipped('pecl-ev tests skipped because ext-ev is not installed.'); + } + + return new PeclEvLoop(); + } +} \ No newline at end of file diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index e930ad37..389f0fb8 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -2,10 +2,15 @@ namespace React\Tests\EventLoop\Timer; +use React\EventLoop\LoopInterface; +use React\EventLoop\Timer\Timer; use React\Tests\EventLoop\TestCase; abstract class AbstractTimerTest extends TestCase { + /** + * @return LoopInterface + */ abstract public function createLoop(); public function testAddTimer() @@ -94,4 +99,13 @@ public function testMinimumIntervalOneMicrosecond() $this->assertEquals(0.000001, $timer->getInterval()); } + + public function testCancelNonexistentTimer() + { + $loop = $this->createLoop(); + + $timer = new Timer($loop, 1, function(){}); + + $loop->cancelTimer($timer); + } } diff --git a/tests/Timer/PeclEvLoopTimerTest.php b/tests/Timer/PeclEvLoopTimerTest.php new file mode 100644 index 00000000..0a4b3e32 --- /dev/null +++ b/tests/Timer/PeclEvLoopTimerTest.php @@ -0,0 +1,17 @@ +markTestSkipped('pecl-ev tests skipped because ext-ev is not installed.'); + } + + return new PeclEvLoop(); + } +} \ No newline at end of file diff --git a/travis-init.sh b/travis-init.sh index 87456016..955e8847 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -8,6 +8,9 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && # install 'event' PHP extension echo "yes" | pecl install event + # install 'ev' PHP extension + echo "yes" | pecl install ev + # install 'libevent' PHP extension (does not support php 7) if [[ "$TRAVIS_PHP_VERSION" != "7.0" && "$TRAVIS_PHP_VERSION" != "7.1" ]]; then
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: