From 4172f6eec6e784357746827376d8de4efb0741f0 Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Wed, 19 Jun 2024 10:03:43 +0200 Subject: [PATCH] Improve PHP 8.4+ support by avoiding implicitly nullable types --- composer.json | 2 +- src/Readline.php | 15 ++++++++++++-- src/Stdio.php | 22 ++++++++++++++++++--- tests/ReadlineTest.php | 6 ++++++ tests/StdioTest.php | 45 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index aa5b483..ee425e5 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "clue/term-react": "^1.0 || ^0.1.1", "clue/utf8-react": "^1.0 || ^0.1", "react/event-loop": "^1.2", - "react/stream": "^1.2" + "react/stream": "^1.4" }, "require-dev": { "clue/arguments": "^2.0", diff --git a/src/Readline.php b/src/Readline.php index b75650e..bed0282 100644 --- a/src/Readline.php +++ b/src/Readline.php @@ -37,7 +37,12 @@ class Readline extends EventEmitter implements ReadableStreamInterface private $autocomplete = null; private $autocompleteSuggestions = 8; - public function __construct(ReadableStreamInterface $input, WritableStreamInterface $output, EventEmitterInterface $base = null) + /** + * @param ReadableStreamInterface $input + * @param WritableStreamInterface $output + * @param ?EventEmitterInterface $base + */ + public function __construct(ReadableStreamInterface $input, WritableStreamInterface $output, $base = null) { $this->input = $input; $this->output = $output; @@ -49,6 +54,10 @@ public function __construct(ReadableStreamInterface $input, WritableStreamInterf // push input through control code parser $parser = new ControlCodeParser($input); + if ($base !== null && !$base instanceof EventEmitterInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #3 ($base) expected null|Evenement\EventEmitterInterface'); + } + $that = $this; $codes = array( "\n" => 'onKeyEnter', // ^J @@ -778,9 +787,11 @@ public function onKeyDown() /** * Will be invoked for character(s) that could not otherwise be processed by the sequencer * + * @param string $chars + * @param ?EventEmitterInterface $base * @internal */ - public function onFallback($chars, EventEmitterInterface $base = null) + public function onFallback($chars, $base = null) { // check if there's any special key binding for any of the chars $buffer = ''; diff --git a/src/Stdio.php b/src/Stdio.php index aff2959..ecdf791 100644 --- a/src/Stdio.php +++ b/src/Stdio.php @@ -35,8 +35,24 @@ class Stdio extends EventEmitter implements DuplexStreamInterface * @param ?WritableStreamInterface $output * @param ?Readline $readline */ - public function __construct(LoopInterface $loop = null, ReadableStreamInterface $input = null, WritableStreamInterface $output = null, Readline $readline = null) + public function __construct($loop = null, $input = null, $output = null, $readline = null) { + if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #1 ($loop) expected null|React\EventLoop\LoopInterface'); + } + + if ($input !== null && !$input instanceof ReadableStreamInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #2 ($input) expected null|React\Stream\ReadableStreamInterface'); + } + + if ($output !== null && !$output instanceof WritableStreamInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #3 ($output) expected null|React\Stream\WritableStreamInterface'); + } + + if ($readline !== null && !$readline instanceof Readline) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #4 ($readline) expected null|Clue\React\Stdio\Readline'); + } + if ($input === null) { $input = $this->createStdin($loop); // @codeCoverageIgnore } @@ -546,7 +562,7 @@ private function restoreTtyMode() * @return ReadableStreamInterface * @codeCoverageIgnore this is covered by functional tests with/without ext-readline */ - private function createStdin(LoopInterface $loop = null) + private function createStdin($loop = null) { // STDIN not defined ("php -a") or already closed (`fclose(STDIN)`) // also support starting program with closed STDIN ("example.php 0<&-") @@ -586,7 +602,7 @@ private function createStdin(LoopInterface $loop = null) * @return WritableStreamInterface * @codeCoverageIgnore this is covered by functional tests */ - private function createStdout(LoopInterface $loop = null) + private function createStdout($loop = null) { // STDOUT not defined ("php -a") or already closed (`fclose(STDOUT)`) // also support starting program with closed STDOUT ("example.php >&-") diff --git a/tests/ReadlineTest.php b/tests/ReadlineTest.php index 3ebdc10..40975ba 100644 --- a/tests/ReadlineTest.php +++ b/tests/ReadlineTest.php @@ -23,6 +23,12 @@ public function setUpReadline() $this->readline = new Readline($this->input, $this->output); } + public function testCtorThrowsExceptionForInvalidBase() + { + $this->setExpectedException('InvalidArgumentException', 'Argument #3 ($base) expected null|Evenement\EventEmitterInterface'); + new Readline($this->input, $this->output, 'invalid'); + } + public function testSettersReturnSelf() { $this->assertSame($this->readline, $this->readline->setEcho(true)); diff --git a/tests/StdioTest.php b/tests/StdioTest.php index 88d9480..a27f95c 100644 --- a/tests/StdioTest.php +++ b/tests/StdioTest.php @@ -53,6 +53,51 @@ public function testCtorReadlineArgWillBeReturnedBygetReadline() $this->assertSame($readline, $stdio->getReadline()); } + public function testCtorThrowsExceptionForInvalidLoop() + { + $input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + + //$readline = $this->getMockBuilder('Clue\React\Stdio\Readline')->disableOriginalConstructor()->getMock(); + $readline = new Readline($input, $output); + + $this->setExpectedException('InvalidArgumentException', 'Argument #1 ($loop) expected null|React\EventLoop\LoopInterface'); + new Stdio('invalid', $input, $output, $readline); + } + + public function testCtorThrowsExceptionForInvalidInput() + { + $input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + + //$readline = $this->getMockBuilder('Clue\React\Stdio\Readline')->disableOriginalConstructor()->getMock(); + $readline = new Readline($input, $output); + + $this->setExpectedException('InvalidArgumentException', 'Argument #2 ($input) expected null|React\Stream\ReadableStreamInterface'); + new Stdio($this->loop, 'invalid', $output, $readline); + } + + public function testCtorThrowsExceptionForInvalidOutput() + { + $input = $input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + + //$readline = $this->getMockBuilder('Clue\React\Stdio\Readline')->disableOriginalConstructor()->getMock(); + $readline = new Readline($input, $output); + + $this->setExpectedException('InvalidArgumentException', 'Argument #3 ($output) expected null|React\Stream\WritableStreamInterface'); + new Stdio($this->loop, $input, 'invalid', $readline); + } + + public function testCtorThrowsExceptionForInvalidReadline() + { + $input = $input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock(); + + $this->setExpectedException('InvalidArgumentException', 'Argument #4 ($readline) expected null|Clue\React\Stdio\Readline'); + new Stdio($this->loop, $input, $output, 'invalid'); + } + public function testWriteEmptyStringWillNotWriteToOutput() { $input = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();