From 6a5eafe300750e5d741f5baaf2d6fa2cea9769d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 15 Jul 2021 12:25:38 +0200 Subject: [PATCH] Simplify usage by supporting new default loop --- README.md | 29 ++++++++++++++-------------- composer.json | 8 ++++---- examples/archive.php | 5 +---- examples/attach-stream.php | 5 +---- examples/benchmark-attach.php | 10 ++++------ examples/benchmark-exec.php | 5 +---- examples/events.php | 5 +---- examples/exec-inspect.php | 5 +---- examples/exec-stream.php | 10 +++++----- examples/export.php | 7 ++----- examples/info.php | 5 +---- examples/logs-stream.php | 5 +---- examples/logs.php | 5 +---- examples/pull.php | 5 +---- examples/push.php | 5 +---- examples/resize.php | 6 +----- examples/stats.php | 5 +---- src/Client.php | 36 +++++++++++++++++++++++++++++------ tests/ClientTest.php | 20 +++++++++++++------ 19 files changed, 85 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 8c4c917..95f3068 100644 --- a/README.md +++ b/README.md @@ -58,14 +58,11 @@ Once [installed](#install), you can use the following code to access the Docker API of your local docker daemon: ```php -$loop = React\EventLoop\Factory::create(); -$client = new Clue\React\Docker\Client($loop); +$client = new Clue\React\Docker\Client(); $client->imageSearch('clue')->then(function (array $images) { var_dump($images); }); - -$loop->run(); ``` See also the [examples](examples). @@ -75,23 +72,26 @@ See also the [examples](examples). ### Client The `Client` is responsible for assembling and sending HTTP requests to the Docker Engine API. -It uses an HTTP client bound to the main [`EventLoop`](https://github.com/reactphp/event-loop#usage) -in order to handle async requests: ```php -$loop = React\EventLoop\Factory::create(); -$client = new Clue\React\Docker\Client($loop); +$client = new Clue\React\Docker\Client(); ``` +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + If your Docker Engine API is not accessible using the default `unix:///var/run/docker.sock` Unix domain socket path, you may optionally pass an explicit URL like this: -``` +```php // explicitly use given UNIX socket path -$client = new Clue\React\Docker\Client($loop, 'unix:///var/run/docker.sock'); +$client = new Clue\React\Docker\Client(null, 'unix:///var/run/docker.sock'); // or connect via TCP/IP to a remote Docker Engine API -$client = new Clue\React\Docker\Client($loop, 'http://10.0.0.2:8000/'); +$client = new Clue\React\Docker\Client(null, 'http://10.0.0.2:8000/'); ``` #### Commands @@ -154,13 +154,12 @@ The resulting blocking code could look something like this: ```php use Clue\React\Block; -$loop = React\EventLoop\Factory::create(); -$client = new Clue\React\Docker\Client($loop); +$client = new Clue\React\Docker\Client(); $promise = $client->imageInspect('busybox'); try { - $results = Block\await($promise, $loop); + $results = Block\await($promise, Loop::get()); // resporesults successfully received } catch (Exception $e) { // an error occured while performing the request @@ -175,7 +174,7 @@ $promises = array( $client->imageInspect('ubuntu'), ); -$inspections = Block\awaitAll($promises, $loop); +$inspections = Block\awaitAll($promises, Loop::get()); ``` Please refer to [clue/reactphp-block](https://github.com/clue/reactphp-block#readme) for more details. diff --git a/composer.json b/composer.json index 0c17df9..c98b7c3 100644 --- a/composer.json +++ b/composer.json @@ -19,12 +19,12 @@ "require": { "php": ">=5.3", "clue/json-stream": "^0.1", - "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3", - "react/http": "^1.0", + "react/event-loop": "^1.2", + "react/http": "^1.4", "react/promise": "^2.0 || ^1.1", "react/promise-stream": "^1.0", - "react/socket": "^1.0", - "react/stream": "^1.0", + "react/socket": "^1.8", + "react/stream": "^1.2", "rize/uri-template": "^0.3" }, "require-dev": { diff --git a/examples/archive.php b/examples/archive.php index 1a38719..06a9b2c 100644 --- a/examples/archive.php +++ b/examples/archive.php @@ -15,8 +15,7 @@ $path = isset($argv[2]) ? $argv[2] : '/etc/passwd'; echo 'Container "' . $container . '" dumping "' . $path . '" (pass as arguments to this example)' . PHP_EOL; -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); $stream = $client->containerArchiveStream($container, $path); @@ -43,5 +42,3 @@ }); $stream->pipe($tar); - -$loop->run(); diff --git a/examples/attach-stream.php b/examples/attach-stream.php index dba5e2c..d2050fd 100644 --- a/examples/attach-stream.php +++ b/examples/attach-stream.php @@ -14,8 +14,7 @@ $container = isset($argv[1]) ? $argv[1] : 'foo'; echo 'Dumping output of container "' . $container . '" (pass as argument to this example)' . PHP_EOL; -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); // use caret notation for any control characters except \t, \r and \n $caret = new Encoder("\t\r\n"); @@ -33,5 +32,3 @@ $stream->on('close', function () { echo 'CLOSED' . PHP_EOL; }); - -$loop->run(); diff --git a/examples/benchmark-attach.php b/examples/benchmark-attach.php index 9a64424..05f7f0c 100644 --- a/examples/benchmark-attach.php +++ b/examples/benchmark-attach.php @@ -11,6 +11,7 @@ // $ docker run -i --rm --log-driver=none busybox dd if=/dev/zero bs=1M count=1000 status=none | dd of=/dev/null use Clue\React\Docker\Client; +use React\EventLoop\Loop; require __DIR__ . '/../vendor/autoload.php'; @@ -26,8 +27,7 @@ $cmd = array_slice($argv, 2); } -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); $client->containerCreate(array( 'Image' => $image, @@ -38,11 +38,11 @@ 'Type' => 'none' ) ) -))->then(function ($container) use ($client, $loop) { +))->then(function ($container) use ($client) { $stream = $client->containerAttachStream($container['Id'], false, true); // we're creating the container without a log, so first wait for attach stream before starting - $loop->addTimer(0.1, function () use ($client, $container) { + Loop::addTimer(0.1, function () use ($client, $container) { $client->containerStart($container['Id'])->then(null, 'printf'); }); @@ -62,5 +62,3 @@ echo 'Received ' . $bytes . ' bytes in ' . round($time, 1) . 's => ' . round($bytes / $time / 1000000, 1) . ' MB/s' . PHP_EOL; }); }, 'printf'); - -$loop->run(); diff --git a/examples/benchmark-exec.php b/examples/benchmark-exec.php index d7a9ff2..6f7092c 100644 --- a/examples/benchmark-exec.php +++ b/examples/benchmark-exec.php @@ -29,8 +29,7 @@ $cmd = array_slice($argv, 2); } -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); $client->execCreate($container, $cmd)->then(function ($info) use ($client) { $stream = $client->execStartStream($info['Id'], true); @@ -50,5 +49,3 @@ echo 'Received ' . $bytes . ' bytes in ' . round($time, 1) . 's => ' . round($bytes / $time / 1000000, 1) . ' MB/s' . PHP_EOL; }); }, 'printf'); - -$loop->run(); diff --git a/examples/events.php b/examples/events.php index 5f9f4a2..e8b54d2 100644 --- a/examples/events.php +++ b/examples/events.php @@ -7,8 +7,7 @@ require __DIR__ . '/../vendor/autoload.php'; -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); // get a list of all events that happened up until this point // expect this list to be limited to the last 64 (or so) events @@ -25,5 +24,3 @@ }); $stream->on('error', 'printf'); - -$loop->run(); diff --git a/examples/exec-inspect.php b/examples/exec-inspect.php index 80b1bfc..aa3d6e9 100644 --- a/examples/exec-inspect.php +++ b/examples/exec-inspect.php @@ -18,8 +18,7 @@ $cmd = array_slice($argv, 2); } -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); $client->execCreate($container, $cmd)->then(function ($info) use ($client) { echo 'Created with info: ' . json_encode($info) . PHP_EOL; @@ -43,5 +42,3 @@ echo 'Response: ' . $e->getResponse()->getBody() . PHP_EOL; } }); - -$loop->run(); diff --git a/examples/exec-stream.php b/examples/exec-stream.php index 720b03b..018b569 100644 --- a/examples/exec-stream.php +++ b/examples/exec-stream.php @@ -4,6 +4,7 @@ // displays the streaming output as it happens. use Clue\React\Docker\Client; +use React\EventLoop\Loop; use React\Stream\WritableResourceStream; require __DIR__ . '/../vendor/autoload.php'; @@ -23,11 +24,10 @@ $cmd = array_slice($argv, 2); } -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); -$out = new WritableResourceStream(STDOUT, $loop); -$stderr = new WritableResourceStream(STDERR, $loop); +$out = new WritableResourceStream(STDOUT); +$stderr = new WritableResourceStream(STDERR); // unkown exit code by default $exit = 1; @@ -56,6 +56,6 @@ }); }, 'printf'); -$loop->run(); +Loop::run(); exit($exit); diff --git a/examples/export.php b/examples/export.php index 459399f..563ac2a 100644 --- a/examples/export.php +++ b/examples/export.php @@ -16,8 +16,7 @@ $target = isset($argv[2]) ? $argv[2] : ($container . '.tar'); echo 'Exporting whole container "' . $container . '" to "' . $target .'" (pass as arguments to this example)' . PHP_EOL; -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); $stream = $client->containerExportStream($container); @@ -26,7 +25,5 @@ echo 'ERROR requesting stream' . PHP_EOL . $e; }); -$out = new WritableResourceStream(fopen($target, 'w'), $loop); +$out = new WritableResourceStream(fopen($target, 'w')); $stream->pipe($out); - -$loop->run(); diff --git a/examples/info.php b/examples/info.php index 6738512..e04c602 100644 --- a/examples/info.php +++ b/examples/info.php @@ -6,11 +6,8 @@ require __DIR__ . '/../vendor/autoload.php'; -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); $client->info()->then(function ($info) { echo json_encode($info, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL; }, 'printf'); - -$loop->run(); diff --git a/examples/logs-stream.php b/examples/logs-stream.php index 5f08db7..05e317d 100644 --- a/examples/logs-stream.php +++ b/examples/logs-stream.php @@ -11,8 +11,7 @@ $container = isset($argv[1]) ? $argv[1] : 'asd'; echo 'Dumping logs (last 100 lines) of container "' . $container . '" (pass as argument to this example)' . PHP_EOL; -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); // use caret notation for any control characters except \t, \r and \n $caret = new Encoder("\t\r\n"); @@ -30,5 +29,3 @@ $stream->on('close', function ($e = null) { echo 'CLOSED' . PHP_EOL . $e; }); - -$loop->run(); diff --git a/examples/logs.php b/examples/logs.php index 75c91bd..f870712 100644 --- a/examples/logs.php +++ b/examples/logs.php @@ -15,8 +15,7 @@ $container = isset($argv[1]) ? $argv[1] : 'foo'; echo 'Dumping logs (last 100 lines) of container "' . $container . '" (pass as argument to this example)' . PHP_EOL; -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); $client->containerLogs($container, false, true, true, 0, false, 100)->then( function ($logs) { @@ -40,5 +39,3 @@ function ($error) use ($container) { EOT; } ); - -$loop->run(); diff --git a/examples/pull.php b/examples/pull.php index 3ed14d6..e79f5e2 100644 --- a/examples/pull.php +++ b/examples/pull.php @@ -10,8 +10,7 @@ $image = isset($argv[1]) ? $argv[1] : 'clue/redis-benchmark'; echo 'Pulling image "' . $image . '" (pass as argument to this example)' . PHP_EOL; -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); $stream = $client->imageCreateStream($image); @@ -22,5 +21,3 @@ $stream->on('close', function () { echo 'stream closed' . PHP_EOL; }); - -$loop->run(); diff --git a/examples/push.php b/examples/push.php index 5f5ca01..3283b25 100644 --- a/examples/push.php +++ b/examples/push.php @@ -11,11 +11,8 @@ $auth = json_decode('{"username": "string", "password": "string", "email": "string", "serveraddress" : "string", "auth": ""}'); echo 'Pushing image "' . $image . '" (pass as argument to this example)' . PHP_EOL; -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); $client->imagePush($image, null, null, $auth)->then(function ($result) { echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL; }, 'printf'); - -$loop->run(); diff --git a/examples/resize.php b/examples/resize.php index 7d72c0a..572f1b6 100644 --- a/examples/resize.php +++ b/examples/resize.php @@ -9,8 +9,7 @@ $container = isset($argv[1]) ? $argv[1] : 'asd'; -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); $client->containerInspect($container)->then(function ($info) use ($client, $container) { $size = $info['HostConfig']['ConsoleSize']; @@ -21,6 +20,3 @@ })->then(function () use ($client) { echo 'Successfully set' . PHP_EOL; }, 'printf'); - - -$loop->run(); diff --git a/examples/stats.php b/examples/stats.php index 505b42c..982c487 100644 --- a/examples/stats.php +++ b/examples/stats.php @@ -10,8 +10,7 @@ $container = isset($argv[1]) ? $argv[1] : 'asd'; echo 'Monitoring "' . $container . '" (pass as argument to this example)' . PHP_EOL; -$loop = React\EventLoop\Factory::create(); -$client = new Client($loop); +$client = new Client(); $stream = $client->containerStatsStream($container); @@ -31,5 +30,3 @@ $stream->on('close', function () { echo 'stream closed' . PHP_EOL; }); - -$loop->run(); diff --git a/src/Client.php b/src/Client.php index cb0230c..e66fe75 100644 --- a/src/Client.php +++ b/src/Client.php @@ -7,6 +7,8 @@ use React\EventLoop\LoopInterface; use React\Http\Browser; use React\Promise\PromiseInterface; +use React\Socket\FixedUriConnector; +use React\Socket\UnixConnector; use React\Stream\ReadableStreamInterface; use Rize\UriTemplate; @@ -31,23 +33,44 @@ class Client private $streamingParser; private $uri; - public function __construct(LoopInterface $loop, $url = null) + /** + * + * This class takes an optional `LoopInterface|null $loop` parameter that can be used to + * pass the event loop instance to use for this object. You can use a `null` value + * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). + * This value SHOULD NOT be given unless you're sure you want to explicitly use a + * given event loop instance. + * + * If your Docker Engine API is not accessible using the default `unix:///var/run/docker.sock` + * Unix domain socket path, you may optionally pass an explicit URL like this: + * + * ```php + * // explicitly use given UNIX socket path + * $client = new Clue\React\Docker\Client(null, 'unix:///var/run/docker.sock'); + * + * // or connect via TCP/IP to a remote Docker Engine API + * $client = new Clue\React\Docker\Client(null, 'http://10.0.0.2:8000/'); + * ``` + * + * @param ?LoopInterface $loop + * @param ?string $url + * @throws \InvalidArgumentException + */ + public function __construct(LoopInterface $loop = null, $url = null) { if ($url === null) { $url = 'unix:///var/run/docker.sock'; } - $browser = new Browser($loop); - + $connector = null; if (substr($url, 0, 7) === 'unix://') { // send everything through a local unix domain socket - $connector = new \React\Socket\FixedUriConnector( + $connector = new FixedUriConnector( $url, - new \React\Socket\UnixConnector($loop) + new UnixConnector($loop) ); // pretend all HTTP URLs to be on localhost - $browser = new Browser($loop, $connector); $url = 'http://localhost/'; } @@ -56,6 +79,7 @@ public function __construct(LoopInterface $loop, $url = null) throw new \InvalidArgumentException('Invalid Docker Engine API URL given'); } + $browser = new Browser($loop, $connector); $this->browser = $browser->withBase($url); $this->parser = new ResponseParser(); $this->streamingParser = new StreamingParser(); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index a1d1ed2..a4ef334 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -10,7 +10,6 @@ class ClientTest extends TestCase { - private $loop; private $browser; private $parser; @@ -22,13 +21,12 @@ class ClientTest extends TestCase */ public function setUpClient() { - $this->loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $this->browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); $this->parser = $this->getMockBuilder('Clue\React\Docker\Io\ResponseParser')->getMock(); $this->streamingParser = $this->getMockBuilder('Clue\React\Docker\Io\StreamingParser')->getMock(); - $this->client = new Client($this->loop); + $this->client = new Client(); $ref = new \ReflectionProperty($this->client, 'browser'); $ref->setAccessible(true); @@ -46,15 +44,25 @@ public function setUpClient() /** * @doesNotPerformAssertions */ - public function testCtor() + public function testCtorWithoutLoop() { - new Client($this->loop); + new Client(); + } + + /** + * @doesNotPerformAssertions + */ + public function testCtorWithLoop() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + new Client($loop); } public function testCtorWithInvalidUrlThrows() { $this->setExpectedException('InvalidArgumentException'); - new Client($this->loop, 'ftp://invalid'); + new Client(null, 'ftp://invalid'); } public function testPing()