Skip to content

Commit 2871e87

Browse files
committed
Initial commit
0 parents  commit 2871e87

File tree

9 files changed

+509
-0
lines changed

9 files changed

+509
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/vendor
2+
/composer.lock

.travis.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
language: php
2+
php:
3+
- 5.3
4+
- 5.6
5+
- hhvm
6+
install:
7+
- composer install --prefer-source --no-interaction
8+
script:
9+
- phpunit --coverage-text

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2015 Christian Lück
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 furnished
10+
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
21+
THE SOFTWARE.

README.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# clue/block-react [![Build Status](https://travis-ci.org/clue/php-block-react.svg?branch=master)](https://travis-ci.org/clue/php-block-react)
2+
3+
Lightweight library that eases integrating async components built for
4+
[React PHP](http://reactphp.org/) in a traditional, blocking environment.
5+
6+
> Note: This project is in early alpha stage! Feel free to report any issues you encounter.
7+
8+
## Introduction
9+
10+
[React PHP](http://reactphp.org/) provides you a great set of base components and
11+
a huge ecosystem of third party libraries in order to perform async operations.
12+
The event-driven paradigm and asynchronous processing of any number of streams
13+
in real time enables you to build a whole new set of application on top of it.
14+
This is great for building modern, scalable applications from scratch and will
15+
likely result in you relying on a whole new software architecture.
16+
17+
But let's face it: Your day-to-day business is unlikely to allow you to build
18+
everything from scratch and ditch your existing production environment.
19+
20+
This is where this library comes into play:
21+
22+
*Let's block React PHP*
23+
24+
More specifically, this library eases the pain of integrating async components
25+
into your traditional, synchronous (blocking) application stack.
26+
27+
### Quickstart example
28+
29+
The following example code demonstrates how this library can be used along with
30+
an [async HTTP client](https://github.com/clue/php-buzz-react) to process two
31+
non-blocking HTTP requests and block until the first (faster) one resolves.
32+
33+
```php
34+
function blockingExample()
35+
{
36+
// use a unique event loop instance for all parallel operations
37+
$loop = React\EventLoop\Factory::create();
38+
$blocker = new Blocker($loop);
39+
40+
// this example uses an HTTP client
41+
// this could be pretty much everything that binds to an event loop
42+
$browser = new Clue\React\Buzz\Browser($loop);
43+
44+
// set up two parallel requests
45+
$request1 = $browser->get('http://www.google.com/');
46+
$request2 = $browser->get('http://www.google.co.uk/');
47+
48+
// keep the loop running (i.e. block) until the first response arrives
49+
$fasterResponse = $blocker->awaitRace(array($request1, $request2));
50+
51+
return $fasterResponse->getBody();
52+
}
53+
```
54+
55+
## Usage
56+
57+
### Blocker
58+
59+
The `Blocker` is responsible for orchestrating the
60+
[`EventLoop`](https://github.com/reactphp/event-loop#usage)
61+
in order to make it run (block) until your conditions are fulfilled.
62+
63+
```php
64+
$loop = React\EventLoop\Factory::create();
65+
$blocker = new Blocker($loop);
66+
```
67+
68+
#### wait()
69+
70+
The `wait($seconds)` method can be used to wait/sleep for $time seconds.
71+
72+
#### awaitOne()
73+
74+
The `awaitOne(PromiseInterface $promise)` method can be used to block waiting for the given $promise to resolve.
75+
76+
#### awaitRace()
77+
78+
The `awaitRace(array $promises)` method can be used to wait for ANY of the given promises to resolve.
79+
80+
#### awaitAll()
81+
82+
The `awaitAll(array $promises)` method can be used to wait for ALL of the given promises to resolve.
83+
84+
## Install
85+
86+
The recommended way to install this library is [through composer](http://getcomposer.org). [New to composer?](http://getcomposer.org/doc/00-intro.md)
87+
88+
```JSON
89+
{
90+
"require": {
91+
"clue/block-react": "dev-master"
92+
}
93+
}
94+
```
95+
96+
## License
97+
98+
MIT

composer.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "clue/block-react",
3+
"description": "Integrate async React PHP components into your blocking environment",
4+
"keywords": ["Event Loop", "blocking", "synchronous", "Promise", "ReactPHP", "async"],
5+
"homepage": "https://github.com/clue/php-block-react",
6+
"license": "MIT",
7+
"authors": [
8+
{
9+
"name": "Christian Lück",
10+
"email": "christian@lueck.tv"
11+
}
12+
],
13+
"autoload": {
14+
"psr-4": { "Clue\\React\\Block\\": "src/" }
15+
},
16+
"require": {
17+
"php": ">=5.3",
18+
"react/event-loop": "0.4.*|0.3.*",
19+
"react/promise": "~2.0|~1.0"
20+
}
21+
}

phpunit.xml.dist

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit bootstrap="tests/bootstrap.php"
4+
colors="true"
5+
convertErrorsToExceptions="true"
6+
convertNoticesToExceptions="true"
7+
convertWarningsToExceptions="true"
8+
>
9+
<testsuites>
10+
<testsuite name="Block React Test Suite">
11+
<directory>./tests/</directory>
12+
</testsuite>
13+
</testsuites>
14+
<filter>
15+
<whitelist>
16+
<directory>./src/</directory>
17+
</whitelist>
18+
</filter>
19+
</phpunit>

src/Blocker.php

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
<?php
2+
3+
namespace Clue\React\Block;
4+
5+
use React\EventLoop\LoopInterface;
6+
use React\Promise\PromiseInterface;
7+
use React\Promise\CancellablePromiseInterface;
8+
use UnderflowException;
9+
use Exception;
10+
11+
class Blocker
12+
{
13+
private $loop;
14+
15+
public function __construct(LoopInterface $loop)
16+
{
17+
$this->loop = $loop;
18+
}
19+
20+
/**
21+
* wait/sleep for $time seconds
22+
*
23+
* @param float $time
24+
*/
25+
public function wait($time)
26+
{
27+
$loop = $this->loop;
28+
$loop->addTimer($time, function () use ($loop) {
29+
$loop->stop();
30+
});
31+
$loop->run();
32+
}
33+
34+
/**
35+
* block waiting for the given $promise to resolve
36+
*
37+
* @param PromiseInterface $promise
38+
* @return mixed returns whatever the promise resolves to
39+
* @throws Exception when the promise is rejected
40+
*/
41+
public function awaitOne(PromiseInterface $promise)
42+
{
43+
$resolved = null;
44+
$exception = null;
45+
46+
$promise->then(
47+
function ($c) use (&$resolved) {
48+
$resolved = $c;
49+
},
50+
function ($error) use (&$exception) {
51+
$exception = $error;
52+
}
53+
);
54+
55+
while ($resolved === null && $exception === null) {
56+
$this->loop->tick();
57+
}
58+
59+
if ($exception !== null) {
60+
throw $exception;
61+
}
62+
63+
return $resolved;
64+
}
65+
66+
/**
67+
* wait for ANY of the given promises to resolve
68+
*
69+
* Once the first promise is resolved, this will try to cancel() all
70+
* remaining promises and return whatever the first promise resolves to.
71+
*
72+
* If ALL promises fail to resolve, this will fail and throw an Exception.
73+
*
74+
* @param array $promises
75+
* @return mixed returns whatever the first promise resolves to
76+
* @throws Exception if ALL promises are rejected
77+
*/
78+
public function awaitRace(array $promises)
79+
{
80+
$wait = count($promises);
81+
$value = null;
82+
$success = false;
83+
84+
foreach ($promises as $key => $promise) {
85+
/* @var $promise PromiseInterface */
86+
$promise->then(
87+
function ($return) use (&$value, &$wait, &$success, $promises) {
88+
if (!$wait) {
89+
// only store first promise value
90+
return;
91+
}
92+
$value = $return;
93+
$wait = 0;
94+
$success = true;
95+
96+
// cancel all remaining promises
97+
foreach ($promises as $promise) {
98+
if ($promise instanceof CancellablePromiseInterface) {
99+
$promise->cancel();
100+
}
101+
}
102+
},
103+
function ($e) use (&$wait) {
104+
if ($wait) {
105+
// count number of promises to await
106+
// cancelling promises will reject all remaining ones, ignore this
107+
--$wait;
108+
}
109+
}
110+
);
111+
}
112+
113+
while ($wait) {
114+
$this->loop->tick();
115+
}
116+
117+
if (!$success) {
118+
throw new UnderflowException('No promise could resolve');
119+
}
120+
121+
return $value;
122+
}
123+
124+
/**
125+
* wait for ALL of the given promises to resolve
126+
*
127+
* Once the last promise resolves, this will return an array with whatever
128+
* each promise resolves to. Array keys will be left intact, i.e. they can
129+
* be used to correlate the return array to the promises passed.
130+
*
131+
* If ANY promise fails to resolve, this will try to cancel() all
132+
* remaining promises and throw an Exception.
133+
*
134+
* @param array $promises
135+
* @return array returns an array with whatever each promise resolves to
136+
* @throws Exception when ANY promise is rejected
137+
*/
138+
public function awaitAll(array $promises)
139+
{
140+
$wait = count($promises);
141+
$exception = null;
142+
$values = array();
143+
144+
foreach ($promises as $key => $promise) {
145+
/* @var $promise PromiseInterface */
146+
$promise->then(
147+
function ($value) use (&$values, $key, &$wait) {
148+
$values[$key] = $value;
149+
--$wait;
150+
},
151+
function ($e) use ($promises, &$exception, &$wait) {
152+
if (!$wait) {
153+
// cancelling promises will reject all remaining ones, only store first error
154+
return;
155+
}
156+
157+
$exception = $e;
158+
$wait = 0;
159+
160+
// cancel all remaining promises
161+
foreach ($promises as $promise) {
162+
if ($promise instanceof CancellablePromiseInterface) {
163+
$promise->cancel();
164+
}
165+
}
166+
}
167+
);
168+
}
169+
170+
while ($wait) {
171+
$this->loop->tick();
172+
}
173+
174+
if ($exception !== null) {
175+
throw $exception;
176+
}
177+
178+
return $values;
179+
}
180+
}

0 commit comments

Comments
 (0)