Repository: laminas/laminas-cli (php)

Issue:
[RFC]: Allow installation with Symfony 7.x series
## RFC

Allowing installation along with Symfony v7 components will require dropping support for PHP 8.1 here.

Considering that `symfony/console` is an important dependency here, it probably makes sense to do that.

What do members of the @laminas/technical-steering-committee think?


Pull request:
Allow installation with Symfony v7 components, Remove support for Symfony v5 components
- Closes #117
- Closes #115
- Closes #116 

Patch:
diff --git a/composer.json b/composer.json
index d8536b5..b27b80f 100644
--- a/composer.json
+++ b/composer.json
@@ -30,19 +30,19 @@
         "php": "~8.1.0 || ~8.2.0 || ~8.3.0",
         "composer-runtime-api": "^2.0.0",
         "psr/container": "^1.0 || ^2.0",
-        "symfony/console": "^5.3.7 || ^6.0",
-        "symfony/event-dispatcher": "^5.0 || ^6.0",
+        "symfony/console": "^6.0 || ^7.0",
+        "symfony/event-dispatcher": "^6.0 || ^7.0",
         "symfony/polyfill-php80": "^1.17",
         "webmozart/assert": "^1.10"
     },
     "require-dev": {
         "laminas/laminas-coding-standard": "~2.5.0",
-        "laminas/laminas-mvc": "^3.6.1",
+        "laminas/laminas-mvc": "^3.7.0",
         "laminas/laminas-servicemanager": "^3.22.1",
         "mikey179/vfsstream": "2.0.x-dev",
-        "phpunit/phpunit": "^10.4.2",
+        "phpunit/phpunit": "^10.5.5",
         "psalm/plugin-phpunit": "^0.18.4",
-        "vimeo/psalm": "^5.15"
+        "vimeo/psalm": "^5.18"
     },
     "autoload": {
         "psr-4": {
diff --git a/composer.lock b/composer.lock
index 5fbe88b..8f3c7bc 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "bfb8475cf8949c863fb6ed2d03be6d3c",
+    "content-hash": "a1ac4dec81cc442e12809dce00695b4c",
     "packages": [
         {
             "name": "psr/container",
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index e710797..d0e03d7 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -3,19 +3,23 @@
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="./vendor/autoload.php"
-        colors="true">
-  <coverage/>
-  <testsuites>
-    <testsuite name="laminas-cli">
-      <directory>./test/</directory>
-    </testsuite>
-  </testsuites>
-  <source>
-    <include>
-      <directory suffix=".php">./src</directory>
-    </include>
-    <exclude>
-      <file>./src/autoload.php</file>
-    </exclude>
-  </source>
+        colors="true"
+        displayDetailsOnSkippedTests="true"
+        displayDetailsOnIncompleteTests="true"
+        displayDetailsOnTestsThatTriggerDeprecations="true"
+        displayDetailsOnTestsThatTriggerErrors="true"
+        displayDetailsOnTestsThatTriggerNotices="true"
+        displayDetailsOnTestsThatTriggerWarnings="true"
+>
+    <coverage/>
+    <testsuites>
+        <testsuite name="laminas-cli">
+            <directory>./test/</directory>
+        </testsuite>
+    </testsuites>
+    <source>
+        <include>
+            <directory suffix=".php">./src</directory>
+        </include>
+    </source>
 </phpunit>
diff --git a/src/Input/AbstractParamAwareInput.php b/src/Input/AbstractParamAwareInput.php
index 61cef22..88f69e7 100644
--- a/src/Input/AbstractParamAwareInput.php
+++ b/src/Input/AbstractParamAwareInput.php
@@ -306,4 +306,12 @@ abstract class AbstractParamAwareInput implements ParamAwareInputInterface
 
         return $originalValidator;
     }
+
+    /**
+     * Returns a stringified representation of the args passed to the command.
+     */
+    public function __toString(): string
+    {
+        return $this->input->__toString();
+    }
 }
diff --git a/src/Input/ParamAwareInput.php b/src/Input/ParamAwareInput.php
index a3c5fae..1d9341f 100644
--- a/src/Input/ParamAwareInput.php
+++ b/src/Input/ParamAwareInput.php
@@ -29,17 +29,13 @@ final class ParamAwareInput extends AbstractParamAwareInput
     /**
      * @param string|array                     $values
      * @param string|bool|int|float|array|null $default
-     * @return mixed
      */
-    public function getParameterOption($values, $default = false, bool $onlyParams = false)
+    public function getParameterOption($values, $default = false, bool $onlyParams = false): mixed
     {
         return $this->input->getParameterOption($values, $default, $onlyParams);
     }
 
-    /**
-     * @return mixed
-     */
-    public function getArgument(string $name)
+    public function getArgument(string $name): mixed
     {
         return $this->input->getArgument($name);
     }
@@ -52,10 +48,7 @@ final class ParamAwareInput extends AbstractParamAwareInput
         $this->input->setArgument($name, $value);
     }
 
-    /**
-     * @return mixed
-     */
-    public function getOption(string $name)
+    public function getOption(string $name): mixed
     {
         return $this->input->getOption($name);
     }


Test patch:
diff --git a/test/Command/ParamAwareCommandTest.php b/test/Command/ParamAwareCommandTest.php
index cdd0e28..f7d924e 100644
--- a/test/Command/ParamAwareCommandTest.php
+++ b/test/Command/ParamAwareCommandTest.php
@@ -6,7 +6,6 @@ namespace LaminasTest\Cli\Command;
 
 use Laminas\Cli\Input\BoolParam;
 use Laminas\Cli\Input\ParamAwareInputInterface;
-use LaminasTest\Cli\TestAsset\ParamAwareCommandStub74;
 use LaminasTest\Cli\TestAsset\ParamAwareCommandStub80;
 use PHPUnit\Framework\MockObject\MockObject;
 use PHPUnit\Framework\TestCase;
@@ -15,13 +14,9 @@ use Symfony\Component\Console\Helper\QuestionHelper;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Output\OutputInterface;
 
-use function version_compare;
-
-use const PHP_VERSION;
-
 class ParamAwareCommandTest extends TestCase
 {
-    private ParamAwareCommandStub74|ParamAwareCommandStub80 $command;
+    private ParamAwareCommandStub80 $command;
 
     /** @psalm-var QuestionHelper&MockObject */
     private QuestionHelper|MockObject $questionHelper;
@@ -40,10 +35,7 @@ class ParamAwareCommandTest extends TestCase
             )
             ->willReturn($this->questionHelper);
 
-        $stubClass     = version_compare(PHP_VERSION, '8.0.0', '>=')
-            ? ParamAwareCommandStub80::class
-            : ParamAwareCommandStub74::class;
-        $this->command = new $stubClass($helperSet);
+        $this->command = new ParamAwareCommandStub80($helperSet);
     }
 
     public function testAddParamProxiesToAddOption(): void
diff --git a/test/Input/ParamAwareInputTest.php b/test/Input/ParamAwareInputTest.php
index 7d81a40..0bc111a 100644
--- a/test/Input/ParamAwareInputTest.php
+++ b/test/Input/ParamAwareInputTest.php
@@ -15,8 +15,10 @@ use Laminas\Cli\Input\PathParam;
 use Laminas\Cli\Input\StringParam;
 use PHPUnit\Framework\MockObject\MockObject;
 use PHPUnit\Framework\TestCase;
+use ReflectionMethod;
 use Symfony\Component\Console\Formatter\NullOutputFormatter;
 use Symfony\Component\Console\Helper\QuestionHelper;
+use Symfony\Component\Console\Input\ArrayInput;
 use Symfony\Component\Console\Input\InputDefinition;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\StreamableInputInterface;
@@ -147,11 +149,15 @@ class ParamAwareInputTest extends TestCase
         array $arguments,
         mixed $expectedOutput
     ): void {
-        $this->decoratedInput
-            ->expects($this->atLeastOnce())
+        $invokation = $this->decoratedInput
+            ->expects(self::atLeastOnce())
             ->method($method)
-            ->with(...$arguments)
-            ->willReturn($expectedOutput);
+            ->with(...$arguments);
+
+        $reflectionMethod = new ReflectionMethod(InputInterface::class, $method);
+        if ((string) $reflectionMethod->getReturnType() !== 'void') {
+            $invokation->willReturn($expectedOutput);
+        }
 
         $input = new $this->class(
             $this->decoratedInput,
@@ -160,7 +166,7 @@ class ParamAwareInputTest extends TestCase
             $this->params
         );
 
-        $this->assertSame($expectedOutput, $input->$method(...$arguments));
+        self::assertSame($expectedOutput, $input->$method(...$arguments));
     }
 
     public function testProxiesToDecoratedInputWithInputDefinition(): void
@@ -171,10 +177,9 @@ class ParamAwareInputTest extends TestCase
         $expectedOutput = null;
 
         $this->decoratedInput
-            ->expects($this->atLeastOnce())
+            ->expects(self::atLeastOnce())
             ->method($method)
-            ->with(...$arguments)
-            ->willReturn($expectedOutput);
+            ->with(...$arguments);
 
         $input = new $this->class(
             $this->decoratedInput,
@@ -214,8 +219,7 @@ class ParamAwareInputTest extends TestCase
         $decoratedInput
             ->expects($this->atLeastOnce())
             ->method('setStream')
-            ->with($this->identicalTo(STDIN))
-            ->willReturn(null);
+            ->with($this->identicalTo(STDIN));
 
         $input = new $this->class(
             $decoratedInput,
@@ -633,4 +637,24 @@ class ParamAwareInputTest extends TestCase
 
         $this->assertNull($input->getParam('non-required'));
     }
+
+    public function testThatCastingToAStringProxiesToTheUnderlyingInputImplementation(): void
+    {
+        /**
+         * Mocking a concrete class here because `__toString` is not part of the InputInterface contract
+         */
+        $decoratedInput = $this->createMock(ArrayInput::class);
+        $decoratedInput->expects(self::atLeastOnce())
+            ->method('__toString')
+            ->willReturn('something');
+
+        $input = new $this->class(
+            $decoratedInput,
+            $this->output,
+            $this->helper,
+            $this->params,
+        );
+
+        self::assertSame('something', $input->__toString());
+    }
 }
diff --git a/test/TestAsset/ParamAwareCommandStub74.php b/test/TestAsset/ParamAwareCommandStub74.php
deleted file mode 100644
index a048fcf..0000000
--- a/test/TestAsset/ParamAwareCommandStub74.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace LaminasTest\Cli\TestAsset;
-
-class ParamAwareCommandStub74 extends AbstractParamAwareCommandStub
-{
-    /**
-     * @param string|array|null $shortcut
-     * @param null|mixed        $default Defaults to null.
-     * @return $this
-     */
-    public function addOption(
-        string $name,
-        $shortcut = null,
-        ?int $mode = null,
-        string $description = '',
-        $default = null
-    ): static {
-        return $this->doAddOption($name, $shortcut, $mode, $description, $default);
-    }
-}
diff --git a/test/TestAsset/ParamAwareCommandStub80.php b/test/TestAsset/ParamAwareCommandStub80.php
index ac639c2..a967951 100644
--- a/test/TestAsset/ParamAwareCommandStub80.php
+++ b/test/TestAsset/ParamAwareCommandStub80.php
@@ -4,19 +4,17 @@ declare(strict_types=1);
 
 namespace LaminasTest\Cli\TestAsset;
 
+use Closure;
+
 class ParamAwareCommandStub80 extends AbstractParamAwareCommandStub
 {
-    /**
-     * @param string|array|null $shortcut
-     * @param null|mixed        $default Defaults to null.
-     * @return $this
-     */
     public function addOption(
         string $name,
-        $shortcut = null,
-        ?int $mode = null,
+        string|array|null $shortcut = null,
+        int|null $mode = null,
         string $description = '',
-        $default = null
+        mixed $default = null,
+        array|Closure $suggestedValues = []
     ): static {
         return $this->doAddOption($name, $shortcut, $mode, $description, $default);
     }