├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Loop.php └── Loop │ ├── Driver.php │ ├── DriverFactory.php │ ├── InvalidWatcherException.php │ └── UnsupportedFeatureException.php └── test ├── DummyDriver.php ├── LoopStateTest.php └── LoopTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /composer.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5 5 | - 5.6 6 | - 7.0 7 | - nightly 8 | - hhvm 9 | 10 | matrix: 11 | allow_failures: 12 | - php: nightly 13 | fast_finish: true 14 | 15 | cache: 16 | directories: 17 | - $HOME/.composer/cache 18 | 19 | install: 20 | - composer install 21 | - composer show -t 22 | 23 | script: 24 | - php vendor/bin/parallel-lint --exclude vendor . 25 | - php vendor/bin/phpunit --coverage-text 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 PHP Asynchronous Interoperability Group 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Event Loop Interopability 2 | 3 | The purpose of this specification is to provide a common interface for 4 | event loop implementations. This allows libraries and components from 5 | different vendors to operate in an event driven architecture, sharing a 6 | common event loop. 7 | 8 | ## Current Status 9 | 10 | This project is currently on hold and to be seen as failed for now. It might be reconsidered at a later point in time. The specification in its current state has been merged into [Amp](https://github.com/amphp/amp). Interoperability between [ReactPHP](https://github.com/reactphp/event-loop) and Amp will be solved via adapters instead of a common interface. [Icicle](https://github.com/icicleio/icicle) has been deprecated and parts of it been merged into Amp libraries. 11 | 12 | ## Why Bother? 13 | 14 | Some programming languages, such as Javascript, have an event loop that is 15 | native to the execution environment. This allows package vendors to easily 16 | create asynchronous software that uses this native event loop. Although PHP 17 | is historically a synchronous programming environment, it is still possible 18 | to use asynchronous programming techniques. Using these techniques, package 19 | vendors have created event loop implementations that have seen success. 20 | 21 | However, as these event loop implementations are from package vendors, it 22 | is not yet possible to create event driven software components that are 23 | independent of the underlying event loop implementation. By creating a 24 | common interface for an event loop, interoperability of this nature will 25 | be possible. 26 | 27 | ## Goals 28 | 29 | The functionality exposed by this interface should include the ability to: 30 | 31 | - Watch input streams for available data 32 | - Watch output streams for the ability to perform non-blocking write operations 33 | - Run single and periodic timers 34 | - Listen for signals 35 | - Defer the execution of callables 36 | 37 | ## Implementations 38 | 39 | You can find [available implementations on Packagist](https://packagist.org/providers/async-interop/event-loop-implementation). 40 | 41 | ## Compatible Packages 42 | 43 | You can find [compatible packages on Packagist](https://packagist.org/packages/async-interop/event-loop/dependents). 44 | 45 | ## Contributors 46 | 47 | * [Aaron Piotrowski](https://github.com/trowski) 48 | * [Andrew Carter](https://github.com/AndrewCarterUK) 49 | * [Bob Weinand](https://github.com/bwoebi) 50 | * [Cees-Jan Kiewiet](https://github.com/WyriHaximus) 51 | * [Christopher Pitt](https://github.com/assertchris) 52 | * [Daniel Lowrey](https://github.com/rdlowrey) 53 | * [Niklas Keller](https://github.com/kelunik) 54 | * [Stephen M. Coakley](https://github.com/coderstephen) 55 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-interop/event-loop", 3 | "description": "An event loop interface for interoperability.", 4 | "keywords": ["event", "loop", "async", "interop"], 5 | "license": "MIT", 6 | "require": { 7 | "php": ">=5.5.0" 8 | }, 9 | "require-dev": { 10 | "phpunit/phpunit": "^4|^5", 11 | "jakub-onderka/php-parallel-lint": "^0.9.2", 12 | "jakub-onderka/php-console-highlighter": "^0.3.2" 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "AsyncInterop\\": "src" 17 | } 18 | }, 19 | "autoload-dev": { 20 | "psr-4": { 21 | "AsyncInterop\\Loop\\Test\\": "test" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ./test 5 | 6 | 7 | 8 | 9 | ./src 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Loop.php: -------------------------------------------------------------------------------- 1 | 0) { 46 | throw new \RuntimeException("Setting a new factory while running isn't allowed!"); 47 | } 48 | 49 | self::$factory = $factory; 50 | 51 | // reset it here, it will be actually instantiated inside execute() or get() 52 | self::$driver = null; 53 | } 54 | 55 | /** 56 | * Execute a callback within the scope of an event loop driver. 57 | * 58 | * The loop MUST continue to run until it is either stopped explicitly, no referenced watchers exist anymore, or an 59 | * exception is thrown that cannot be handled. Exceptions that cannot be handled are exceptions thrown from an 60 | * error handler or exceptions that would be passed to an error handler but none exists to handle them. 61 | * 62 | * @param callable $callback The callback to execute. 63 | * @param Driver $driver The event loop driver. If `null`, a new one is created from the set factory. 64 | * 65 | * @return void 66 | * 67 | * @see \AsyncInterop\Loop::setFactory() 68 | */ 69 | public static function execute(callable $callback, Driver $driver = null) 70 | { 71 | $previousDriver = self::$driver; 72 | 73 | self::$driver = $driver ?: self::createDriver(); 74 | self::$level++; 75 | 76 | try { 77 | self::$driver->defer($callback); 78 | self::$driver->run(); 79 | } finally { 80 | self::$driver = $previousDriver; 81 | self::$level--; 82 | } 83 | } 84 | 85 | /** 86 | * Create a new driver if a factory is present, otherwise throw. 87 | * 88 | * @return Driver 89 | * 90 | * @throws \Exception If no factory is set or no driver returned from factory. 91 | */ 92 | private static function createDriver() 93 | { 94 | if (self::$factory === null) { 95 | throw new \Exception("No loop driver factory set; Either pass a driver to Loop::execute or set a factory."); 96 | } 97 | 98 | $driver = self::$factory->create(); 99 | 100 | if (!$driver instanceof Driver) { 101 | $type = is_object($driver) ? "an instance of " . get_class($driver) : gettype($driver); 102 | throw new \Exception("Loop driver factory returned {$type}, but must return an instance of Driver."); 103 | } 104 | 105 | return $driver; 106 | } 107 | 108 | /** 109 | * Retrieve the event loop driver that is in scope. 110 | * 111 | * @return Driver 112 | */ 113 | public static function get() 114 | { 115 | if (self::$driver) { 116 | return self::$driver; 117 | } 118 | 119 | return self::$driver = self::createDriver(); 120 | } 121 | 122 | /** 123 | * Stop the event loop. 124 | * 125 | * When an event loop is stopped, it continues with its current tick and exits the loop afterwards. Multiple calls 126 | * to stop MUST be ignored and MUST NOT raise an exception. 127 | * 128 | * @return void 129 | */ 130 | public static function stop() 131 | { 132 | $driver = self::$driver ?: self::get(); 133 | $driver->stop(); 134 | } 135 | 136 | /** 137 | * Defer the execution of a callback. 138 | * 139 | * The deferred callable MUST be executed before any other type of watcher in a tick. Order of enabling MUST be 140 | * preserved when executing the callbacks. 141 | * 142 | * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) 143 | * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. 144 | * 145 | * @param callable(string $watcherId, mixed $data) $callback The callback to defer. The `$watcherId` will be 146 | * invalidated before the callback call. 147 | * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. 148 | * 149 | * @return string An unique identifier that can be used to cancel, enable or disable the watcher. 150 | */ 151 | public static function defer(callable $callback, $data = null) 152 | { 153 | $driver = self::$driver ?: self::get(); 154 | return $driver->defer($callback, $data); 155 | } 156 | 157 | /** 158 | * Delay the execution of a callback. 159 | * 160 | * The delay is a minimum and approximate, accuracy is not guaranteed. Order of calls MUST be determined by which 161 | * timers expire first, but timers with the same expiration time MAY be executed in any order. 162 | * 163 | * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) 164 | * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. 165 | * 166 | * @param int $delay The amount of time, in milliseconds, to delay the execution for. 167 | * @param callable(string $watcherId, mixed $data) $callback The callback to delay. The `$watcherId` will be 168 | * invalidated before the callback call. 169 | * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. 170 | * 171 | * @return string An unique identifier that can be used to cancel, enable or disable the watcher. 172 | */ 173 | public static function delay($time, callable $callback, $data = null) 174 | { 175 | $driver = self::$driver ?: self::get(); 176 | return $driver->delay($time, $callback, $data); 177 | } 178 | 179 | /** 180 | * Repeatedly execute a callback. 181 | * 182 | * The interval between executions is a minimum and approximate, accuracy is not guaranteed. Order of calls MUST be 183 | * determined by which timers expire first, but timers with the same expiration time MAY be executed in any order. 184 | * The first execution is scheduled after the first interval period. 185 | * 186 | * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) 187 | * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. 188 | * 189 | * @param int $interval The time interval, in milliseconds, to wait between executions. 190 | * @param callable(string $watcherId, mixed $data) $callback The callback to repeat. 191 | * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. 192 | * 193 | * @return string An unique identifier that can be used to cancel, enable or disable the watcher. 194 | */ 195 | public static function repeat($interval, callable $callback, $data = null) 196 | { 197 | $driver = self::$driver ?: self::get(); 198 | return $driver->repeat($interval, $callback, $data); 199 | } 200 | 201 | /** 202 | * Execute a callback when a stream resource becomes readable or is closed for reading. 203 | * 204 | * Warning: Closing resources locally, e.g. with `fclose`, might not invoke the callback. Be sure to `cancel` the 205 | * watcher when closing the resource locally. Drivers MAY choose to notify the user if there are watchers on invalid 206 | * resources, but are not required to, due to the high performance impact. Watchers on closed resources are 207 | * therefore undefined behavior. 208 | * 209 | * Multiple watchers on the same stream MAY be executed in any order. 210 | * 211 | * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) 212 | * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. 213 | * 214 | * @param resource $stream The stream to monitor. 215 | * @param callable(string $watcherId, resource $stream, mixed $data) $callback The callback to execute. 216 | * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. 217 | * 218 | * @return string An unique identifier that can be used to cancel, enable or disable the watcher. 219 | */ 220 | public static function onReadable($stream, callable $callback, $data = null) 221 | { 222 | $driver = self::$driver ?: self::get(); 223 | return $driver->onReadable($stream, $callback, $data); 224 | } 225 | 226 | /** 227 | * Execute a callback when a stream resource becomes writable or is closed for writing. 228 | * 229 | * Warning: Closing resources locally, e.g. with `fclose`, might not invoke the callback. Be sure to `cancel` the 230 | * watcher when closing the resource locally. Drivers MAY choose to notify the user if there are watchers on invalid 231 | * resources, but are not required to, due to the high performance impact. Watchers on closed resources are 232 | * therefore undefined behavior. 233 | * 234 | * Multiple watchers on the same stream MAY be executed in any order. 235 | * 236 | * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) 237 | * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. 238 | * 239 | * @param resource $stream The stream to monitor. 240 | * @param callable(string $watcherId, resource $stream, mixed $data) $callback The callback to execute. 241 | * @param mixed $data Arbitrary data given to the callback function as the `$data` parameter. 242 | * 243 | * @return string An unique identifier that can be used to cancel, enable or disable the watcher. 244 | */ 245 | public static function onWritable($stream, callable $callback, $data = null) 246 | { 247 | $driver = self::$driver ?: self::get(); 248 | return $driver->onWritable($stream, $callback, $data); 249 | } 250 | 251 | /** 252 | * Execute a callback when a signal is received. 253 | * 254 | * Warning: Installing the same signal on different instances of this interface is deemed undefined behavior. 255 | * Implementations MAY try to detect this, if possible, but are not required to. This is due to technical 256 | * limitations of the signals being registered globally per process. 257 | * 258 | * Multiple watchers on the same signal MAY be executed in any order. 259 | * 260 | * The created watcher MUST immediately be marked as enabled, but only be activated (i.e. callback can be called) 261 | * right before the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. 262 | * 263 | * @param int $signo The signal number to monitor. 264 | * @param callable(string $watcherId, int $signo, mixed $data) $callback The callback to execute. 265 | * @param mixed $data Arbitrary data given to the callback function as the $data parameter. 266 | * 267 | * @return string An unique identifier that can be used to cancel, enable or disable the watcher. 268 | * 269 | * @throws UnsupportedFeatureException If signal handling is not supported. 270 | */ 271 | public static function onSignal($signo, callable $callback, $data = null) 272 | { 273 | $driver = self::$driver ?: self::get(); 274 | return $driver->onSignal($signo, $callback, $data); 275 | } 276 | 277 | /** 278 | * Enable a watcher to be active starting in the next tick. 279 | * 280 | * Watchers MUST immediately be marked as enabled, but only be activated (i.e. callbacks can be called) right before 281 | * the next tick. Callbacks of watchers MUST NOT be called in the tick they were enabled. 282 | * 283 | * @param string $watcherId The watcher identifier. 284 | * 285 | * @return void 286 | * 287 | * @throws InvalidWatcherException If the watcher identifier is invalid. 288 | */ 289 | public static function enable($watcherId) 290 | { 291 | $driver = self::$driver ?: self::get(); 292 | $driver->enable($watcherId); 293 | } 294 | 295 | /** 296 | * Disable a watcher immediately. 297 | * 298 | * A watcher MUST be disabled immediately, e.g. if a defer watcher disables a later defer watcher, the second defer 299 | * watcher isn't executed in this tick. 300 | * 301 | * Disabling a watcher MUST NOT invalidate the watcher. Calling this function MUST NOT fail, even if passed an 302 | * invalid watcher. 303 | * 304 | * @param string $watcherId The watcher identifier. 305 | * 306 | * @return void 307 | */ 308 | public static function disable($watcherId) 309 | { 310 | $driver = self::$driver ?: self::get(); 311 | $driver->disable($watcherId); 312 | } 313 | 314 | /** 315 | * Cancel a watcher. 316 | * 317 | * This will detatch the event loop from all resources that are associated to the watcher. After this operation the 318 | * watcher is permanently invalid. Calling this function MUST NOT fail, even if passed an invalid watcher. 319 | * 320 | * @param string $watcherId The watcher identifier. 321 | * 322 | * @return void 323 | */ 324 | public static function cancel($watcherId) 325 | { 326 | $driver = self::$driver ?: self::get(); 327 | $driver->cancel($watcherId); 328 | } 329 | 330 | /** 331 | * Reference a watcher. 332 | * 333 | * This will keep the event loop alive whilst the watcher is still being monitored. Watchers have this state by 334 | * default. 335 | * 336 | * @param string $watcherId The watcher identifier. 337 | * 338 | * @return void 339 | * 340 | * @throws InvalidWatcherException If the watcher identifier is invalid. 341 | */ 342 | public static function reference($watcherId) 343 | { 344 | $driver = self::$driver ?: self::get(); 345 | $driver->reference($watcherId); 346 | } 347 | 348 | /** 349 | * Unreference a watcher. 350 | * 351 | * The event loop should exit the run method when only unreferenced watchers are still being monitored. Watchers 352 | * are all referenced by default. 353 | * 354 | * @param string $watcherId The watcher identifier. 355 | * 356 | * @return void 357 | * 358 | * @throws InvalidWatcherException If the watcher identifier is invalid. 359 | */ 360 | public static function unreference($watcherId) 361 | { 362 | $driver = self::$driver ?: self::get(); 363 | $driver->unreference($watcherId); 364 | } 365 | 366 | /** 367 | * Stores information in the loop bound registry. 368 | * 369 | * This can be used to store loop bound information. Stored information is package private. Packages MUST NOT 370 | * retrieve the stored state of other packages. Packages MUST use the following prefix for keys: `vendor.package.` 371 | * 372 | * @param string $key The namespaced storage key. 373 | * @param mixed $value The value to be stored. 374 | * 375 | * @return void 376 | */ 377 | public static function setState($key, $value) 378 | { 379 | $driver = self::$driver ?: self::get(); 380 | $driver->setState($key, $value); 381 | } 382 | 383 | /** 384 | * Gets information stored bound to the loop. 385 | * 386 | * Stored information is package private. Packages MUST NOT retrieve the stored state of other packages. Packages 387 | * MUST use the following prefix for keys: `vendor.package.` 388 | * 389 | * @param string $key The namespaced storage key. 390 | * 391 | * @return mixed The previously stored value or `null` if it doesn't exist. 392 | */ 393 | public static function getState($key) 394 | { 395 | $driver = self::$driver ?: self::get(); 396 | return $driver->getState($key); 397 | } 398 | 399 | /** 400 | * Set a callback to be executed when an error occurs. 401 | * 402 | * The callback receives the error as the first and only parameter. The return value of the callback gets ignored. 403 | * If it can't handle the error, it MUST throw the error. Errors thrown by the callback or during its invocation 404 | * MUST be thrown into the `run` loop and stop the driver. 405 | * 406 | * Subsequent calls to this method will overwrite the previous handler. 407 | * 408 | * @param callable(\Throwable|\Exception $error)|null $callback The callback to execute. `null` will clear the 409 | * current handler. 410 | * 411 | * @return callable(\Throwable|\Exception $error)|null The previous handler, `null` if there was none. 412 | */ 413 | public static function setErrorHandler(callable $callback = null) 414 | { 415 | $driver = self::$driver ?: self::get(); 416 | return $driver->setErrorHandler($callback); 417 | } 418 | 419 | /** 420 | * Retrieve an associative array of information about the event loop driver. 421 | * 422 | * The returned array MUST contain the following data describing the driver's currently registered watchers: 423 | * 424 | * [ 425 | * "defer" => ["enabled" => int, "disabled" => int], 426 | * "delay" => ["enabled" => int, "disabled" => int], 427 | * "repeat" => ["enabled" => int, "disabled" => int], 428 | * "on_readable" => ["enabled" => int, "disabled" => int], 429 | * "on_writable" => ["enabled" => int, "disabled" => int], 430 | * "on_signal" => ["enabled" => int, "disabled" => int], 431 | * "enabled_watchers" => ["referenced" => int, "unreferenced" => int], 432 | * ]; 433 | * 434 | * Implementations MAY optionally add more information in the array but at minimum the above `key => value` format 435 | * MUST always be provided. 436 | * 437 | * @return array Statistics about the loop in the described format. 438 | */ 439 | public static function getInfo() 440 | { 441 | $driver = self::$driver ?: self::get(); 442 | return $driver->getInfo(); 443 | } 444 | 445 | /** 446 | * Disable construction as this is a static class. 447 | */ 448 | private function __construct() 449 | { 450 | // intentionally left blank 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /src/Loop/Driver.php: -------------------------------------------------------------------------------- 1 | registry[$key]); 248 | } else { 249 | $this->registry[$key] = $value; 250 | } 251 | } 252 | 253 | /** 254 | * Gets information stored bound to the loop. 255 | * 256 | * Stored information is package private. Packages MUST NOT retrieve the stored state of other packages. Packages 257 | * MUST use the following prefix for keys: `vendor.package.` 258 | * 259 | * @param string $key The namespaced storage key. 260 | * 261 | * @return mixed The previously stored value or `null` if it doesn't exist. 262 | */ 263 | final public function getState($key) 264 | { 265 | return isset($this->registry[$key]) ? $this->registry[$key] : null; 266 | } 267 | 268 | /** 269 | * Set a callback to be executed when an error occurs. 270 | * 271 | * The callback receives the error as the first and only parameter. The return value of the callback gets ignored. 272 | * If it can't handle the error, it MUST throw the error. Errors thrown by the callback or during its invocation 273 | * MUST be thrown into the `run` loop and stop the driver. 274 | * 275 | * Subsequent calls to this method will overwrite the previous handler. 276 | * 277 | * @param callable(\Throwable|\Exception $error)|null $callback The callback to execute. `null` will clear the 278 | * current handler. 279 | * 280 | * @return callable(\Throwable|\Exception $error)|null The previous handler, `null` if there was none. 281 | */ 282 | abstract public function setErrorHandler(callable $callback = null); 283 | 284 | /** 285 | * Retrieve an associative array of information about the event loop driver. 286 | * 287 | * The returned array MUST contain the following data describing the driver's currently registered watchers: 288 | * 289 | * [ 290 | * "defer" => ["enabled" => int, "disabled" => int], 291 | * "delay" => ["enabled" => int, "disabled" => int], 292 | * "repeat" => ["enabled" => int, "disabled" => int], 293 | * "on_readable" => ["enabled" => int, "disabled" => int], 294 | * "on_writable" => ["enabled" => int, "disabled" => int], 295 | * "on_signal" => ["enabled" => int, "disabled" => int], 296 | * "enabled_watchers" => ["referenced" => int, "unreferenced" => int], 297 | * ]; 298 | * 299 | * Implementations MAY optionally add more information in the array but at minimum the above `key => value` format 300 | * MUST always be provided. 301 | * 302 | * @return array Statistics about the loop in the described format. 303 | */ 304 | abstract public function getInfo(); 305 | 306 | /** 307 | * Get the underlying loop handle. 308 | * 309 | * Example: the `uv_loop` resource for `libuv` or the `EvLoop` object for `libev` or `null` for a native driver. 310 | * 311 | * Note: This function is *not* exposed in the `Loop` class. Users shall access it directly on the respective loop 312 | * instance. 313 | * 314 | * @return null|object|resource The loop handle the event loop operates on. `null` if there is none. 315 | */ 316 | abstract public function getHandle(); 317 | } 318 | -------------------------------------------------------------------------------- /src/Loop/DriverFactory.php: -------------------------------------------------------------------------------- 1 | watcherId = $watcherId; 22 | 23 | if ($message === null) { 24 | $message = "An invalid watcher identifier has been used: '{$watcherId}'"; 25 | } 26 | 27 | parent::__construct($message); 28 | } 29 | 30 | /** 31 | * @return string The watcher identifier. 32 | */ 33 | public function getWatcherId() 34 | { 35 | return $this->watcherId; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Loop/UnsupportedFeatureException.php: -------------------------------------------------------------------------------- 1 | defers)) { 15 | try { 16 | $defer(self::$id++, $data); 17 | } catch (\Exception $e) { 18 | if ($handler = $this->handler) { 19 | $handler($e); 20 | } else { 21 | throw $e; 22 | } 23 | } 24 | } 25 | } 26 | 27 | public function defer(callable $callback, $data = null) { 28 | $this->defers[] = [$callback, $data]; 29 | } 30 | 31 | public function setErrorHandler(callable $callback = null) { 32 | $this->handler = $callback; 33 | } 34 | 35 | public function stop() {} 36 | public function delay($delay, callable $callback, $data = null) { return self::$id++; } 37 | public function repeat($interval, callable $callback, $data = null) { return self::$id++; } 38 | public function onReadable($stream, callable $callback, $data = null) { return self::$id++; } 39 | public function onWritable($stream, callable $callback, $data = null) { return self::$id++; } 40 | public function onSignal($signo, callable $callback, $data = null) { return self::$id++; } 41 | public function enable($watcherId) {} 42 | public function disable($watcherId) {} 43 | public function cancel($watcherId) {} 44 | public function reference($watcherId) {} 45 | public function unreference($watcherId) {} 46 | public function getInfo() {} 47 | public function getHandle() {} 48 | } 49 | -------------------------------------------------------------------------------- /test/LoopStateTest.php: -------------------------------------------------------------------------------- 1 | loop = $this->getMockForAbstractClass(Driver::class); 12 | } 13 | 14 | /** @test */ 15 | public function defaultsToNull() 16 | { 17 | $this->assertNull($this->loop->getState("foobar")); 18 | } 19 | 20 | /** 21 | * @test 22 | * @dataProvider provideValues 23 | */ 24 | public function getsPreviouslySetValue($value) 25 | { 26 | $this->loop->setState("foobar", $value); 27 | $this->assertSame($value, $this->loop->getState("foobar")); 28 | } 29 | 30 | public function provideValues() 31 | { 32 | return [ 33 | ["string"], 34 | [42], 35 | [1.001], 36 | [true], 37 | [false], 38 | [null], 39 | [new \StdClass], 40 | ]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/LoopTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(Loop\DriverFactory::class)->getMock(); 22 | $factory->method("create")->willReturn($driver); 23 | 24 | Loop::setFactory($factory); 25 | 26 | Loop::execute(function () use ($factory) { 27 | Loop::setFactory($factory); 28 | }); 29 | } 30 | 31 | /** @test */ 32 | public function executeStackReturnsScopedDriver() { 33 | $driver1 = new DummyDriver; 34 | $driver2 = new DummyDriver; 35 | 36 | Loop::execute(function () use ($driver1, $driver2) { 37 | $this->assertSame($driver1, Loop::get()); 38 | 39 | Loop::execute(function () use ($driver2) { 40 | $this->assertSame($driver2, Loop::get()); 41 | }, $driver2); 42 | 43 | $this->assertSame($driver1, Loop::get()); 44 | }, $driver1); 45 | } 46 | } 47 | --------------------------------------------------------------------------------