diff --git a/.ci-tools/phpstan-baseline.neon b/.ci-tools/phpstan-baseline.neon
index e174eb2a..53039fc8 100644
--- a/.ci-tools/phpstan-baseline.neon
+++ b/.ci-tools/phpstan-baseline.neon
@@ -600,12 +600,6 @@ parameters:
count: 1
path: ../src/Bundle/DependencyInjection/JoseFrameworkExtension.php
- -
- rawMessage: 'Method Jose\Bundle\JoseFramework\DependencyInjection\JoseFrameworkExtension::getAlias() has Symfony\Component\DependencyInjection\Exception\BadMethodCallException in PHPDoc @throws tag but it''s not thrown.'
- identifier: throws.unusedType
- count: 1
- path: ../src/Bundle/DependencyInjection/JoseFrameworkExtension.php
-
-
rawMessage: 'Method Jose\Bundle\JoseFramework\DependencyInjection\JoseFrameworkExtension::getConfiguration() has a parameter $container with a type declaration of Symfony\Component\DependencyInjection\ContainerBuilder, but containers should not be injected.'
identifier: ergebnis.noParameterWithContainerTypeDeclaration
@@ -5370,12 +5364,6 @@ parameters:
count: 1
path: ../src/Bundle/Resources/config/analyzers.php
- -
- rawMessage: 'Method Jose\Bundle\JoseFramework\Routing\JWKSetLoader::load() has Exception in PHPDoc @throws tag but it''s not thrown.'
- identifier: throws.unusedType
- count: 1
- path: ../src/Bundle/Routing/JWKSetLoader.php
-
-
rawMessage: 'Method Jose\Bundle\JoseFramework\Routing\JWKSetLoader::load() has parameter $type with a nullable type declaration.'
identifier: ergebnis.noParameterWithNullableTypeDeclaration
@@ -5466,12 +5454,6 @@ parameters:
count: 1
path: ../src/Bundle/Serializer/JWESerializer.php
- -
- rawMessage: 'Method Jose\Bundle\JoseFramework\Serializer\JWESerializer::denormalize() has Symfony\Component\Serializer\Exception\ExceptionInterface in PHPDoc @throws tag but it''s not thrown.'
- identifier: throws.unusedType
- count: 1
- path: ../src/Bundle/Serializer/JWESerializer.php
-
-
rawMessage: 'Method Jose\Bundle\JoseFramework\Serializer\JWESerializer::denormalize() has parameter $format with a nullable type declaration.'
identifier: ergebnis.noParameterWithNullableTypeDeclaration
@@ -5574,12 +5556,6 @@ parameters:
count: 1
path: ../src/Bundle/Serializer/JWSSerializer.php
- -
- rawMessage: 'Method Jose\Bundle\JoseFramework\Serializer\JWSSerializer::denormalize() has Symfony\Component\Serializer\Exception\ExceptionInterface in PHPDoc @throws tag but it''s not thrown.'
- identifier: throws.unusedType
- count: 1
- path: ../src/Bundle/Serializer/JWSSerializer.php
-
-
rawMessage: 'Method Jose\Bundle\JoseFramework\Serializer\JWSSerializer::denormalize() has parameter $format with a nullable type declaration.'
identifier: ergebnis.noParameterWithNullableTypeDeclaration
@@ -9720,12 +9696,6 @@ parameters:
count: 1
path: ../src/Library/KeyManagement/KeyConverter/KeyConverter.php
- -
- rawMessage: 'Parameter #1 $certificate of static method Jose\Component\KeyManagement\KeyConverter\KeyConverter::loadKeyFromCertificate() expects string, mixed given.'
- identifier: argument.type
- count: 1
- path: ../src/Library/KeyManagement/KeyConverter/KeyConverter.php
-
-
rawMessage: 'Parameter #1 $details of static method Jose\Component\KeyManagement\KeyConverter\KeyConverter::tryToLoadECKey() expects array{type: int, key: string}, non-empty-array given.'
identifier: argument.type
diff --git a/.ci-tools/phpunit.xml.dist b/.ci-tools/phpunit.xml.dist
index 0bd89523..4621b193 100644
--- a/.ci-tools/phpunit.xml.dist
+++ b/.ci-tools/phpunit.xml.dist
@@ -18,7 +18,7 @@
-
+
./../src
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f00ae50f..a4637be4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,7 +16,7 @@ jobs:
name: "0️⃣ Pre-checks"
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: "Check file permissions"
run: |
@@ -43,7 +43,7 @@ jobs:
outputs:
cache-key: ${{ steps.cache-key-generator.outputs.key }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- id: cache-key-generator
run: echo "key=composer-${{ runner.os }}-${{ hashFiles('composer.lock') }}" >> $GITHUB_OUTPUT
@@ -69,7 +69,7 @@ jobs:
container:
image: ghcr.io/spomky-labs/phpqa:8.4
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- uses: actions/cache@v4
with:
path: |
@@ -85,7 +85,7 @@ jobs:
container:
image: ghcr.io/spomky-labs/phpqa:8.4
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- uses: actions/cache@v4
with:
path: |
@@ -101,7 +101,7 @@ jobs:
container:
image: ghcr.io/spomky-labs/phpqa:8.4
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- uses: actions/cache@v4
with:
path: |
@@ -117,7 +117,7 @@ jobs:
container:
image: ghcr.io/spomky-labs/phpqa:8.4
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- uses: actions/cache@v4
with:
path: |
@@ -136,7 +136,7 @@ jobs:
container:
image: ghcr.io/spomky-labs/phpqa:8.4
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- run: composer exec -- parallel-lint src tests
check_licenses:
@@ -146,7 +146,7 @@ jobs:
container:
image: ghcr.io/spomky-labs/phpqa:8.4
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- run: castor check-licenses
deptrac:
@@ -156,7 +156,7 @@ jobs:
container:
image: ghcr.io/spomky-labs/phpqa:8.4
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- uses: actions/cache@v4
with:
path: |
@@ -166,7 +166,7 @@ jobs:
- run: castor deptrac
tests:
- name: "8️⃣ Unit & Functional Tests (PHP ${{ matrix.php-version }}${{ matrix.lowest-deps && ' --prefer-lowest' || '' }})"
+ name: "🧪 Unit & Functional Tests (PHP ${{ matrix.php-version }}${{ matrix.lowest-deps && ' - Lowest Deps' || '' }})"
needs:
- prepare_dependencies
- phpstan
@@ -177,19 +177,25 @@ jobs:
- check_licenses
- deptrac
runs-on: ubuntu-latest
+ continue-on-error: ${{ matrix.experimental || false }}
strategy:
+ fail-fast: false
matrix:
include:
+ - php-version: '8.2'
+ lowest-deps: true
- php-version: '8.2'
- php-version: '8.3'
- php-version: '8.4'
+ - php-version: '8.5'
+ experimental: true
container:
image: ghcr.io/spomky-labs/phpqa:${{ matrix.php-version }}
env:
XDEBUG_MODE: coverage
PHP_VERSION: ${{ matrix.php-version }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Install dependencies
run: |
if [ "${{ matrix.lowest-deps || 'false' }}" = "true" ]; then
@@ -197,7 +203,7 @@ jobs:
else
composer install --no-interaction --no-progress
fi
-
+
- name: Run PHPUnit
run: castor phpunit
@@ -211,7 +217,7 @@ jobs:
env:
XDEBUG_MODE: coverage
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Execute Infection
run: castor infect
@@ -220,7 +226,7 @@ jobs:
needs: [prepare_dependencies]
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Check exported files
run: |
EXPECTED=".gitsplit.yml,CODE_OF_CONDUCT.md,LICENSE,README.md,SECURITY.md,castor.php,composer.json"
diff --git a/castor.php b/castor.php
index 0108671b..f9c95192 100644
--- a/castor.php
+++ b/castor.php
@@ -218,7 +218,7 @@ function phpunit(): void
{
phpqa(
[
- 'composer', 'exec', '--', 'phpunit',
+ 'composer', 'exec', '--', 'phpunit-11',
'--coverage-xml', '.ci-tools/coverage',
'--log-junit=.ci-tools/coverage/junit.xml',
'--configuration', '.ci-tools/phpunit.xml.dist',
diff --git a/src/Library/Console/AddKeyIntoKeysetCommand.php b/src/Library/Console/AddKeyIntoKeysetCommand.php
index da69e17f..d87bb8aa 100644
--- a/src/Library/Console/AddKeyIntoKeysetCommand.php
+++ b/src/Library/Console/AddKeyIntoKeysetCommand.php
@@ -16,9 +16,7 @@
use function is_array;
use function is_string;
-#[AsCommand(name: 'keyset:add:key', description: 'Add a key into a key set.', help: <<<'TXT'
-This command adds a key at the end of a key set.
-TXT)]
+#[AsCommand(name: 'keyset:add:key', description: 'Add a key into a key set.')]
final class AddKeyIntoKeysetCommand extends ObjectOutputCommand
{
#[Override]
@@ -26,6 +24,7 @@ protected function configure(): void
{
parent::configure();
$this
+ ->setHelp('This command adds a key at the end of a key set.')
->addArgument('jwkset', InputArgument::REQUIRED, 'The JWKSet object')
->addArgument('jwk', InputArgument::REQUIRED, 'The new JWK object');
}
diff --git a/src/Library/Console/JKULoaderCommand.php b/src/Library/Console/JKULoaderCommand.php
index 5ddd2486..16a06243 100644
--- a/src/Library/Console/JKULoaderCommand.php
+++ b/src/Library/Console/JKULoaderCommand.php
@@ -13,9 +13,7 @@
use Symfony\Component\Console\Output\OutputInterface;
use function is_string;
-#[AsCommand(name: 'keyset:load:jku', description: 'Loads a key set from an url.', help: <<<'TXT'
-This command will try to get a key set from an URL. The distant key set is a JWKSet.
-TXT)]
+#[AsCommand(name: 'keyset:load:jku', description: 'Loads a key set from an url.')]
final class JKULoaderCommand extends ObjectOutputCommand
{
public function __construct(
@@ -30,6 +28,7 @@ protected function configure(): void
{
parent::configure();
$this
+ ->setHelp('This command will try to get a key set from an URL. The distant key set is a JWKSet.')
->addArgument('url', InputArgument::REQUIRED, 'The URL');
}
diff --git a/src/Library/Console/KeyAnalyzerCommand.php b/src/Library/Console/KeyAnalyzerCommand.php
index 4d3e02ac..1b83414c 100644
--- a/src/Library/Console/KeyAnalyzerCommand.php
+++ b/src/Library/Console/KeyAnalyzerCommand.php
@@ -18,9 +18,7 @@
use function is_array;
use function is_string;
-#[AsCommand(name: 'key:analyze', description: 'JWK quality analyzer.', help: <<<'TXT'
-This command will analyze a JWK object and find security issues.
-TXT)]
+#[AsCommand(name: 'key:analyze', description: 'JWK quality analyzer.')]
final class KeyAnalyzerCommand extends Command
{
public function __construct(
@@ -35,6 +33,7 @@ protected function configure(): void
{
parent::configure();
$this
+ ->setHelp('This command will analyze a JWK object and find security issues.')
->addArgument('jwk', InputArgument::REQUIRED, 'The JWK object');
}
diff --git a/src/Library/Console/KeysetAnalyzerCommand.php b/src/Library/Console/KeysetAnalyzerCommand.php
index 780d2ecd..bc7ff34b 100644
--- a/src/Library/Console/KeysetAnalyzerCommand.php
+++ b/src/Library/Console/KeysetAnalyzerCommand.php
@@ -21,9 +21,7 @@
use function is_string;
use function sprintf;
-#[AsCommand(name: 'keyset:analyze', description: 'JWKSet quality analyzer.', help: <<<'TXT'
-This command will analyze a JWKSet object and find security issues.
-TXT)]
+#[AsCommand(name: 'keyset:analyze', description: 'JWKSet quality analyzer.')]
final class KeysetAnalyzerCommand extends Command
{
public function __construct(
@@ -39,6 +37,7 @@ protected function configure(): void
{
parent::configure();
$this
+ ->setHelp('This command will analyze a JWKSet object and find security issues.')
->addArgument('jwkset', InputArgument::REQUIRED, 'The JWKSet object');
}
diff --git a/src/Library/Console/MergeKeysetCommand.php b/src/Library/Console/MergeKeysetCommand.php
index 5e3a9d57..97c2a736 100644
--- a/src/Library/Console/MergeKeysetCommand.php
+++ b/src/Library/Console/MergeKeysetCommand.php
@@ -14,9 +14,7 @@
use Symfony\Component\Console\Output\OutputInterface;
use function is_array;
-#[AsCommand(name: 'keyset:merge', description: 'Merge several key sets into one.', help: <<<'TXT'
-This command merges several key sets into one. It is very useful when you generate e.g. RSA, EC and OKP keys and you want only one key set to rule them all.
-TXT)]
+#[AsCommand(name: 'keyset:merge', description: 'Merge several key sets into one.')]
final class MergeKeysetCommand extends ObjectOutputCommand
{
#[Override]
@@ -24,6 +22,9 @@ protected function configure(): void
{
parent::configure();
$this
+ ->setHelp(
+ 'This command merges several key sets into one. It is very useful when you generate e.g. RSA, EC and OKP keys and you want only one key set to rule them all.'
+ )
->addArgument('jwksets', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'The JWKSet objects');
}
diff --git a/src/Library/Console/PublicKeyCommand.php b/src/Library/Console/PublicKeyCommand.php
index d44494c0..46e7d390 100644
--- a/src/Library/Console/PublicKeyCommand.php
+++ b/src/Library/Console/PublicKeyCommand.php
@@ -18,9 +18,6 @@
#[AsCommand(
name: 'key:convert:public',
description: 'Convert a private key into public key. Symmetric keys (shared keys) are not changed.',
- help: <<<'TXT'
-This command converts a private key into a public key.
-TXT
)]
final class PublicKeyCommand extends ObjectOutputCommand
{
@@ -29,6 +26,7 @@ protected function configure(): void
{
parent::configure();
$this
+ ->setHelp('This command converts a private key into a public key.')
->addArgument('jwk', InputArgument::REQUIRED, 'The JWK object');
}
diff --git a/src/Library/Console/PublicKeysetCommand.php b/src/Library/Console/PublicKeysetCommand.php
index 84d87bcc..c4ecf296 100644
--- a/src/Library/Console/PublicKeysetCommand.php
+++ b/src/Library/Console/PublicKeysetCommand.php
@@ -18,9 +18,6 @@
#[AsCommand(
name: 'keyset:convert:public',
description: 'Convert private keys in a key set into public keys. Symmetric keys (shared keys) are not changed.',
- help: <<<'TXT'
-This command converts private keys in a key set into public keys.
-TXT
)]
final class PublicKeysetCommand extends ObjectOutputCommand
{
@@ -29,6 +26,7 @@ protected function configure(): void
{
parent::configure();
$this
+ ->setHelp('This command converts private keys in a key set into public keys.')
->addArgument('jwkset', InputArgument::REQUIRED, 'The JWKSet object');
}
diff --git a/src/Library/Console/RotateKeysetCommand.php b/src/Library/Console/RotateKeysetCommand.php
index bab57e61..ad493fea 100644
--- a/src/Library/Console/RotateKeysetCommand.php
+++ b/src/Library/Console/RotateKeysetCommand.php
@@ -17,9 +17,7 @@
use function is_array;
use function is_string;
-#[AsCommand(name: 'keyset:rotate', description: 'Rotate a key set.', help: <<<'TXT'
-This command removes the last key in a key set a place a new one at the beginning.
-TXT)]
+#[AsCommand(name: 'keyset:rotate', description: 'Rotate a key set.')]
final class RotateKeysetCommand extends ObjectOutputCommand
{
#[Override]
@@ -27,6 +25,7 @@ protected function configure(): void
{
parent::configure();
$this
+ ->setHelp('This command removes the last key in a key set a place a new one at the beginning.')
->addArgument('jwkset', InputArgument::REQUIRED, 'The JWKSet object')
->addArgument('jwk', InputArgument::REQUIRED, 'The new JWK object');
}
diff --git a/src/Library/Console/X5ULoaderCommand.php b/src/Library/Console/X5ULoaderCommand.php
index d7465627..f91141f1 100644
--- a/src/Library/Console/X5ULoaderCommand.php
+++ b/src/Library/Console/X5ULoaderCommand.php
@@ -13,9 +13,7 @@
use Symfony\Component\Console\Output\OutputInterface;
use function is_string;
-#[AsCommand(name: 'keyset:load:x5u', description: 'Loads a key set from an url.', help: <<<'TXT'
-This command will try to get a key set from an URL. The distant key set is list of X.509 certificates.
-TXT)]
+#[AsCommand(name: 'keyset:load:x5u', description: 'Loads a key set from an url.')]
final class X5ULoaderCommand extends ObjectOutputCommand
{
public function __construct(
@@ -30,6 +28,9 @@ protected function configure(): void
{
parent::configure();
$this
+ ->setHelp(
+ 'This command will try to get a key set from an URL. The distant key set is list of X.509 certificates.'
+ )
->addArgument('url', InputArgument::REQUIRED, 'The URL');
}
diff --git a/tests/Component/Console/AnalyzeCommandTest.php b/tests/Component/Console/AnalyzeCommandTest.php
index 1be39cb2..c645cef4 100644
--- a/tests/Component/Console/AnalyzeCommandTest.php
+++ b/tests/Component/Console/AnalyzeCommandTest.php
@@ -17,6 +17,22 @@
*/
final class AnalyzeCommandTest extends KernelTestCase
{
+ #[Test]
+ public function commandsHaveHelpTextDefined(): void
+ {
+ // Given
+ $application = new Application(self::bootKernel());
+
+ // When & Then - Verify help text is set for all analyze commands
+ $keyAnalyzeCommand = $application->find('key:analyze');
+ static::assertNotEmpty($keyAnalyzeCommand->getHelp(), 'key:analyze command should have help text');
+ static::assertStringContainsString('JWK object', $keyAnalyzeCommand->getHelp());
+
+ $keysetAnalyzeCommand = $application->find('keyset:analyze');
+ static::assertNotEmpty($keysetAnalyzeCommand->getHelp(), 'keyset:analyze command should have help text');
+ static::assertStringContainsString('JWKSet object', $keysetAnalyzeCommand->getHelp());
+ }
+
#[Test]
public function iCanAnalyzeAKeyAndGetInformation(): void
{
diff --git a/tests/Component/Console/CommandHelpCompatibilityTest.php b/tests/Component/Console/CommandHelpCompatibilityTest.php
new file mode 100644
index 00000000..eae76fd2
--- /dev/null
+++ b/tests/Component/Console/CommandHelpCompatibilityTest.php
@@ -0,0 +1,82 @@
+
+ */
+ public static function commandsWithExpectedHelpTextProvider(): iterable
+ {
+ yield 'key:analyze' => ['key:analyze', 'JWK object'];
+ yield 'key:convert:public' => ['key:convert:public', 'private key into a public key'];
+ yield 'keyset:analyze' => ['keyset:analyze', 'JWKSet object'];
+ yield 'keyset:add:key' => ['keyset:add:key', 'key at the end'];
+ yield 'keyset:rotate' => ['keyset:rotate', 'last key'];
+ yield 'keyset:merge' => ['keyset:merge', 'several key sets'];
+ yield 'keyset:load:jku' => ['keyset:load:jku', 'JWKSet'];
+ yield 'keyset:load:x5u' => ['keyset:load:x5u', 'X.509 certificates'];
+ yield 'keyset:convert:public' => ['keyset:convert:public', 'private keys in a key set into public keys'];
+ }
+
+ #[Test]
+ #[DataProvider('commandsWithExpectedHelpTextProvider')]
+ public function commandsHaveHelpTextDefinedViaSetHelpMethod(string $commandName, string $expectedSubstring): void
+ {
+ // Given
+ $application = new Application(self::bootKernel());
+
+ // When
+ $command = $application->find($commandName);
+ $helpText = $command->getHelp();
+
+ // Then
+ static::assertNotEmpty($helpText, sprintf('Command "%s" should have help text defined', $commandName));
+ static::assertStringContainsString(
+ $expectedSubstring,
+ $helpText,
+ sprintf('Command "%s" help text should contain "%s"', $commandName, $expectedSubstring)
+ );
+ }
+
+ #[Test]
+ public function allCommandsHaveNonEmptyHelpText(): void
+ {
+ // Given
+ $application = new Application(self::bootKernel());
+ $commandNames = [
+ 'key:analyze',
+ 'key:convert:public',
+ 'keyset:analyze',
+ 'keyset:add:key',
+ 'keyset:rotate',
+ 'keyset:merge',
+ 'keyset:load:jku',
+ 'keyset:load:x5u',
+ 'keyset:convert:public',
+ ];
+
+ // When & Then
+ foreach ($commandNames as $commandName) {
+ $command = $application->find($commandName);
+ static::assertNotEmpty(
+ $command->getHelp(),
+ sprintf('Command "%s" must have help text for Symfony Console 7.0 compatibility', $commandName)
+ );
+ }
+ }
+}