diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..da20d18
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,7 @@
+/.gitattributes export-ignore
+/.github/workflows/ export-ignore
+/.gitignore export-ignore
+/examples/ export-ignore
+/phpunit.xml.dist export-ignore
+/phpunit.xml.legacy export-ignore
+/tests/ export-ignore
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..9c09fb8
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: clue
+custom: https://clue.engineering/support
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..b521e6d
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,37 @@
+name: CI
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ PHPUnit:
+ name: PHPUnit (PHP ${{ matrix.php }})
+ runs-on: ubuntu-22.04
+ strategy:
+ matrix:
+ php:
+ - 8.3
+ - 8.2
+ - 8.1
+ - 8.0
+ - 7.4
+ - 7.3
+ - 7.2
+ - 7.1
+ - 7.0
+ - 5.6
+ - 5.5
+ - 5.4
+ - 5.3
+ steps:
+ - uses: actions/checkout@v4
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: xdebug
+ - run: composer install
+ - run: vendor/bin/phpunit --coverage-text
+ if: ${{ matrix.php >= 7.3 }}
+ - run: vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy
+ if: ${{ matrix.php < 7.3 }}
diff --git a/.gitignore b/.gitignore
index de4a392..4fbb073 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,2 @@
-/vendor
+/vendor/
/composer.lock
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 9fbb7fa..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-language: php
-
-php:
-# - 5.3 # requires old distro, see below
- - 5.4
- - 5.5
- - 5.6
- - 7.0
- - 7.1
- - 7.2
- - 7.3
-
-# lock distro so future defaults will not break the build
-dist: trusty
-
-matrix:
- include:
- - php: 5.3
- dist: precise
-
-sudo: false
-
-install:
- - composer install --no-interaction
-
-script:
- - vendor/bin/phpunit --coverage-text
diff --git a/README.md b/README.md
index 339a1ac..4708425 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,7 @@
-# clue/reactphp-shell [](https://travis-ci.org/clue/reactphp-shell)
+# clue/reactphp-shell
+
+[](https://github.com/clue/reactphp-shell/actions)
+[](https://packagist.org/packages/clue/shell-react)
Run async commands within any interactive shell command, built on top of [ReactPHP](https://reactphp.org/).
@@ -10,25 +13,30 @@ Once [installed](#install), you can use the following code to run an interactive
bash shell and issue some commands within:
```php
-$loop = React\EventLoop\Factory::create();
-$launcher = new ProcessLauncher($loop);
+createDeferredShell('bash');
$shell->execute('echo -n $USER')->then(function ($result) {
var_dump('current user', $result);
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
$shell->execute('env | sort | head -n10')->then(function ($env) {
var_dump('env', $env);
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
$shell->end();
-
-$loop->run();
```
-See also the [examples](examples):
+See also the [examples](examples/):
* [Run shell commands within a bash shell](examples/bash.php)
* [Run PHP code within an interactive PHP shell](examples/php.php)
@@ -36,34 +44,34 @@ See also the [examples](examples):
## Install
-The recommended way to install this library is [through Composer](https://getcomposer.org).
+The recommended way to install this library is [through Composer](https://getcomposer.org/).
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
This will install the latest supported version:
```bash
-$ composer require clue/shell-react:^0.2
+composer require clue/shell-react:^0.2
```
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
This project aims to run on any platform and thus does not require any PHP
-extensions and supports running on legacy PHP 5.3 through current PHP 7+.
-It's *highly recommended to use PHP 7+* for this project.
+extensions and supports running on legacy PHP 5.3 through current PHP 8+.
+It's *highly recommended to use the latest supported PHP version* for this project.
## Tests
To run the test suite, you first need to clone this repo and then install all
-dependencies [through Composer](https://getcomposer.org):
+dependencies [through Composer](https://getcomposer.org/):
```bash
-$ composer install
+composer install
```
To run the test suite, go to the project root and run:
```bash
-$ php vendor/bin/phpunit
+vendor/bin/phpunit
```
## License
diff --git a/composer.json b/composer.json
index 63c3095..d8f087e 100644
--- a/composer.json
+++ b/composer.json
@@ -11,16 +11,23 @@
}
],
"autoload": {
- "psr-4" : { "Clue\\React\\Shell\\": "src/" }
+ "psr-4" : {
+ "Clue\\React\\Shell\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Clue\\Tests\\React\\Shell\\": "tests/"
+ }
},
"require": {
"php": ">=5.3",
- "react/child-process": "^0.5 || ^0.4 || ^0.3",
- "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
- "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.6",
- "react/promise": "^2.0 || ^1.0"
+ "react/child-process": "^0.6.3",
+ "react/event-loop": "^1.2",
+ "react/stream": "^1.2",
+ "react/promise": "^3.0 || ^2.0 || ^1.0"
},
"require-dev": {
- "phpunit/phpunit": "^7.0 || ^6.0 || ^5.0 || ^4.8.35"
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
}
}
diff --git a/examples/bash.php b/examples/bash.php
index 5bd971f..bf7c190 100644
--- a/examples/bash.php
+++ b/examples/bash.php
@@ -1,23 +1,21 @@
createDeferredShell('bash 2>&1');
$shell->execute('echo -n $USER')->then(function ($result) {
var_dump('current user', $result);
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
$shell->execute('env | sort | head -n10')->then(function ($env) {
var_dump('env', $env);
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
$shell->end();
-
-$loop->run();
diff --git a/examples/docker.php b/examples/docker.php
index 650ba09..ac1a5c1 100644
--- a/examples/docker.php
+++ b/examples/docker.php
@@ -1,23 +1,21 @@
createDeferredShell('docker run -i --rm debian bash');
$shell->execute('id')->then(function ($result) {
var_dump('current user', $result);
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
$shell->execute('env')->then(function ($env) {
var_dump('env', $env);
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
$shell->end();
-
-$loop->run();
diff --git a/examples/php.php b/examples/php.php
index a2a70f7..1f71dc3 100644
--- a/examples/php.php
+++ b/examples/php.php
@@ -1,12 +1,8 @@
createDeferredShell('php -a');
$shell->setBounding("echo '{{ bounding }}';");
@@ -21,8 +17,8 @@
CODE
)->then(function ($output) {
echo 'Program output: ' . PHP_EOL . $output . PHP_EOL;
+}, function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
$shell->end();
-
-$loop->run();
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index b3f90f3..7a7aa2c 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,19 +1,23 @@
-
+
+ convertDeprecationsToExceptions="true">
./tests/
-
-
+
+
./src/
-
-
-
\ No newline at end of file
+
+
+
+
+
+
diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy
new file mode 100644
index 0000000..45513ae
--- /dev/null
+++ b/phpunit.xml.legacy
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ ./tests/
+
+
+
+
+ ./src/
+
+
+
+
+
+
diff --git a/src/ProcessLauncher.php b/src/ProcessLauncher.php
index c32f248..27ce977 100644
--- a/src/ProcessLauncher.php
+++ b/src/ProcessLauncher.php
@@ -3,17 +3,28 @@
namespace Clue\React\Shell;
use React\ChildProcess\Process;
+use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use Clue\React\Shell\DeferredShell;
use React\Stream\CompositeStream;
class ProcessLauncher
{
+ /** @var LoopInterface */
private $loop;
- public function __construct(LoopInterface $loop)
+ /**
+ * 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.
+ *
+ * @param ?LoopInterface $loop
+ */
+ public function __construct(LoopInterface $loop = null)
{
- $this->loop = $loop;
+ $this->loop = $loop ?: Loop::get();
}
/**
@@ -41,7 +52,7 @@ public function createDeferredShell($process)
// forcefully terminate process when stream closes
$stream->on('close', function () use ($process) {
if ($process->isRunning()) {
- $process->terminate(SIGKILL);
+ $process->terminate(defined('SIGKILL') ? SIGKILL : null);
}
});
diff --git a/tests/DeferredShellTest.php b/tests/DeferredShellTest.php
index 8fd8ad4..5825b33 100644
--- a/tests/DeferredShellTest.php
+++ b/tests/DeferredShellTest.php
@@ -1,12 +1,17 @@
stream = $this->getMockBuilder('React\Stream\DuplexStreamInterface')->getMock();
}
diff --git a/tests/FunctionalTest.php b/tests/FunctionalTest.php
index 7d37099..1434549 100644
--- a/tests/FunctionalTest.php
+++ b/tests/FunctionalTest.php
@@ -1,5 +1,7 @@
loop = Factory::create();
$this->launcher = new ProcessLauncher($this->loop);
diff --git a/tests/ProcessLauncherTest.php b/tests/ProcessLauncherTest.php
index 89f912f..364de12 100644
--- a/tests/ProcessLauncherTest.php
+++ b/tests/ProcessLauncherTest.php
@@ -1,5 +1,7 @@
loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
$this->processLauncher = new ProcessLauncher($this->loop);
}
+ public function testConstructWithoutLoopAssignsLoopAutomatically()
+ {
+ $launcher = new ProcessLauncher();
+
+ $ref = new \ReflectionProperty($launcher, 'loop');
+ $ref->setAccessible(true);
+ $loop = $ref->getValue($launcher);
+
+ $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop);
+ }
+
public function testProcessWillBeStarted()
{
$process = $this->getMockBuilder('React\ChildProcess\Process')->disableOriginalConstructor()->getMock();
@@ -36,7 +52,7 @@ public function testClosingStreamTerminatesRunningProcess()
$process->stdin->expects($this->any())->method('isWritable')->willReturn(true);
$process->expects($this->once())->method('isRunning')->will($this->returnValue(true));
- $process->expects($this->once())->method('terminate')->with($this->equalTo(SIGKILL));
+ $process->expects($this->once())->method('terminate')->with($this->equalTo(defined('SIGKILL') ? SIGKILL : null));
$shell = $this->processLauncher->createDeferredShell($process);
diff --git a/tests/bootstrap.php b/tests/TestCase.php
similarity index 83%
rename from tests/bootstrap.php
rename to tests/TestCase.php
index 3e3aaee..052104a 100644
--- a/tests/bootstrap.php
+++ b/tests/TestCase.php
@@ -1,13 +1,11 @@
getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock();
+ if (method_exists('PHPUnit\Framework\MockObject\MockBuilder', 'addMethods')) {
+ // PHPUnit 9+
+ return $this->getMockBuilder('stdClass')->addMethods(array('__invoke'))->getMock();
+ } else {
+ // legacy PHPUnit 4 - PHPUnit 8
+ return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock();
+ }
}
protected function expectPromiseResolve($promise)