From 36ce35dc25b90c6fa150ad0e62a60245c405e0c4 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 11:05:55 -0600 Subject: [PATCH 01/30] Update license year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 0d96a4a..80685b7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2021 - 2024 PhpCfdi https://www.phpcfdi.com/ +Copyright (c) 2021 - 2025 PhpCfdi https://www.phpcfdi.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 532ad1d0442b6fbb4e5d3574e6f8965929773cac Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 11:06:25 -0600 Subject: [PATCH 02/30] Add check type for finfo --- src/CaptchaImage.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/CaptchaImage.php b/src/CaptchaImage.php index 599da5d..16f4b33 100644 --- a/src/CaptchaImage.php +++ b/src/CaptchaImage.php @@ -6,6 +6,7 @@ use finfo; use InvalidArgumentException; +use LogicException; final class CaptchaImage implements CaptchaImageInterface { @@ -73,6 +74,9 @@ private static function finfo(): finfo if (null === $finfo) { $finfo = new finfo(); // @codeCoverageIgnore } + if (! $finfo instanceof finfo) { + throw new LogicException('Unable to create a finfo instance.'); + } return $finfo; } From c11aa7b2879fb1c8e875bb037b3fc1e4019dfdd3 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 11:07:26 -0600 Subject: [PATCH 03/30] Convert implicit nullable types to explicit --- src/HttpClient/HttpException.php | 4 ++-- src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php | 2 +- .../CaptchaLocalResolver/CaptchaLocalResolverConnector.php | 2 +- src/Resolvers/CommandLineResolver.php | 4 ++-- src/UnableToResolveCaptchaException.php | 2 +- tests/TestCase.php | 2 +- .../AntiCaptchaResolver/AntiCaptchaConnectorTest.php | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/HttpClient/HttpException.php b/src/HttpClient/HttpException.php index de282fb..a148e94 100644 --- a/src/HttpClient/HttpException.php +++ b/src/HttpClient/HttpException.php @@ -20,8 +20,8 @@ class HttpException extends RuntimeException public function __construct( RequestInterface $request, - ResponseInterface $response = null, - Throwable $previous = null + ?ResponseInterface $response = null, + ?Throwable $previous = null ) { parent::__construct("Error on {$request->getMethod()} {$request->getUri()}", 0, $previous); $this->request = $request; diff --git a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php index c0deeeb..659657e 100644 --- a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php +++ b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php @@ -35,7 +35,7 @@ class AntiCaptchaConnector * @param HttpClientInterface|null $httpClient * @throws UndiscoverableClientException */ - public function __construct(string $clientKey, HttpClientInterface $httpClient = null) + public function __construct(string $clientKey, ?HttpClientInterface $httpClient = null) { $this->clientKey = $clientKey; $this->httpClient = $httpClient ?? HttpClient::discover(); diff --git a/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php b/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php index 1a903ef..c7dc99d 100644 --- a/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php +++ b/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php @@ -35,7 +35,7 @@ class CaptchaLocalResolverConnector * @param HttpClientInterface|null $httpClient * @throws UndiscoverableClientException */ - public function __construct(string $baseUrl, HttpClientInterface $httpClient = null) + public function __construct(string $baseUrl, ?HttpClientInterface $httpClient = null) { $this->baseUrl = rtrim($baseUrl, '/'); $this->httpClient = $httpClient ?? HttpClient::discover(); diff --git a/src/Resolvers/CommandLineResolver.php b/src/Resolvers/CommandLineResolver.php index b4ceced..8cdf255 100644 --- a/src/Resolvers/CommandLineResolver.php +++ b/src/Resolvers/CommandLineResolver.php @@ -55,8 +55,8 @@ public function __construct( */ public static function create( array $command, - CommandLineResolver\AnswerBuilderInterface $answerBuilder = null, - CommandLineResolver\ProcessRunnerInterface $processRunner = null + ?CommandLineResolver\AnswerBuilderInterface $answerBuilder = null, + ?CommandLineResolver\ProcessRunnerInterface $processRunner = null ): self { return new self( $command, diff --git a/src/UnableToResolveCaptchaException.php b/src/UnableToResolveCaptchaException.php index 047b29b..edbdf4c 100644 --- a/src/UnableToResolveCaptchaException.php +++ b/src/UnableToResolveCaptchaException.php @@ -18,7 +18,7 @@ class UnableToResolveCaptchaException extends RuntimeException public function __construct( CaptchaResolverInterface $resolver, CaptchaImageInterface $image, - Throwable $previous = null + ?Throwable $previous = null ) { parent::__construct('Unable to resolve captcha image', 0, $previous); $this->resolver = $resolver; diff --git a/tests/TestCase.php b/tests/TestCase.php index 364f907..6b656be 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -18,7 +18,7 @@ public static function fileContents(string $filename): string return file_get_contents(static::filePath($filename)) ?: ''; } - public function checkPortIsOpen(string $hostname, int $port, float $timeoutSeconds = null): void + public function checkPortIsOpen(string $hostname, int $port, ?float $timeoutSeconds = null): void { $timeoutSeconds = $timeoutSeconds ?? intval(ini_get('default_socket_timeout')); $socket = fsockopen($hostname, $port, $errorNumber, $errorMessage, $timeoutSeconds); diff --git a/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php b/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php index 89907a2..8f80eb9 100644 --- a/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php +++ b/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php @@ -21,7 +21,7 @@ final class AntiCaptchaConnectorTest extends HttpTestCase /** @var string */ private $clientKey = 'client-key'; - public function createConnector(HttpClientInterface $client = null): AntiCaptchaConnector + public function createConnector(?HttpClientInterface $client = null): AntiCaptchaConnector { return new AntiCaptchaConnector($this->clientKey, $client); } From e924e0c65e8466fdb386fcd9fefc03b0ea040546 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 11:08:18 -0600 Subject: [PATCH 04/30] Better check for converting code & answer values --- .../CaptchaLocalResolverConnector.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php b/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php index c7dc99d..15dc0a3 100644 --- a/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php +++ b/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php @@ -86,7 +86,7 @@ public function sendImage(CaptchaImageInterface $image): string } $contents = strval($response->getBody()); $data = json_decode($contents, true); - $code = (is_array($data)) ? strval($data['code'] ?? '') : ''; + $code = (is_array($data) && isset($data['code']) && is_scalar($data['code'])) ? strval($data['code']) : ''; if ('' === $code) { throw new RuntimeException('Image was sent but service returns empty code'); } @@ -94,11 +94,11 @@ public function sendImage(CaptchaImageInterface $image): string } /** - * Check code for answer, if empty string means answer does not exists yet + * Check code for answer, if empty string means answer does not exist yet * * @param string $code * @return string - * @throws RuntimeException if code does not exists + * @throws RuntimeException if code does not exist * @throws RuntimeException if unable to check code * @throws RuntimeException if http transaction error occurs */ @@ -119,7 +119,7 @@ public function checkCode(string $code): string } $contents = strval($response->getBody()); $data = json_decode($contents, true); - return (is_array($data)) ? strval($data['answer'] ?? '') : ''; + return (is_array($data) && isset($data['answer']) && is_scalar($data['answer'])) ? strval($data['answer']) : ''; } public function buildUri(string $action): string From 22572bf458fe615c6447b0470d47c02ac1beedc0 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 11:47:47 -0600 Subject: [PATCH 05/30] Improve types for PHPStan --- .../CaptchaLocalResolverConnectorTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Unit/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnectorTest.php b/tests/Unit/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnectorTest.php index 254ef99..bb810c0 100644 --- a/tests/Unit/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnectorTest.php +++ b/tests/Unit/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnectorTest.php @@ -13,6 +13,7 @@ use PhpCfdi\ImageCaptchaResolver\Tests\Extending\AssertHasPreviousExceptionTrait; use PhpCfdi\ImageCaptchaResolver\Tests\HttpTestCase; use PhpCfdi\ImageCaptchaResolver\Tests\Unit\FakeExpiredTimer; +use Psr\Http\Message\RequestInterface; use RuntimeException; final class CaptchaLocalResolverConnectorTest extends HttpTestCase @@ -57,6 +58,7 @@ public function testSendImage(): void $code = $connector->sendImage($image); + /** @var RequestInterface $lastRequest */ $lastRequest = $phpHttpMockClient->getLastRequest(); $this->assertSame($serviceCode, $code); $this->assertJsonStringEqualsJsonString( @@ -105,6 +107,7 @@ public function testCheckCodeWithAnswer(): void $answer = $connector->checkCode($serviceCode); + /** @var RequestInterface $lastRequest */ $lastRequest = $phpHttpMockClient->getLastRequest(); $this->assertSame($serviceAnswer, $answer); $this->assertJsonStringEqualsJsonString( @@ -125,6 +128,7 @@ public function testCheckCodeWithoutAnswer(): void $answer = $connector->checkCode($serviceCode); + /** @var RequestInterface $lastRequest */ $lastRequest = $phpHttpMockClient->getLastRequest(); $this->assertSame($serviceAnswer, $answer); $this->assertJsonStringEqualsJsonString( From d4af632cd261de43a3bb41c94bdc6121b81ca4e3 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 11:48:24 -0600 Subject: [PATCH 06/30] Improve types for PHPStan (anti-captcha) --- .../AntiCaptchaConnector.php | 35 ++++++++++++++++--- .../AntiCaptchaConnectorTest.php | 11 +++--- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php index 659657e..c0d21aa 100644 --- a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php +++ b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php @@ -67,6 +67,15 @@ public function createTask($base64Image): string ], ]; + /** + * @see https://anti-captcha.com/es/apidoc/methods/createTask + * @phpstan-var stdClass&object{ + * errorId: int, + * errorCode?: string, + * errorDescription?: string, + * taskId: int, + * } $result + */ $result = $this->request('createTask', $postData); return (string) $result->taskId; @@ -79,19 +88,36 @@ public function createTask($base64Image): string */ public function getTaskResult(string $taskId): string { - /** @see https://anti-captcha.com/es/apidoc/methods/getTaskResult */ + /** + * @see https://anti-captcha.com/es/apidoc/methods/getTaskResult + * @phpstan-var stdClass&object{ + * errorId: int, + * errorCode?: string, + * errorDescription?: string, + * status?: string, + * solution?: stdClass&object{text: string, url: string}, + * cost?: string, + * ip?: string, + * createTime?: int, + * endTime?: int, + * solveCount?: int, + * } $result + */ $result = $this->request('getTaskResult', [ 'taskId' => $taskId, ]); - $antiCaptchaStatus = strtolower($result->status); + $antiCaptchaStatus = strtolower($result->status ?? ''); if ('processing' === $antiCaptchaStatus) { return ''; } if ('ready' === $antiCaptchaStatus) { + if (! isset($result->solution)) { + throw new LogicException('Expected solution object was not received.'); + } return strval($result->solution->text ?? ''); } - throw new LogicException("Unknown status '$result->status' for task"); + throw new LogicException(sprintf("Unknown status '%s' for task", $result->status ?? '')); } /** @@ -115,10 +141,11 @@ public function request(string $methodName, array $postData): stdClass if (! $result instanceof stdClass) { $result = (object) ['errorId' => 1, 'errorDescription' => 'Response is not a JSON object']; } + /** @phpstan-var stdClass&object{errorId?: scalar, errorDescription?: scalar} $result */ $errorId = intval($result->errorId ?? 0); if ($errorId > 0) { throw new RuntimeException( - sprintf('Anti-Captcha Error (%d): %s', $errorId, strval($result->errorDescription ?? '')) + sprintf('Anti-Captcha Error (%d): %s', $errorId, $result->errorDescription ?? '') ); } diff --git a/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php b/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php index 8f80eb9..d1c4f1b 100644 --- a/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php +++ b/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php @@ -11,6 +11,7 @@ use PhpCfdi\ImageCaptchaResolver\Resolvers\AntiCaptchaResolver\AntiCaptchaConnector; use PhpCfdi\ImageCaptchaResolver\Tests\Extending\AssertHasPreviousExceptionTrait; use PhpCfdi\ImageCaptchaResolver\Tests\HttpTestCase; +use Psr\Http\Message\RequestInterface; use RuntimeException; use stdClass; @@ -55,12 +56,13 @@ public function testCreateTask(): void $taskId = $connector->createTask($image); $this->assertSame($expectedTaskId, $taskId); + /** @var RequestInterface $lastRequest */ $lastRequest = $phpHttpMockClient->getLastRequest(); - /** @var stdClass $sentValues */ + /** @phpstan-var stdClass&object{clientKey: string, task: stdClass&object{type: string, body: string}} $sentValues */ $sentValues = json_decode((string) $lastRequest->getBody()); - $this->assertSame($this->clientKey, $sentValues->clientKey ?? ''); - $this->assertSame('ImageToTextTask', $sentValues->task->type ?? ''); - $this->assertSame($image->asBase64(), $sentValues->task->body ?? ''); + $this->assertSame($this->clientKey, $sentValues->clientKey); + $this->assertSame('ImageToTextTask', $sentValues->task->type); + $this->assertSame($image->asBase64(), $sentValues->task->body); } public function testGetTaskResultProcessing(): void @@ -112,6 +114,7 @@ public function testRequest(): void $connector = $this->createConnector($this->createHttpClient($phpHttpMockClient)); $requestData = ['foo' => 'bar']; $result = $connector->request('fake-method', $requestData); + /** @var RequestInterface $request */ $request = $phpHttpMockClient->getLastRequest(); $this->assertJsonStringEqualsJsonString( From 9fd08ef30c8d5c275b5d420f343a8adee558eebc Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 11:49:16 -0600 Subject: [PATCH 07/30] Update development tools --- .phive/phars.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.phive/phars.xml b/.phive/phars.xml index 694e12d..14544eb 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,8 +1,8 @@ - - - - - + + + + + From 8d0c58cb3edd894284751026462a3f7acc6d3c9c Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 12:36:54 -0600 Subject: [PATCH 08/30] Composer normalization --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index adb6ac7..6203edb 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,6 @@ "name": "phpcfdi/image-captcha-resolver", "description": "Conectores para resolver captchas de imágenes", "license": "MIT", - "keywords": [], "authors": [ { "name": "Carlos C Soto", From c059d58ae3f7933de89d2b78f2d9d7b5081dce43 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 12:37:10 -0600 Subject: [PATCH 09/30] Remove deprecated rule --- phpcs.xml.dist | 1 - 1 file changed, 1 deletion(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 299f73e..3a5a85a 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -18,7 +18,6 @@ - From dfc3717a017ce8c6a2c94ebd245b209580f95333 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 12:40:53 -0600 Subject: [PATCH 10/30] Add PHP 8.4 to test matrix --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1aaa294..16806e7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -97,7 +97,7 @@ jobs: runs-on: "ubuntu-latest" strategy: matrix: - php-version: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3'] + php-version: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] steps: - name: Checkout uses: actions/checkout@v4 From 4bdf84328b7df5227a9dfe8aeb658731f1b61cf8 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 12:41:34 -0600 Subject: [PATCH 11/30] Run jobs using PHP 8.4 --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 16806e7..5207351 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.3' + php-version: '8.4' coverage: none tools: composer-normalize env: @@ -39,7 +39,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.3' + php-version: '8.4' coverage: none tools: cs2pr, phpcs env: @@ -56,7 +56,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.3' + php-version: '8.4' coverage: none tools: cs2pr, php-cs-fixer env: @@ -73,7 +73,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.3' + php-version: '8.4' coverage: none tools: composer:v2, cs2pr, phpstan env: From 2f6bd18b1b6e00aeee875bbacd2f87e45ba5edbf Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 12:43:00 -0600 Subject: [PATCH 12/30] Update SonarQube Cloud integration --- .github/workflows/sonarcloud.yml | 109 -------------------------- .github/workflows/sonarqube-cloud.yml | 53 +++++++++++++ 2 files changed, 53 insertions(+), 109 deletions(-) delete mode 100644 .github/workflows/sonarcloud.yml create mode 100644 .github/workflows/sonarqube-cloud.yml diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml deleted file mode 100644 index 5c792e6..0000000 --- a/.github/workflows/sonarcloud.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: sonarcloud -on: - workflow_dispatch: - push: - branches: [ "main" ] - -# Actions -# shivammathur/setup-php@v2 https://github.com/marketplace/actions/setup-php-action -# sonarsource/sonarcloud-github-action@master https://github.com/marketplace/actions/sonarcloud-scan - -jobs: - - tests-coverage: - name: Build code coverage - runs-on: "ubuntu-latest" - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - coverage: xdebug - tools: composer:v2 - env: - fail-fast: true - - name: Get composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - name: Cache dependencies - uses: actions/cache@v4 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer- - - name: Install project dependencies - run: composer upgrade --no-interaction --no-progress --prefer-dist - - name: Create code coverage - run: vendor/bin/phpunit --testdox --verbose --coverage-xml=build/coverage --coverage-clover=build/coverage/clover.xml --log-junit=build/coverage/junit.xml - - name: Store code coverage - uses: actions/upload-artifact@v4 - with: - name: code-coverage - path: build/coverage - - sonarcloud-secrets: - name: SonarCloud check secrets are present - runs-on: ubuntu-latest - outputs: - github: ${{ steps.check-secrets.outputs.github }} - sonar: ${{ steps.check-secrets.outputs.sonar }} - steps: - - name: Check secrets are present - id: check-secrets - run: | - if [ -n "${{ secrets.GITHUB_TOKEN }}" ]; then - echo "github=yes" >> $GITHUB_OUTPUT - else - echo "github=no" >> $GITHUB_OUTPUT - echo "::warning ::GITHUB_TOKEN non set" - fi - if [ -n "${{ secrets.SONAR_TOKEN }}" ]; then - echo "sonar=yes" >> $GITHUB_OUTPUT - else - echo "sonar=no" >> $GITHUB_OUTPUT - echo "::warning ::SONAR_TOKEN non set" - fi - - sonarcloud: - name: SonarCloud Scan and Report - needs: [ "tests-coverage", "sonarcloud-secrets" ] - if: ${{ needs.sonarcloud-secrets.outputs.github == 'yes' && needs.sonarcloud-secrets.outputs.sonar == 'yes' }} - runs-on: "ubuntu-latest" - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Unshallow clone to provide blame information - run: git fetch --unshallow - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - coverage: none - tools: composer:v2 - - name: Get composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - name: Cache dependencies - uses: actions/cache@v4 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer- - - name: Install project dependencies - run: composer upgrade --no-interaction --no-progress --prefer-dist - - name: Obtain code coverage - uses: actions/download-artifact@v4 - with: - name: code-coverage - path: build/coverage - - name: Prepare SonarCloud Code Coverage Files - run: | - sed 's#'$GITHUB_WORKSPACE'#/github/workspace#g' build/coverage/junit.xml > build/sonar-junit.xml - sed 's#'$GITHUB_WORKSPACE'#/github/workspace#g' build/coverage/clover.xml > build/sonar-coverage.xml - - name: SonarCloud Scan - uses: sonarsource/sonarcloud-github-action@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/sonarqube-cloud.yml b/.github/workflows/sonarqube-cloud.yml new file mode 100644 index 0000000..5f098d8 --- /dev/null +++ b/.github/workflows/sonarqube-cloud.yml @@ -0,0 +1,53 @@ +name: "SonarQube Cloud" +on: + workflow_dispatch: + push: + branches: [ "main" ] + +# Actions +# shivammathur/setup-php@v2 https://github.com/marketplace/actions/setup-php-action +# SonarSource/sonarqube-scan-action@v5 https://github.com/marketplace/actions/official-sonarqube-scan + +jobs: + + sonarqube-cloud: + name: SonarCloud Scan and Report + runs-on: "ubuntu-latest" + steps: + - name: Check SONAR_TOKEN secret + run: | + if [ -z "${{ secrets.SONAR_TOKEN }}" ]; then + echo "::warning ::SONAR_TOKEN non set" + exit 1 + fi + - name: Checkout + uses: actions/checkout@v4 + - name: Unshallow clone to provide blame information + run: git fetch --unshallow + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: xdebug + tools: composer:v2 + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + - name: Install project dependencies + run: composer upgrade --no-interaction --no-progress --prefer-dist + - name: Create code coverage + run: vendor/bin/phpunit --testdox --coverage-xml=build/coverage --coverage-clover=build/coverage/clover.xml --log-junit=build/coverage/junit.xml + - name: Prepare SonarCloud Code Coverage Files + run: | + sed 's#'$GITHUB_WORKSPACE'#/github/workspace#g' build/coverage/junit.xml > build/sonar-junit.xml + sed 's#'$GITHUB_WORKSPACE'#/github/workspace#g' build/coverage/clover.xml > build/sonar-coverage.xml + - name: SonarCloud Scan + uses: SonarSource/sonarqube-scan-action@v5 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} From 8fdcd5b5129ed9ca19fa2a79d072bf828a9d3d63 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 12:48:23 -0600 Subject: [PATCH 13/30] Update code standard rules --- .php-cs-fixer.dist.php | 5 ++++- src/HttpClient/HttpClient.php | 4 ++-- src/Resolvers/AntiCaptchaResolver.php | 2 +- .../AntiCaptchaResolver/AntiCaptchaConnector.php | 2 +- src/Resolvers/CaptchaLocalResolver.php | 2 +- src/Resolvers/CommandLineResolver.php | 2 +- .../CommandLineResolver/SymfonyProcessRunner.php | 2 +- tests/Extending/AssertHasPreviousExceptionTrait.php | 2 +- tests/Extending/HasPreviousException.php | 2 +- tests/HttpTestCase.php | 2 +- tests/Unit/CaptchaAnswerTest.php | 2 +- tests/Unit/CaptchaImageTest.php | 2 +- .../AntiCaptchaResolver/AntiCaptchaConnectorTest.php | 8 ++++---- .../CaptchaLocalResolverConnectorTest.php | 6 +++--- 14 files changed, 23 insertions(+), 20 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index d0f7f78..7b79000 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -23,6 +23,7 @@ 'no_empty_statement' => true, 'no_extra_blank_lines' => true, 'type_declaration_spaces' => true, + 'trailing_comma_in_multiline' => ['after_heredoc' => true, 'elements' => ['arrays', 'arguments']], 'no_blank_lines_after_phpdoc' => true, 'object_operator_without_whitespace' => true, 'binary_operator_spaces' => true, @@ -35,16 +36,18 @@ 'standardize_not_equals' => true, 'concat_space' => ['spacing' => 'one'], 'linebreak_after_opening_tag' => true, + 'fully_qualified_strict_types' => true, // symfony:risky 'no_alias_functions' => true, 'self_accessor' => true, // contrib 'not_operator_with_successor_space' => true, + 'ordered_imports' => ['imports_order' => ['class', 'function', 'const']], // @PSR12 sort_algorithm: none ]) ->setFinder( PhpCsFixer\Finder::create() ->in(__DIR__) ->append([__FILE__]) - ->exclude(['vendor', 'tools', 'build']) + ->exclude(['vendor', 'tools', 'build']), ) ; diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index edbbcb0..b8aacc8 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -47,7 +47,7 @@ public static function discover(): self return new self( Psr18ClientDiscovery::find(), Psr17FactoryDiscovery::findRequestFactory(), - Psr17FactoryDiscovery::findStreamFactory() + Psr17FactoryDiscovery::findStreamFactory(), ); // @codeCoverageIgnoreStart } catch (Throwable $exception) { @@ -93,7 +93,7 @@ public function postJson(string $uri, array $headers = [], $data = null): Respon $request = $this->createJsonRequest('POST', $uri, $headers); $request = $request->withBody( - $this->streamFactory->createStream(json_encode($data) ?: '') + $this->streamFactory->createStream(json_encode($data) ?: ''), ); return $this->send($request); diff --git a/src/Resolvers/AntiCaptchaResolver.php b/src/Resolvers/AntiCaptchaResolver.php index 4c5b0bb..ef94b7c 100644 --- a/src/Resolvers/AntiCaptchaResolver.php +++ b/src/Resolvers/AntiCaptchaResolver.php @@ -66,7 +66,7 @@ public static function create( ): self { return new self( new AntiCaptchaConnector($clientKey), - new Timer($initialWaitSeconds, $timeoutSeconds, $waitMilliseconds) + new Timer($initialWaitSeconds, $timeoutSeconds, $waitMilliseconds), ); } diff --git a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php index c0d21aa..3bbf036 100644 --- a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php +++ b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php @@ -145,7 +145,7 @@ public function request(string $methodName, array $postData): stdClass $errorId = intval($result->errorId ?? 0); if ($errorId > 0) { throw new RuntimeException( - sprintf('Anti-Captcha Error (%d): %s', $errorId, $result->errorDescription ?? '') + sprintf('Anti-Captcha Error (%d): %s', $errorId, $result->errorDescription ?? ''), ); } diff --git a/src/Resolvers/CaptchaLocalResolver.php b/src/Resolvers/CaptchaLocalResolver.php index 21f2e74..07d5646 100644 --- a/src/Resolvers/CaptchaLocalResolver.php +++ b/src/Resolvers/CaptchaLocalResolver.php @@ -52,7 +52,7 @@ public static function create( ): self { return new self( new CaptchaLocalResolverConnector($baseUrl), - new Timer($initialWaitSeconds, $timeoutSeconds, $sleepMilliseconds) + new Timer($initialWaitSeconds, $timeoutSeconds, $sleepMilliseconds), ); } diff --git a/src/Resolvers/CommandLineResolver.php b/src/Resolvers/CommandLineResolver.php index 8cdf255..71181cd 100644 --- a/src/Resolvers/CommandLineResolver.php +++ b/src/Resolvers/CommandLineResolver.php @@ -61,7 +61,7 @@ public static function create( return new self( $command, $answerBuilder ?? new CommandLineResolver\LastLineAnswerBuilder(), - $processRunner ?? new CommandLineResolver\SymfonyProcessRunner() + $processRunner ?? new CommandLineResolver\SymfonyProcessRunner(), ); } diff --git a/src/Resolvers/CommandLineResolver/SymfonyProcessRunner.php b/src/Resolvers/CommandLineResolver/SymfonyProcessRunner.php index 81b03f9..c80c2bc 100644 --- a/src/Resolvers/CommandLineResolver/SymfonyProcessRunner.php +++ b/src/Resolvers/CommandLineResolver/SymfonyProcessRunner.php @@ -40,7 +40,7 @@ public function run(string ...$command): ProcessResult $process->run( function (string $type, string $text) use (&$output): void { $output[] = rtrim($text, PHP_EOL); - } + }, ); } catch (ProcessTimedOutException $exception) { throw new RuntimeException("Process timeout after {$this->getTimeoutSeconds()} seconds", 0, $exception); diff --git a/tests/Extending/AssertHasPreviousExceptionTrait.php b/tests/Extending/AssertHasPreviousExceptionTrait.php index 72f469d..8d7f0fb 100644 --- a/tests/Extending/AssertHasPreviousExceptionTrait.php +++ b/tests/Extending/AssertHasPreviousExceptionTrait.php @@ -17,7 +17,7 @@ public static function assertHasPreviousException( Assert::assertThat( $expectedException, static::hasPreviousException($exception), - $message + $message, ); } diff --git a/tests/Extending/HasPreviousException.php b/tests/Extending/HasPreviousException.php index 6e20fd9..cb94fed 100644 --- a/tests/Extending/HasPreviousException.php +++ b/tests/Extending/HasPreviousException.php @@ -22,7 +22,7 @@ public function toString(): string return sprintf( ' is part of previous exception chain of %s &%s', get_class($this->exception), - spl_object_hash($this->exception) + spl_object_hash($this->exception), ); } diff --git a/tests/HttpTestCase.php b/tests/HttpTestCase.php index 4c0e572..064fc9e 100644 --- a/tests/HttpTestCase.php +++ b/tests/HttpTestCase.php @@ -56,7 +56,7 @@ protected function createJsonRespose(array $data): ResponseInterface return $responseFactory->createResponse() ->withHeader('Content-Type', 'application/json') ->withBody( - $streamFactory->createStream(json_encode($data) ?: '') + $streamFactory->createStream(json_encode($data) ?: ''), ); } diff --git a/tests/Unit/CaptchaAnswerTest.php b/tests/Unit/CaptchaAnswerTest.php index 2b94e54..1a39b24 100644 --- a/tests/Unit/CaptchaAnswerTest.php +++ b/tests/Unit/CaptchaAnswerTest.php @@ -23,7 +23,7 @@ public function testObjectProperties(): void $this->assertSame($value, $answer->jsonSerialize()); $this->assertJsonStringEqualsJsonString( json_encode($value) ?: '', - json_encode($answer, JSON_THROW_ON_ERROR) + json_encode($answer, JSON_THROW_ON_ERROR), ); } diff --git a/tests/Unit/CaptchaImageTest.php b/tests/Unit/CaptchaImageTest.php index 2e0d771..cf52a26 100644 --- a/tests/Unit/CaptchaImageTest.php +++ b/tests/Unit/CaptchaImageTest.php @@ -29,7 +29,7 @@ public function testObjectProperties(): void $this->assertSame($contentBase64, $image->jsonSerialize()); $this->assertJsonStringEqualsJsonString( json_encode($contentBase64) ?: '', - json_encode($image, JSON_THROW_ON_ERROR) + json_encode($image, JSON_THROW_ON_ERROR), ); } diff --git a/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php b/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php index d1c4f1b..1d70a61 100644 --- a/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php +++ b/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php @@ -84,7 +84,7 @@ public function testGetTaskResultReady(): void $phpHttpMockClient = $this->createPhpHttpMockClient(); $phpHttpMockClient->addResponse( - $this->createJsonRespose(['status' => 'Ready', 'solution' => ['text' => $expectedResult]]) + $this->createJsonRespose(['status' => 'Ready', 'solution' => ['text' => $expectedResult]]), ); $connector = $this->createConnector($this->createHttpClient($phpHttpMockClient)); @@ -119,13 +119,13 @@ public function testRequest(): void $this->assertJsonStringEqualsJsonString( json_encode($requestData + ['clientKey' => $this->clientKey]) ?: '', - (string) $request->getBody() + (string) $request->getBody(), ); $this->assertEquals( json_decode(json_encode($responseData) ?: ''), $result, - 'result was expected to be an object with the same values' + 'result was expected to be an object with the same values', ); } @@ -133,7 +133,7 @@ public function testRequestWithErrorInResponse(): void { $phpHttpMockClient = $this->createPhpHttpMockClient(); $phpHttpMockClient->addResponse( - $this->createJsonRespose(['errorId' => '1', 'errorDescription' => 'description']) + $this->createJsonRespose(['errorId' => '1', 'errorDescription' => 'description']), ); $connector = $this->createConnector($this->createHttpClient($phpHttpMockClient)); diff --git a/tests/Unit/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnectorTest.php b/tests/Unit/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnectorTest.php index bb810c0..776b2c6 100644 --- a/tests/Unit/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnectorTest.php +++ b/tests/Unit/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnectorTest.php @@ -63,7 +63,7 @@ public function testSendImage(): void $this->assertSame($serviceCode, $code); $this->assertJsonStringEqualsJsonString( json_encode(['image' => $image->asBase64()]) ?: '', - (string) $lastRequest->getBody() + (string) $lastRequest->getBody(), ); } @@ -112,7 +112,7 @@ public function testCheckCodeWithAnswer(): void $this->assertSame($serviceAnswer, $answer); $this->assertJsonStringEqualsJsonString( json_encode(['code' => $serviceCode]) ?: '', - (string) $lastRequest->getBody() + (string) $lastRequest->getBody(), ); } @@ -133,7 +133,7 @@ public function testCheckCodeWithoutAnswer(): void $this->assertSame($serviceAnswer, $answer); $this->assertJsonStringEqualsJsonString( json_encode(['code' => $serviceCode]) ?: '', - (string) $lastRequest->getBody() + (string) $lastRequest->getBody(), ); } From d155c803b29d0df1df386135130404a99fa92ae7 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 12:53:26 -0600 Subject: [PATCH 14/30] Prepare version 0.2.5 --- docs/CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 90bced6..b29d59c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,6 +5,23 @@ Usamos [Versionado Semántico 2.0.0](SEMVER.md) por lo que puedes usar esta libr Pueden aparecer cambios no liberados que se integran a la rama principal, pero no ameritan una nueva liberación de versión, aunque sí su incorporación en la rama principal de trabajo. Generalmente, se tratan de cambios en el desarrollo. +## Versión 0.2.5 2025-09-12 + +- Se revisa la compatibilidad de PHP 8.4. +- Se cambian las definiciones implícitas a tipos *nullables* a explícitas. +- Se actualizó el año de la licencia. +- Se hacen diversos cambios para asegurar los tipos de datos y satisfacer PHPStan. + +Cambios de mantenimiento al entorno de desarrollo: + +- Se normalizó `composer.json`. +- Se actualizan las reglas para `php-cs-fixer` y `phpcs`. +- En los flujos de trabajo de GitHub: + - Se agrega PHP 8.4 a la matrix de pruebas. + - Los trabajos se ejecutan en PHP 8.4. + - Se actualiza la integración con SonarQube Cloud. +- Se actualizaron las herramientas de desarrollo. + ## Versión 0.2.4 2024-10-03 - Se agrega la compatibilidad de `symfony/process` a las versiones `6.x` y `7.x`. From d88ceb55fa0fdc182291e34d747112d7cbd453d8 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 12:59:30 -0600 Subject: [PATCH 15/30] Fix badges logo for SonarQube Cloud --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 95580dc..defc81b 100644 --- a/README.md +++ b/README.md @@ -321,6 +321,6 @@ and licensed for use under the MIT License (MIT). Please see [LICENSE][] for mor [badge-build]: https://img.shields.io/github/actions/workflow/status/phpcfdi/image-captcha-resolver/build.yml?branch=main&logo=github-actions [badge-reliability]: https://sonarcloud.io/api/project_badges/measure?project=phpcfdi_image-captcha-resolver&metric=reliability_rating [badge-maintainability]: https://sonarcloud.io/api/project_badges/measure?project=phpcfdi_image-captcha-resolver&metric=sqale_rating -[badge-coverage]: https://img.shields.io/sonar/coverage/phpcfdi_image-captcha-resolver/main?logo=sonarcloud&server=https%3A%2F%2Fsonarcloud.io -[badge-violations]: https://img.shields.io/sonar/violations/phpcfdi_image-captcha-resolver/main?format=long&logo=sonarcloud&server=https%3A%2F%2Fsonarcloud.io +[badge-coverage]: https://img.shields.io/sonar/coverage/phpcfdi_image-captcha-resolver/main?logo=sonarqubecloud&server=https%3A%2F%2Fsonarcloud.io +[badge-violations]: https://img.shields.io/sonar/violations/phpcfdi_image-captcha-resolver/main?format=long&logo=sonarqubecloud&server=https%3A%2F%2Fsonarcloud.io [badge-downloads]: https://img.shields.io/packagist/dt/phpcfdi/image-captcha-resolver?logo=packagist From 2d034083e40f67b763500fd415be015cd6a02507 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 13:13:26 -0600 Subject: [PATCH 16/30] Bump minimal PHP version to 7.4 --- .github/workflows/build.yml | 2 +- composer.json | 2 +- tests/TestCase.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5207351..8cae64c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -97,7 +97,7 @@ jobs: runs-on: "ubuntu-latest" strategy: matrix: - php-version: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + php-version: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/composer.json b/composer.json index 6203edb..01820ee 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "source": "https://github.com/phpcfdi/image-captcha-resolver" }, "require": { - "php": ">=7.3", + "php": ">=7.4", "ext-fileinfo": "*", "ext-json": "*", "php-http/discovery": "^1.14", diff --git a/tests/TestCase.php b/tests/TestCase.php index 6b656be..96cf0c5 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -20,7 +20,7 @@ public static function fileContents(string $filename): string public function checkPortIsOpen(string $hostname, int $port, ?float $timeoutSeconds = null): void { - $timeoutSeconds = $timeoutSeconds ?? intval(ini_get('default_socket_timeout')); + $timeoutSeconds ??= intval(ini_get('default_socket_timeout')); $socket = fsockopen($hostname, $port, $errorNumber, $errorMessage, $timeoutSeconds); if (false === $socket) { throw new RuntimeException($errorMessage, $errorNumber); From 731fc415d3c8a9eed1bf5cfa89d78fe2c41f64a7 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 13:19:18 -0600 Subject: [PATCH 17/30] Type declarations --- src/CaptchaAnswer.php | 3 +-- src/CaptchaImage.php | 6 ++---- src/HttpClient/HttpClient.php | 9 +++------ src/HttpClient/HttpException.php | 6 ++---- src/Internal/TemporaryFile.php | 3 +-- src/Resolvers/AntiCaptchaResolver.php | 6 ++---- .../AntiCaptchaResolver/AntiCaptchaConnector.php | 6 ++---- src/Resolvers/CaptchaLocalResolver.php | 6 ++---- .../CaptchaLocalResolverConnector.php | 5 ++--- src/Resolvers/CommandLineResolver.php | 8 +++----- src/Resolvers/CommandLineResolver/ProcessResult.php | 5 ++--- src/Resolvers/ConsoleResolver.php | 3 +-- src/Resolvers/MockResolver.php | 2 +- src/Resolvers/MultiResolver.php | 2 +- src/Timer/Timer.php | 9 +++------ src/UnableToResolveCaptchaException.php | 6 ++---- tests/Extending/HasPreviousException.php | 3 +-- tests/HttpTestCase.php | 9 +++------ .../CaptchaLocalResolverUsageTest.php | 2 +- .../AntiCaptchaResolver/AntiCaptchaConnectorTest.php | 3 +-- .../CaptchaLocalResolverConnectorTest.php | 3 +-- 21 files changed, 37 insertions(+), 68 deletions(-) diff --git a/src/CaptchaAnswer.php b/src/CaptchaAnswer.php index 39f66bd..05228fb 100644 --- a/src/CaptchaAnswer.php +++ b/src/CaptchaAnswer.php @@ -10,8 +10,7 @@ final class CaptchaAnswer implements JsonSerializable, CaptchaAnswerInterface { - /** @var string */ - private $value; + private string $value; public function __construct(string $value) { diff --git a/src/CaptchaImage.php b/src/CaptchaImage.php index 16f4b33..edc8664 100644 --- a/src/CaptchaImage.php +++ b/src/CaptchaImage.php @@ -10,11 +10,9 @@ final class CaptchaImage implements CaptchaImageInterface { - /** @var string */ - private $contents; + private string $contents; - /** @var string */ - private $mimeType; + private string $mimeType; /** * CaptchaImage constructor. diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index b8aacc8..734232d 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -17,14 +17,11 @@ final class HttpClient implements HttpClientInterface { - /** @var ClientInterface */ - private $client; + private ClientInterface $client; - /** @var RequestFactoryInterface */ - private $requestFactory; + private RequestFactoryInterface $requestFactory; - /** @var StreamFactoryInterface */ - private $streamFactory; + private StreamFactoryInterface $streamFactory; public function __construct( ClientInterface $client, diff --git a/src/HttpClient/HttpException.php b/src/HttpClient/HttpException.php index a148e94..48cd6b2 100644 --- a/src/HttpClient/HttpException.php +++ b/src/HttpClient/HttpException.php @@ -12,11 +12,9 @@ class HttpException extends RuntimeException { - /** @var RequestInterface */ - private $request; + private RequestInterface $request; - /** @var ResponseInterface|null */ - private $response; + private ?ResponseInterface $response; public function __construct( RequestInterface $request, diff --git a/src/Internal/TemporaryFile.php b/src/Internal/TemporaryFile.php index 4db41dd..1d1a143 100644 --- a/src/Internal/TemporaryFile.php +++ b/src/Internal/TemporaryFile.php @@ -12,8 +12,7 @@ */ final class TemporaryFile { - /** @var string */ - private $path; + private string $path; public function __construct(string $prefix = '', string $directory = '') { diff --git a/src/Resolvers/AntiCaptchaResolver.php b/src/Resolvers/AntiCaptchaResolver.php index ef94b7c..ca49ccd 100644 --- a/src/Resolvers/AntiCaptchaResolver.php +++ b/src/Resolvers/AntiCaptchaResolver.php @@ -24,11 +24,9 @@ final class AntiCaptchaResolver implements CaptchaResolverInterface public const DEFAULT_WAIT = 2000; - /** @var AntiCaptchaConnector */ - private $connector; + private AntiCaptchaConnector $connector; - /** @var TimerInterface */ - private $timer; + private TimerInterface $timer; public function __construct( AntiCaptchaConnector $connector, diff --git a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php index 3bbf036..8131b6c 100644 --- a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php +++ b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php @@ -22,11 +22,9 @@ class AntiCaptchaConnector { public const BASE_URL = 'https://api.anti-captcha.com/'; - /** @var string */ - private $clientKey; + private string $clientKey; - /** @var HttpClientInterface */ - private $httpClient; + private HttpClientInterface $httpClient; /** * AntiCaptchaConnector constructor. diff --git a/src/Resolvers/CaptchaLocalResolver.php b/src/Resolvers/CaptchaLocalResolver.php index 07d5646..0a17f03 100644 --- a/src/Resolvers/CaptchaLocalResolver.php +++ b/src/Resolvers/CaptchaLocalResolver.php @@ -22,11 +22,9 @@ class CaptchaLocalResolver implements CaptchaResolverInterface public const DEFAULT_WAIT = 500; - /** @var CaptchaLocalResolverConnector */ - private $connector; + private CaptchaLocalResolverConnector $connector; - /** @var TimerInterface */ - private $timer; + private TimerInterface $timer; public function __construct( CaptchaLocalResolverConnector $connector, diff --git a/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php b/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php index 15dc0a3..54a60a7 100644 --- a/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php +++ b/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php @@ -23,10 +23,9 @@ class CaptchaLocalResolverConnector { /** @var string Full URL to access service, by example http://localhost:9095 */ - private $baseUrl; + private string $baseUrl; - /** @var HttpClientInterface */ - private $httpClient; + private HttpClientInterface $httpClient; /** * Connector constructor diff --git a/src/Resolvers/CommandLineResolver.php b/src/Resolvers/CommandLineResolver.php index 71181cd..37b681c 100644 --- a/src/Resolvers/CommandLineResolver.php +++ b/src/Resolvers/CommandLineResolver.php @@ -16,13 +16,11 @@ final class CommandLineResolver implements CaptchaResolverInterface { /** @var string[] */ - private $command; + private array $command; - /** @var CommandLineResolver\AnswerBuilderInterface */ - private $answerBuilder; + private CommandLineResolver\AnswerBuilderInterface $answerBuilder; - /** @var CommandLineResolver\ProcessRunnerInterface */ - private $processRunner; + private CommandLineResolver\ProcessRunnerInterface $processRunner; /** * CommandLineResolver constructor. diff --git a/src/Resolvers/CommandLineResolver/ProcessResult.php b/src/Resolvers/CommandLineResolver/ProcessResult.php index 1afcf2e..efd8bdc 100644 --- a/src/Resolvers/CommandLineResolver/ProcessResult.php +++ b/src/Resolvers/CommandLineResolver/ProcessResult.php @@ -6,11 +6,10 @@ final class ProcessResult { - /** @var int */ - private $exitCode; + private int $exitCode; /** @var string[] */ - private $output; + private array $output; /** * @param int $exitCode diff --git a/src/Resolvers/ConsoleResolver.php b/src/Resolvers/ConsoleResolver.php index e8a1b25..4aa0159 100644 --- a/src/Resolvers/ConsoleResolver.php +++ b/src/Resolvers/ConsoleResolver.php @@ -24,8 +24,7 @@ class ConsoleResolver implements CaptchaResolverInterface public const MAX_WAIT = 300; - /** @var string */ - private $captchaOutputFile; + private string $captchaOutputFile; /** @var float|int */ private $waitForAnswerSeconds; diff --git a/src/Resolvers/MockResolver.php b/src/Resolvers/MockResolver.php index 13e03b7..8dc860f 100644 --- a/src/Resolvers/MockResolver.php +++ b/src/Resolvers/MockResolver.php @@ -14,7 +14,7 @@ final class MockResolver implements CaptchaResolverInterface, Countable { /** @var CaptchaAnswerInterface[]|UnableToResolveCaptchaException[] */ - private $resolveResponses; + private array $resolveResponses; /** * MockResolver constructor. diff --git a/src/Resolvers/MultiResolver.php b/src/Resolvers/MultiResolver.php index 51386d4..8081829 100644 --- a/src/Resolvers/MultiResolver.php +++ b/src/Resolvers/MultiResolver.php @@ -14,7 +14,7 @@ final class MultiResolver implements CaptchaResolverInterface, Countable { /** @var CaptchaResolverInterface[] */ - private $resolvers; + private array $resolvers; /** @var array */ private $lastResults = []; diff --git a/src/Timer/Timer.php b/src/Timer/Timer.php index 27ae1fc..e9a123e 100644 --- a/src/Timer/Timer.php +++ b/src/Timer/Timer.php @@ -6,14 +6,11 @@ final class Timer implements TimerInterface { - /** @var int */ - private $initialSeconds; + private int $initialSeconds; - /** @var int */ - private $timeoutSeconds; + private int $timeoutSeconds; - /** @var int */ - private $waitMilliseconds; + private int $waitMilliseconds; /** @var float */ private $untilTime = 0; diff --git a/src/UnableToResolveCaptchaException.php b/src/UnableToResolveCaptchaException.php index edbdf4c..f3a88fc 100644 --- a/src/UnableToResolveCaptchaException.php +++ b/src/UnableToResolveCaptchaException.php @@ -9,11 +9,9 @@ class UnableToResolveCaptchaException extends RuntimeException { - /** @var CaptchaResolverInterface */ - private $resolver; + private CaptchaResolverInterface $resolver; - /** @var CaptchaImageInterface */ - private $image; + private CaptchaImageInterface $image; public function __construct( CaptchaResolverInterface $resolver, diff --git a/tests/Extending/HasPreviousException.php b/tests/Extending/HasPreviousException.php index cb94fed..9337515 100644 --- a/tests/Extending/HasPreviousException.php +++ b/tests/Extending/HasPreviousException.php @@ -9,8 +9,7 @@ class HasPreviousException extends Constraint { - /** @var Throwable */ - private $exception; + private Throwable $exception; public function __construct(Throwable $exception) { diff --git a/tests/HttpTestCase.php b/tests/HttpTestCase.php index 064fc9e..aacd23a 100644 --- a/tests/HttpTestCase.php +++ b/tests/HttpTestCase.php @@ -16,14 +16,11 @@ abstract class HttpTestCase extends TestCase { - /** @var StreamFactoryInterface */ - private $streamFactory; + private StreamFactoryInterface $streamFactory; - /** @var RequestFactoryInterface */ - private $requestFactory; + private RequestFactoryInterface $requestFactory; - /** @var ResponseFactoryInterface */ - private $responseFactory; + private ResponseFactoryInterface $responseFactory; /** @noinspection PhpUnhandledExceptionInspection */ protected function setUp(): void diff --git a/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php b/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php index ea5c305..e1b8525 100644 --- a/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php +++ b/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php @@ -12,7 +12,7 @@ final class CaptchaLocalResolverUsageTest extends TestCase { /** @var string */ - private $localResolverUrl; + private string $localResolverUrl; protected function setUp(): void { diff --git a/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php b/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php index 1d70a61..eecf32b 100644 --- a/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php +++ b/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php @@ -19,8 +19,7 @@ final class AntiCaptchaConnectorTest extends HttpTestCase { use AssertHasPreviousExceptionTrait; - /** @var string */ - private $clientKey = 'client-key'; + private string $clientKey = 'client-key'; public function createConnector(?HttpClientInterface $client = null): AntiCaptchaConnector { diff --git a/tests/Unit/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnectorTest.php b/tests/Unit/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnectorTest.php index 776b2c6..26016a0 100644 --- a/tests/Unit/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnectorTest.php +++ b/tests/Unit/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnectorTest.php @@ -20,8 +20,7 @@ final class CaptchaLocalResolverConnectorTest extends HttpTestCase { use AssertHasPreviousExceptionTrait; - /** @var string */ - private $baseUrl = 'http://localhost:9095'; + private string $baseUrl = 'http://localhost:9095'; public function createConnectorWithMockClient(HttpClientInterface $client): CaptchaLocalResolverConnector { From d6a8e0ca5ba82d5240eab976bceee2b0e76bd9b0 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 13:21:28 -0600 Subject: [PATCH 18/30] Dead code --- src/CaptchaAnswerInterface.php | 1 - src/CaptchaResolverInterface.php | 2 -- src/HttpClient/HttpClient.php | 8 -------- src/HttpClient/HttpClientInterface.php | 2 -- src/Resolvers/AntiCaptchaResolver.php | 5 ----- .../AntiCaptchaResolver/AntiCaptchaConnector.php | 7 ------- src/Resolvers/CaptchaLocalResolver.php | 5 ----- .../CaptchaLocalResolverConnector.php | 8 -------- src/Resolvers/CommandLineResolver.php | 7 ------- src/Resolvers/CommandLineResolver/ProcessResult.php | 1 - .../CommandLineResolver/ProcessRunnerInterface.php | 2 -- src/Timer/TimerInterface.php | 2 -- tests/Extending/HasPreviousException.php | 1 - tests/HttpTestCase.php | 1 - .../CaptchaLocalResolverUsageTest.php | 1 - tests/Unit/CaptchaAnswerTest.php | 1 - tests/Unit/ConsoleResolverWithInput.php | 1 - 17 files changed, 55 deletions(-) diff --git a/src/CaptchaAnswerInterface.php b/src/CaptchaAnswerInterface.php index 4bc6687..62e19cc 100644 --- a/src/CaptchaAnswerInterface.php +++ b/src/CaptchaAnswerInterface.php @@ -15,7 +15,6 @@ public function getValue(): string; * Compare the current value to another * * @param Stringable|scalar $value hould be a string or string compatible - * @return bool */ public function equalsTo($value): bool; } diff --git a/src/CaptchaResolverInterface.php b/src/CaptchaResolverInterface.php index 6d84f61..298bfdb 100644 --- a/src/CaptchaResolverInterface.php +++ b/src/CaptchaResolverInterface.php @@ -10,8 +10,6 @@ interface CaptchaResolverInterface * Perform the required operations to resolve the captcha * It must never return an empty string * - * @param CaptchaImageInterface $image - * @return CaptchaAnswerInterface * @throws UnableToResolveCaptchaException */ public function resolve(CaptchaImageInterface $image): CaptchaAnswerInterface; diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index 734232d..5e68322 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -54,10 +54,7 @@ public static function discover(): self } /** - * @param string $method - * @param string $uri * @param array $headers - * @return RequestInterface */ public function createRequest(string $method, string $uri, array $headers): RequestInterface { @@ -71,10 +68,7 @@ public function createRequest(string $method, string $uri, array $headers): Requ } /** - * @param string $method - * @param string $uri * @param array $headers - * @return RequestInterface */ public function createJsonRequest(string $method, string $uri, array $headers): RequestInterface { @@ -97,8 +91,6 @@ public function postJson(string $uri, array $headers = [], $data = null): Respon } /** - * @param RequestInterface $request - * @return ResponseInterface * @throws HttpException */ public function send(RequestInterface $request): ResponseInterface diff --git a/src/HttpClient/HttpClientInterface.php b/src/HttpClient/HttpClientInterface.php index 8f94c3f..7bba3ed 100644 --- a/src/HttpClient/HttpClientInterface.php +++ b/src/HttpClient/HttpClientInterface.php @@ -9,10 +9,8 @@ interface HttpClientInterface { /** - * @param string $uri * @param array $headers * @param array|object|mixed $data - * @return ResponseInterface * @throws HttpException */ public function postJson(string $uri, array $headers = [], $data = null): ResponseInterface; diff --git a/src/Resolvers/AntiCaptchaResolver.php b/src/Resolvers/AntiCaptchaResolver.php index ca49ccd..953ac06 100644 --- a/src/Resolvers/AntiCaptchaResolver.php +++ b/src/Resolvers/AntiCaptchaResolver.php @@ -49,11 +49,6 @@ public function getTimer(): TimerInterface /** * Factory method with defaults * - * @param string $clientKey - * @param int $initialWaitSeconds - * @param int $timeoutSeconds - * @param int $waitMilliseconds - * @return self * @throws UndiscoverableClientException */ public static function create( diff --git a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php index 8131b6c..d8be1d3 100644 --- a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php +++ b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php @@ -29,8 +29,6 @@ class AntiCaptchaConnector /** * AntiCaptchaConnector constructor. * - * @param string $clientKey - * @param HttpClientInterface|null $httpClient * @throws UndiscoverableClientException */ public function __construct(string $clientKey, ?HttpClientInterface $httpClient = null) @@ -51,7 +49,6 @@ public function getHttpClient(): HttpClientInterface /** * @param string|Stringable $base64Image - * @return string * @throws RuntimeException */ public function createTask($base64Image): string @@ -80,8 +77,6 @@ public function createTask($base64Image): string } /** - * @param string $taskId - * @return string * @throws RuntimeException */ public function getTaskResult(string $taskId): string @@ -119,9 +114,7 @@ public function getTaskResult(string $taskId): string } /** - * @param string $methodName * @param array $postData - * @return stdClass * @throws RuntimeException When anti-captcha service return an error status */ public function request(string $methodName, array $postData): stdClass diff --git a/src/Resolvers/CaptchaLocalResolver.php b/src/Resolvers/CaptchaLocalResolver.php index 0a17f03..7fe325c 100644 --- a/src/Resolvers/CaptchaLocalResolver.php +++ b/src/Resolvers/CaptchaLocalResolver.php @@ -35,11 +35,6 @@ public function __construct( } /** - * @param string $baseUrl - * @param int $initialWaitSeconds - * @param int $timeoutSeconds - * @param int $sleepMilliseconds - * @return self * @throws UndiscoverableClientException */ public static function create( diff --git a/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php b/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php index 54a60a7..ad10519 100644 --- a/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php +++ b/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php @@ -31,7 +31,6 @@ class CaptchaLocalResolverConnector * Connector constructor * * @param string $baseUrl Full URL to access service, by example http://localhost:9095 - * @param HttpClientInterface|null $httpClient * @throws UndiscoverableClientException */ public function __construct(string $baseUrl, ?HttpClientInterface $httpClient = null) @@ -41,9 +40,6 @@ public function __construct(string $baseUrl, ?HttpClientInterface $httpClient = } /** - * @param CaptchaImageInterface $image - * @param TimerInterface $timer - * @return CaptchaAnswerInterface * @throws RuntimeException if unable to get an answer after seconds * @throws RuntimeException if unable to send image * @throws RuntimeException if code does not exists @@ -69,8 +65,6 @@ public function resolveImage(CaptchaImageInterface $image, TimerInterface $timer } /** - * @param CaptchaImageInterface $image - * @return string * @throws RuntimeException if unable to send image * @throws RuntimeException if image was sent but service returns empty code * @throws RuntimeException if http transaction error occurs @@ -95,8 +89,6 @@ public function sendImage(CaptchaImageInterface $image): string /** * Check code for answer, if empty string means answer does not exist yet * - * @param string $code - * @return string * @throws RuntimeException if code does not exist * @throws RuntimeException if unable to check code * @throws RuntimeException if http transaction error occurs diff --git a/src/Resolvers/CommandLineResolver.php b/src/Resolvers/CommandLineResolver.php index 37b681c..fc44a37 100644 --- a/src/Resolvers/CommandLineResolver.php +++ b/src/Resolvers/CommandLineResolver.php @@ -26,8 +26,6 @@ final class CommandLineResolver implements CaptchaResolverInterface * CommandLineResolver constructor. * * @param string[] $command - * @param CommandLineResolver\AnswerBuilderInterface $answerBuilder - * @param CommandLineResolver\ProcessRunnerInterface $processRunner */ public function __construct( array $command, @@ -47,9 +45,6 @@ public function __construct( /** * @param string[] $command - * @param CommandLineResolver\AnswerBuilderInterface|null $answerBuilder - * @param CommandLineResolver\ProcessRunnerInterface|null $processRunner - * @return static */ public static function create( array $command, @@ -102,8 +97,6 @@ public function buildCommand(string $fileNameArgument): array } /** - * @param CaptchaImageInterface $image - * @return CaptchaAnswerInterface * @throws RuntimeException */ private function realResolve(CaptchaImageInterface $image): CaptchaAnswerInterface diff --git a/src/Resolvers/CommandLineResolver/ProcessResult.php b/src/Resolvers/CommandLineResolver/ProcessResult.php index efd8bdc..5d10c2c 100644 --- a/src/Resolvers/CommandLineResolver/ProcessResult.php +++ b/src/Resolvers/CommandLineResolver/ProcessResult.php @@ -12,7 +12,6 @@ final class ProcessResult private array $output; /** - * @param int $exitCode * @param string[] $output */ public function __construct(int $exitCode, array $output) diff --git a/src/Resolvers/CommandLineResolver/ProcessRunnerInterface.php b/src/Resolvers/CommandLineResolver/ProcessRunnerInterface.php index ccfbc34..2302f97 100644 --- a/src/Resolvers/CommandLineResolver/ProcessRunnerInterface.php +++ b/src/Resolvers/CommandLineResolver/ProcessRunnerInterface.php @@ -9,8 +9,6 @@ interface ProcessRunnerInterface { /** - * @param string ...$command - * @return ProcessResult * @throws RuntimeException on any kind of execution error */ public function run(string ...$command): ProcessResult; diff --git a/src/Timer/TimerInterface.php b/src/Timer/TimerInterface.php index f37b82a..6f91264 100644 --- a/src/Timer/TimerInterface.php +++ b/src/Timer/TimerInterface.php @@ -18,13 +18,11 @@ public function wait(): void; /** * Return true if the timer is expired - * @return bool */ public function isExpired(): bool; /** * The total time to wait to define that the timer is expired - * @return int */ public function getTimeoutSeconds(): int; } diff --git a/tests/Extending/HasPreviousException.php b/tests/Extending/HasPreviousException.php index 9337515..180a61d 100644 --- a/tests/Extending/HasPreviousException.php +++ b/tests/Extending/HasPreviousException.php @@ -38,7 +38,6 @@ protected function failureDescription($other): string /** * @param Throwable $other - * @return bool */ protected function matches($other): bool { diff --git a/tests/HttpTestCase.php b/tests/HttpTestCase.php index aacd23a..0d77e0a 100644 --- a/tests/HttpTestCase.php +++ b/tests/HttpTestCase.php @@ -44,7 +44,6 @@ protected function createHttpClient(ClientInterface $client): HttpClientInterfac /** * @param array $data - * @return ResponseInterface */ protected function createJsonRespose(array $data): ResponseInterface { diff --git a/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php b/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php index e1b8525..7fbfb59 100644 --- a/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php +++ b/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php @@ -11,7 +11,6 @@ final class CaptchaLocalResolverUsageTest extends TestCase { - /** @var string */ private string $localResolverUrl; protected function setUp(): void diff --git a/tests/Unit/CaptchaAnswerTest.php b/tests/Unit/CaptchaAnswerTest.php index 1a39b24..f75cfac 100644 --- a/tests/Unit/CaptchaAnswerTest.php +++ b/tests/Unit/CaptchaAnswerTest.php @@ -28,7 +28,6 @@ public function testObjectProperties(): void } /** - * @param string $value * @testWith [""] * ["\n"] */ diff --git a/tests/Unit/ConsoleResolverWithInput.php b/tests/Unit/ConsoleResolverWithInput.php index 7491033..da58a68 100644 --- a/tests/Unit/ConsoleResolverWithInput.php +++ b/tests/Unit/ConsoleResolverWithInput.php @@ -14,7 +14,6 @@ final class ConsoleResolverWithInput extends ConsoleResolver private $stdin; /** - * @param string $input * @return $this * @throws RuntimeException */ From c27ab58834ee662b70b75285c25005f2e21b3f1a Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 13:24:20 -0600 Subject: [PATCH 19/30] Code quality --- src/CaptchaImage.php | 4 ++-- tests/Unit/FakeExpiredTimer.php | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/CaptchaImage.php b/src/CaptchaImage.php index edc8664..beb507b 100644 --- a/src/CaptchaImage.php +++ b/src/CaptchaImage.php @@ -32,7 +32,7 @@ public function __construct(string $contents) throw new InvalidArgumentException('The captcha image is not base64 encoded'); } - $mimeType = self::finfo()->buffer($binary, FILEINFO_MIME_TYPE) ?: ''; + $mimeType = $this->finfo()->buffer($binary, FILEINFO_MIME_TYPE) ?: ''; if ('image/' !== substr($mimeType, 0, 6)) { throw new InvalidArgumentException('The captcha image is not an image'); } @@ -65,7 +65,7 @@ public static function newFromInlineHtml(string $contents): self return self::newFromBase64((string) preg_replace('#\s#', '', $parts['image'])); } - private static function finfo(): finfo + private function finfo(): finfo { // if finfo is used in other places on the project then move it to a static class static $finfo = null; diff --git a/tests/Unit/FakeExpiredTimer.php b/tests/Unit/FakeExpiredTimer.php index d7dac54..6a48ebf 100644 --- a/tests/Unit/FakeExpiredTimer.php +++ b/tests/Unit/FakeExpiredTimer.php @@ -12,12 +12,11 @@ class FakeExpiredTimer implements TimerInterface public $expireAfter; /** @var int */ - private $waitCount; + private $waitCount = 0; public function __construct(int $expireAfter = 0) { $this->expireAfter = $expireAfter; - $this->waitCount = 0; } public function getTimeoutSeconds(): int From b428599a328fd6ba25c336b934e540027803b59b Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 13:32:42 -0600 Subject: [PATCH 20/30] Bump minimal PHP version to 8.0 --- .github/workflows/build.yml | 2 +- .php-cs-fixer.dist.php | 6 +++--- composer.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8cae64c..73c0485 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -97,7 +97,7 @@ jobs: runs-on: "ubuntu-latest" strategy: matrix: - php-version: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + php-version: ['8.0', '8.1', '8.2', '8.3', '8.4'] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 7b79000..bcd8ae2 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -15,15 +15,15 @@ ->setRules([ '@PSR12' => true, '@PSR12:risky' => true, - '@PHP71Migration:risky' => true, - '@PHP73Migration' => true, + '@PHP80Migration:risky' => true, + '@PHP80Migration' => true, // symfony 'class_attributes_separation' => true, 'whitespace_after_comma_in_array' => true, 'no_empty_statement' => true, 'no_extra_blank_lines' => true, 'type_declaration_spaces' => true, - 'trailing_comma_in_multiline' => ['after_heredoc' => true, 'elements' => ['arrays', 'arguments']], + 'trailing_comma_in_multiline' => ['after_heredoc' => true, 'elements' => ['arrays', 'match', 'arguments', 'parameters']], 'no_blank_lines_after_phpdoc' => true, 'object_operator_without_whitespace' => true, 'binary_operator_spaces' => true, diff --git a/composer.json b/composer.json index 01820ee..eb52e13 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "source": "https://github.com/phpcfdi/image-captcha-resolver" }, "require": { - "php": ">=7.4", + "php": ">=8.0", "ext-fileinfo": "*", "ext-json": "*", "php-http/discovery": "^1.14", From 2eeadacc876861049ee93dc8b46c235de5299f56 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 13:33:12 -0600 Subject: [PATCH 21/30] Apply rector/rector changes --- src/CaptchaAnswer.php | 2 +- src/CaptchaImage.php | 2 +- src/HttpClient/HttpClient.php | 15 +++------------ src/HttpClient/HttpException.php | 7 ++----- src/Resolvers/AntiCaptchaResolver.php | 14 +++----------- .../AntiCaptchaResolver/AntiCaptchaConnector.php | 5 +---- src/Resolvers/CaptchaLocalResolver.php | 14 +++----------- src/Resolvers/CommandLineResolver.php | 12 +++--------- .../CommandLineResolver/ProcessResult.php | 9 +-------- .../CommandLineResolver/SymfonyProcessRunner.php | 3 +-- src/Resolvers/ConsoleResolver.php | 10 ++++------ src/Timer/Timer.php | 11 +---------- src/UnableToResolveCaptchaException.php | 12 +++--------- .../Extending/AssertHasPreviousExceptionTrait.php | 2 +- tests/Extending/HasPreviousException.php | 11 ++++------- .../CaptchaLocalResolverUsageTest.php | 2 +- tests/Unit/FakeExpiredTimer.php | 3 +-- 17 files changed, 34 insertions(+), 100 deletions(-) diff --git a/src/CaptchaAnswer.php b/src/CaptchaAnswer.php index 05228fb..733b86f 100644 --- a/src/CaptchaAnswer.php +++ b/src/CaptchaAnswer.php @@ -30,7 +30,7 @@ public function equalsTo($value): bool { try { return $this->value === strval($value); - } catch (Throwable $ex) { + } catch (Throwable) { return false; } } diff --git a/src/CaptchaImage.php b/src/CaptchaImage.php index beb507b..3464f7c 100644 --- a/src/CaptchaImage.php +++ b/src/CaptchaImage.php @@ -33,7 +33,7 @@ public function __construct(string $contents) } $mimeType = $this->finfo()->buffer($binary, FILEINFO_MIME_TYPE) ?: ''; - if ('image/' !== substr($mimeType, 0, 6)) { + if (! str_starts_with($mimeType, 'image/')) { throw new InvalidArgumentException('The captcha image is not an image'); } diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index 5e68322..ffd81d7 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -17,20 +17,11 @@ final class HttpClient implements HttpClientInterface { - private ClientInterface $client; - - private RequestFactoryInterface $requestFactory; - - private StreamFactoryInterface $streamFactory; - public function __construct( - ClientInterface $client, - RequestFactoryInterface $requestFactory, - StreamFactoryInterface $streamFactory + private ClientInterface $client, + private RequestFactoryInterface $requestFactory, + private StreamFactoryInterface $streamFactory, ) { - $this->client = $client; - $this->requestFactory = $requestFactory; - $this->streamFactory = $streamFactory; } /** diff --git a/src/HttpClient/HttpException.php b/src/HttpClient/HttpException.php index 48cd6b2..addfe8f 100644 --- a/src/HttpClient/HttpException.php +++ b/src/HttpClient/HttpException.php @@ -14,16 +14,13 @@ class HttpException extends RuntimeException { private RequestInterface $request; - private ?ResponseInterface $response; - public function __construct( RequestInterface $request, - ?ResponseInterface $response = null, - ?Throwable $previous = null + private ?ResponseInterface $response = null, + ?Throwable $previous = null, ) { parent::__construct("Error on {$request->getMethod()} {$request->getUri()}", 0, $previous); $this->request = $request; - $this->response = $response; } public function getRequest(): RequestInterface diff --git a/src/Resolvers/AntiCaptchaResolver.php b/src/Resolvers/AntiCaptchaResolver.php index 953ac06..c502cc6 100644 --- a/src/Resolvers/AntiCaptchaResolver.php +++ b/src/Resolvers/AntiCaptchaResolver.php @@ -24,16 +24,8 @@ final class AntiCaptchaResolver implements CaptchaResolverInterface public const DEFAULT_WAIT = 2000; - private AntiCaptchaConnector $connector; - - private TimerInterface $timer; - - public function __construct( - AntiCaptchaConnector $connector, - TimerInterface $timer - ) { - $this->connector = $connector; - $this->timer = $timer; + public function __construct(private AntiCaptchaConnector $connector, private TimerInterface $timer) + { } public function getConnector(): AntiCaptchaConnector @@ -55,7 +47,7 @@ public static function create( string $clientKey, int $initialWaitSeconds = self::DEFAULT_INITIAL_WAIT, int $timeoutSeconds = self::DEFAULT_TIMEOUT, - int $waitMilliseconds = self::DEFAULT_WAIT + int $waitMilliseconds = self::DEFAULT_WAIT, ): self { return new self( new AntiCaptchaConnector($clientKey), diff --git a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php index d8be1d3..f6dd325 100644 --- a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php +++ b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php @@ -22,8 +22,6 @@ class AntiCaptchaConnector { public const BASE_URL = 'https://api.anti-captcha.com/'; - private string $clientKey; - private HttpClientInterface $httpClient; /** @@ -31,9 +29,8 @@ class AntiCaptchaConnector * * @throws UndiscoverableClientException */ - public function __construct(string $clientKey, ?HttpClientInterface $httpClient = null) + public function __construct(private string $clientKey, ?HttpClientInterface $httpClient = null) { - $this->clientKey = $clientKey; $this->httpClient = $httpClient ?? HttpClient::discover(); } diff --git a/src/Resolvers/CaptchaLocalResolver.php b/src/Resolvers/CaptchaLocalResolver.php index 7fe325c..927d80b 100644 --- a/src/Resolvers/CaptchaLocalResolver.php +++ b/src/Resolvers/CaptchaLocalResolver.php @@ -22,16 +22,8 @@ class CaptchaLocalResolver implements CaptchaResolverInterface public const DEFAULT_WAIT = 500; - private CaptchaLocalResolverConnector $connector; - - private TimerInterface $timer; - - public function __construct( - CaptchaLocalResolverConnector $connector, - TimerInterface $timer - ) { - $this->connector = $connector; - $this->timer = $timer; + public function __construct(private CaptchaLocalResolverConnector $connector, private TimerInterface $timer) + { } /** @@ -41,7 +33,7 @@ public static function create( string $baseUrl, int $initialWaitSeconds = self::DEFAULT_INITIAL_WAIT, int $timeoutSeconds = self::DEFAULT_TIMEOUT, - int $sleepMilliseconds = self::DEFAULT_WAIT + int $sleepMilliseconds = self::DEFAULT_WAIT, ): self { return new self( new CaptchaLocalResolverConnector($baseUrl), diff --git a/src/Resolvers/CommandLineResolver.php b/src/Resolvers/CommandLineResolver.php index fc44a37..77455e1 100644 --- a/src/Resolvers/CommandLineResolver.php +++ b/src/Resolvers/CommandLineResolver.php @@ -18,10 +18,6 @@ final class CommandLineResolver implements CaptchaResolverInterface /** @var string[] */ private array $command; - private CommandLineResolver\AnswerBuilderInterface $answerBuilder; - - private CommandLineResolver\ProcessRunnerInterface $processRunner; - /** * CommandLineResolver constructor. * @@ -29,8 +25,8 @@ final class CommandLineResolver implements CaptchaResolverInterface */ public function __construct( array $command, - CommandLineResolver\AnswerBuilderInterface $answerBuilder, - CommandLineResolver\ProcessRunnerInterface $processRunner + private CommandLineResolver\AnswerBuilderInterface $answerBuilder, + private CommandLineResolver\ProcessRunnerInterface $processRunner, ) { if ([] === $command) { throw new LogicException('Invalid command argument'); @@ -39,8 +35,6 @@ public function __construct( throw new LogicException('Command cannot be "{file}"'); } $this->command = $command; - $this->answerBuilder = $answerBuilder; - $this->processRunner = $processRunner; } /** @@ -49,7 +43,7 @@ public function __construct( public static function create( array $command, ?CommandLineResolver\AnswerBuilderInterface $answerBuilder = null, - ?CommandLineResolver\ProcessRunnerInterface $processRunner = null + ?CommandLineResolver\ProcessRunnerInterface $processRunner = null, ): self { return new self( $command, diff --git a/src/Resolvers/CommandLineResolver/ProcessResult.php b/src/Resolvers/CommandLineResolver/ProcessResult.php index 5d10c2c..73a242c 100644 --- a/src/Resolvers/CommandLineResolver/ProcessResult.php +++ b/src/Resolvers/CommandLineResolver/ProcessResult.php @@ -6,18 +6,11 @@ final class ProcessResult { - private int $exitCode; - - /** @var string[] */ - private array $output; - /** * @param string[] $output */ - public function __construct(int $exitCode, array $output) + public function __construct(private int $exitCode, private array $output) { - $this->exitCode = $exitCode; - $this->output = $output; } public function getExitCode(): int diff --git a/src/Resolvers/CommandLineResolver/SymfonyProcessRunner.php b/src/Resolvers/CommandLineResolver/SymfonyProcessRunner.php index c80c2bc..3ecfd26 100644 --- a/src/Resolvers/CommandLineResolver/SymfonyProcessRunner.php +++ b/src/Resolvers/CommandLineResolver/SymfonyProcessRunner.php @@ -15,8 +15,7 @@ class SymfonyProcessRunner implements ProcessRunnerInterface { public const DEFAULT_TIMEOUT = 60; - /** @var float */ - private $timeoutSeconds; + private int|float $timeoutSeconds; public function __construct(float $timeoutSeconds = self::DEFAULT_TIMEOUT) { diff --git a/src/Resolvers/ConsoleResolver.php b/src/Resolvers/ConsoleResolver.php index 4aa0159..cb8b4ee 100644 --- a/src/Resolvers/ConsoleResolver.php +++ b/src/Resolvers/ConsoleResolver.php @@ -26,13 +26,11 @@ class ConsoleResolver implements CaptchaResolverInterface private string $captchaOutputFile; - /** @var float|int */ - private $waitForAnswerSeconds; - - public function __construct(string $captchaOutputFile = '', float $waitForAnswerSeconds = self::DEFAULT_WAIT) - { + public function __construct( + string $captchaOutputFile = '', + private float|int $waitForAnswerSeconds = self::DEFAULT_WAIT, + ) { $this->captchaOutputFile = $captchaOutputFile ?: (getcwd() . '/captcha.png'); - $this->waitForAnswerSeconds = $waitForAnswerSeconds; } public function resolve(CaptchaImageInterface $image): CaptchaAnswerInterface diff --git a/src/Timer/Timer.php b/src/Timer/Timer.php index e9a123e..814d75a 100644 --- a/src/Timer/Timer.php +++ b/src/Timer/Timer.php @@ -6,20 +6,11 @@ final class Timer implements TimerInterface { - private int $initialSeconds; - - private int $timeoutSeconds; - - private int $waitMilliseconds; - /** @var float */ private $untilTime = 0; - public function __construct(int $initialSeconds, int $timeoutSeconds, int $waitMilliseconds) + public function __construct(private int $initialSeconds, private int $timeoutSeconds, private int $waitMilliseconds) { - $this->initialSeconds = $initialSeconds; - $this->timeoutSeconds = $timeoutSeconds; - $this->waitMilliseconds = $waitMilliseconds; } public function getInitialSeconds(): int diff --git a/src/UnableToResolveCaptchaException.php b/src/UnableToResolveCaptchaException.php index f3a88fc..1c1c8f9 100644 --- a/src/UnableToResolveCaptchaException.php +++ b/src/UnableToResolveCaptchaException.php @@ -9,18 +9,12 @@ class UnableToResolveCaptchaException extends RuntimeException { - private CaptchaResolverInterface $resolver; - - private CaptchaImageInterface $image; - public function __construct( - CaptchaResolverInterface $resolver, - CaptchaImageInterface $image, - ?Throwable $previous = null + private CaptchaResolverInterface $resolver, + private CaptchaImageInterface $image, + ?Throwable $previous = null, ) { parent::__construct('Unable to resolve captcha image', 0, $previous); - $this->resolver = $resolver; - $this->image = $image; } public function getResolver(): CaptchaResolverInterface diff --git a/tests/Extending/AssertHasPreviousExceptionTrait.php b/tests/Extending/AssertHasPreviousExceptionTrait.php index 8d7f0fb..fb9fa7c 100644 --- a/tests/Extending/AssertHasPreviousExceptionTrait.php +++ b/tests/Extending/AssertHasPreviousExceptionTrait.php @@ -12,7 +12,7 @@ trait AssertHasPreviousExceptionTrait public static function assertHasPreviousException( Throwable $expectedException, Throwable $exception, - string $message = '' + string $message = '', ): void { Assert::assertThat( $expectedException, diff --git a/tests/Extending/HasPreviousException.php b/tests/Extending/HasPreviousException.php index 180a61d..7e928b2 100644 --- a/tests/Extending/HasPreviousException.php +++ b/tests/Extending/HasPreviousException.php @@ -9,18 +9,15 @@ class HasPreviousException extends Constraint { - private Throwable $exception; - - public function __construct(Throwable $exception) + public function __construct(private Throwable $exception) { - $this->exception = $exception; } public function toString(): string { return sprintf( ' is part of previous exception chain of %s &%s', - get_class($this->exception), + $this->exception::class, spl_object_hash($this->exception), ); } @@ -29,9 +26,9 @@ protected function failureDescription($other): string { return sprintf( '%s &%s has previous exception %s &%s', - get_class($this->exception), + $this->exception::class, spl_object_hash($this->exception), - (is_object($other)) ? get_class($other) : gettype($other), + get_debug_type($other), (is_object($other)) ? spl_object_hash($other) : '', ); } diff --git a/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php b/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php index 7fbfb59..a228fd8 100644 --- a/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php +++ b/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php @@ -30,7 +30,7 @@ protected function setUp(): void $port = intval(parse_url($localResolverUrl, PHP_URL_PORT) ?: 80); try { $this->checkPortIsOpen($host, $port); - } catch (RuntimeException $exception) { + } catch (RuntimeException) { $this->markTestSkipped("Captcha local resolver service is not open at $host:$port"); } diff --git a/tests/Unit/FakeExpiredTimer.php b/tests/Unit/FakeExpiredTimer.php index 6a48ebf..d1220bc 100644 --- a/tests/Unit/FakeExpiredTimer.php +++ b/tests/Unit/FakeExpiredTimer.php @@ -11,8 +11,7 @@ class FakeExpiredTimer implements TimerInterface /** @var int */ public $expireAfter; - /** @var int */ - private $waitCount = 0; + private int|float $waitCount = 0; public function __construct(int $expireAfter = 0) { From 7b69fea4534f46e9445f136531902d1e7637520b Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 13:38:02 -0600 Subject: [PATCH 22/30] Bump minimal PHP version to 8.1 --- .github/workflows/build.yml | 2 +- .php-cs-fixer.dist.php | 2 +- composer.json | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 73c0485..2bfe902 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -97,7 +97,7 @@ jobs: runs-on: "ubuntu-latest" strategy: matrix: - php-version: ['8.0', '8.1', '8.2', '8.3', '8.4'] + php-version: ['8.1', '8.2', '8.3', '8.4'] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index bcd8ae2..5fb3399 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -16,7 +16,7 @@ '@PSR12' => true, '@PSR12:risky' => true, '@PHP80Migration:risky' => true, - '@PHP80Migration' => true, + '@PHP81Migration' => true, // symfony 'class_attributes_separation' => true, 'whitespace_after_comma_in_array' => true, diff --git a/composer.json b/composer.json index eb52e13..24acdf0 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "source": "https://github.com/phpcfdi/image-captcha-resolver" }, "require": { - "php": ">=8.0", + "php": ">=8.1", "ext-fileinfo": "*", "ext-json": "*", "php-http/discovery": "^1.14", @@ -31,6 +31,7 @@ "php-http/mock-client": "^1.4", "phpunit/phpunit": "^9.5", "symfony/process": "^5.4 || ^6.4 || ^7.1", + "symfony/process": "^6.4 || ^7.1", "vlucas/phpdotenv": "^5.3" }, "suggest": { From cb4642d676a0426f9a787ad6424b9aac1fcc9d26 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 13:47:08 -0600 Subject: [PATCH 23/30] Apply rector/rector changes --- src/CaptchaAnswer.php | 2 +- src/CaptchaImage.php | 4 ++-- src/HttpClient/HttpClient.php | 6 +++--- src/HttpClient/HttpException.php | 4 ++-- src/Internal/TemporaryFile.php | 2 +- src/Resolvers/AntiCaptchaResolver.php | 6 ++++-- .../AntiCaptchaResolver/AntiCaptchaConnector.php | 4 ++-- src/Resolvers/CaptchaLocalResolver.php | 10 +++++----- .../CaptchaLocalResolverConnector.php | 4 ++-- src/Resolvers/CommandLineResolver.php | 6 +++--- .../CommandLineResolver/ProcessResult.php | 2 +- .../CommandLineResolver/SymfonyProcessRunner.php | 2 +- src/Resolvers/ConsoleResolver.php | 2 +- src/Resolvers/MultiResolver.php | 14 +++++++------- src/Timer/Timer.php | 10 ++++++---- src/UnableToResolveCaptchaException.php | 4 ++-- tests/Extending/HasPreviousException.php | 2 +- 17 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/CaptchaAnswer.php b/src/CaptchaAnswer.php index 733b86f..0cbfa44 100644 --- a/src/CaptchaAnswer.php +++ b/src/CaptchaAnswer.php @@ -10,7 +10,7 @@ final class CaptchaAnswer implements JsonSerializable, CaptchaAnswerInterface { - private string $value; + private readonly string $value; public function __construct(string $value) { diff --git a/src/CaptchaImage.php b/src/CaptchaImage.php index 3464f7c..077c5b4 100644 --- a/src/CaptchaImage.php +++ b/src/CaptchaImage.php @@ -10,9 +10,9 @@ final class CaptchaImage implements CaptchaImageInterface { - private string $contents; + private readonly string $contents; - private string $mimeType; + private readonly string $mimeType; /** * CaptchaImage constructor. diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index ffd81d7..461a439 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -18,9 +18,9 @@ final class HttpClient implements HttpClientInterface { public function __construct( - private ClientInterface $client, - private RequestFactoryInterface $requestFactory, - private StreamFactoryInterface $streamFactory, + private readonly ClientInterface $client, + private readonly RequestFactoryInterface $requestFactory, + private readonly StreamFactoryInterface $streamFactory, ) { } diff --git a/src/HttpClient/HttpException.php b/src/HttpClient/HttpException.php index addfe8f..e081b35 100644 --- a/src/HttpClient/HttpException.php +++ b/src/HttpClient/HttpException.php @@ -12,11 +12,11 @@ class HttpException extends RuntimeException { - private RequestInterface $request; + private readonly RequestInterface $request; public function __construct( RequestInterface $request, - private ?ResponseInterface $response = null, + private readonly ?ResponseInterface $response = null, ?Throwable $previous = null, ) { parent::__construct("Error on {$request->getMethod()} {$request->getUri()}", 0, $previous); diff --git a/src/Internal/TemporaryFile.php b/src/Internal/TemporaryFile.php index 1d1a143..240d041 100644 --- a/src/Internal/TemporaryFile.php +++ b/src/Internal/TemporaryFile.php @@ -12,7 +12,7 @@ */ final class TemporaryFile { - private string $path; + private readonly string $path; public function __construct(string $prefix = '', string $directory = '') { diff --git a/src/Resolvers/AntiCaptchaResolver.php b/src/Resolvers/AntiCaptchaResolver.php index c502cc6..826fd35 100644 --- a/src/Resolvers/AntiCaptchaResolver.php +++ b/src/Resolvers/AntiCaptchaResolver.php @@ -24,8 +24,10 @@ final class AntiCaptchaResolver implements CaptchaResolverInterface public const DEFAULT_WAIT = 2000; - public function __construct(private AntiCaptchaConnector $connector, private TimerInterface $timer) - { + public function __construct( + private readonly AntiCaptchaConnector $connector, + private readonly TimerInterface $timer, + ) { } public function getConnector(): AntiCaptchaConnector diff --git a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php index f6dd325..f990c71 100644 --- a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php +++ b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php @@ -22,14 +22,14 @@ class AntiCaptchaConnector { public const BASE_URL = 'https://api.anti-captcha.com/'; - private HttpClientInterface $httpClient; + private readonly HttpClientInterface $httpClient; /** * AntiCaptchaConnector constructor. * * @throws UndiscoverableClientException */ - public function __construct(private string $clientKey, ?HttpClientInterface $httpClient = null) + public function __construct(private readonly string $clientKey, ?HttpClientInterface $httpClient = null) { $this->httpClient = $httpClient ?? HttpClient::discover(); } diff --git a/src/Resolvers/CaptchaLocalResolver.php b/src/Resolvers/CaptchaLocalResolver.php index 927d80b..dc36f1f 100644 --- a/src/Resolvers/CaptchaLocalResolver.php +++ b/src/Resolvers/CaptchaLocalResolver.php @@ -22,13 +22,13 @@ class CaptchaLocalResolver implements CaptchaResolverInterface public const DEFAULT_WAIT = 500; - public function __construct(private CaptchaLocalResolverConnector $connector, private TimerInterface $timer) - { + public function __construct( + private readonly CaptchaLocalResolverConnector $connector, + private readonly TimerInterface $timer, + ) { } - /** - * @throws UndiscoverableClientException - */ + /** @throws UndiscoverableClientException */ public static function create( string $baseUrl, int $initialWaitSeconds = self::DEFAULT_INITIAL_WAIT, diff --git a/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php b/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php index ad10519..3b8f5c1 100644 --- a/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php +++ b/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php @@ -23,9 +23,9 @@ class CaptchaLocalResolverConnector { /** @var string Full URL to access service, by example http://localhost:9095 */ - private string $baseUrl; + private readonly string $baseUrl; - private HttpClientInterface $httpClient; + private readonly HttpClientInterface $httpClient; /** * Connector constructor diff --git a/src/Resolvers/CommandLineResolver.php b/src/Resolvers/CommandLineResolver.php index 77455e1..1c82d30 100644 --- a/src/Resolvers/CommandLineResolver.php +++ b/src/Resolvers/CommandLineResolver.php @@ -16,7 +16,7 @@ final class CommandLineResolver implements CaptchaResolverInterface { /** @var string[] */ - private array $command; + private readonly array $command; /** * CommandLineResolver constructor. @@ -25,8 +25,8 @@ final class CommandLineResolver implements CaptchaResolverInterface */ public function __construct( array $command, - private CommandLineResolver\AnswerBuilderInterface $answerBuilder, - private CommandLineResolver\ProcessRunnerInterface $processRunner, + private readonly CommandLineResolver\AnswerBuilderInterface $answerBuilder, + private readonly CommandLineResolver\ProcessRunnerInterface $processRunner, ) { if ([] === $command) { throw new LogicException('Invalid command argument'); diff --git a/src/Resolvers/CommandLineResolver/ProcessResult.php b/src/Resolvers/CommandLineResolver/ProcessResult.php index 73a242c..a8ae9b4 100644 --- a/src/Resolvers/CommandLineResolver/ProcessResult.php +++ b/src/Resolvers/CommandLineResolver/ProcessResult.php @@ -9,7 +9,7 @@ final class ProcessResult /** * @param string[] $output */ - public function __construct(private int $exitCode, private array $output) + public function __construct(private readonly int $exitCode, private array $output) { } diff --git a/src/Resolvers/CommandLineResolver/SymfonyProcessRunner.php b/src/Resolvers/CommandLineResolver/SymfonyProcessRunner.php index 3ecfd26..ed9ec51 100644 --- a/src/Resolvers/CommandLineResolver/SymfonyProcessRunner.php +++ b/src/Resolvers/CommandLineResolver/SymfonyProcessRunner.php @@ -15,7 +15,7 @@ class SymfonyProcessRunner implements ProcessRunnerInterface { public const DEFAULT_TIMEOUT = 60; - private int|float $timeoutSeconds; + private readonly int|float $timeoutSeconds; public function __construct(float $timeoutSeconds = self::DEFAULT_TIMEOUT) { diff --git a/src/Resolvers/ConsoleResolver.php b/src/Resolvers/ConsoleResolver.php index cb8b4ee..0b9a178 100644 --- a/src/Resolvers/ConsoleResolver.php +++ b/src/Resolvers/ConsoleResolver.php @@ -24,7 +24,7 @@ class ConsoleResolver implements CaptchaResolverInterface public const MAX_WAIT = 300; - private string $captchaOutputFile; + private readonly string $captchaOutputFile; public function __construct( string $captchaOutputFile = '', diff --git a/src/Resolvers/MultiResolver.php b/src/Resolvers/MultiResolver.php index 8081829..feb12f4 100644 --- a/src/Resolvers/MultiResolver.php +++ b/src/Resolvers/MultiResolver.php @@ -13,18 +13,18 @@ final class MultiResolver implements CaptchaResolverInterface, Countable { - /** @var CaptchaResolverInterface[] */ - private array $resolvers; + /** @var list */ + private readonly array $resolvers; - /** @var array */ - private $lastResults = []; + /** @var list */ + private array $lastResults = []; public function __construct(CaptchaResolverInterface ...$resolvers) { - $this->resolvers = $resolvers; + $this->resolvers = array_values($resolvers); } - /** @return CaptchaResolverInterface[] */ + /** @return list */ public function getResolvers(): array { return $this->resolvers; @@ -58,7 +58,7 @@ public function clearResults(): void $this->lastResults = []; } - /** @return array */ + /** @return list */ public function getLastResults(): array { return $this->lastResults; diff --git a/src/Timer/Timer.php b/src/Timer/Timer.php index 814d75a..9aa6fca 100644 --- a/src/Timer/Timer.php +++ b/src/Timer/Timer.php @@ -6,11 +6,13 @@ final class Timer implements TimerInterface { - /** @var float */ - private $untilTime = 0; + private float $untilTime = 0; - public function __construct(private int $initialSeconds, private int $timeoutSeconds, private int $waitMilliseconds) - { + public function __construct( + private readonly int $initialSeconds, + private readonly int $timeoutSeconds, + private readonly int $waitMilliseconds, + ) { } public function getInitialSeconds(): int diff --git a/src/UnableToResolveCaptchaException.php b/src/UnableToResolveCaptchaException.php index 1c1c8f9..b4db39c 100644 --- a/src/UnableToResolveCaptchaException.php +++ b/src/UnableToResolveCaptchaException.php @@ -10,8 +10,8 @@ class UnableToResolveCaptchaException extends RuntimeException { public function __construct( - private CaptchaResolverInterface $resolver, - private CaptchaImageInterface $image, + private readonly CaptchaResolverInterface $resolver, + private readonly CaptchaImageInterface $image, ?Throwable $previous = null, ) { parent::__construct('Unable to resolve captcha image', 0, $previous); diff --git a/tests/Extending/HasPreviousException.php b/tests/Extending/HasPreviousException.php index 7e928b2..a6314a8 100644 --- a/tests/Extending/HasPreviousException.php +++ b/tests/Extending/HasPreviousException.php @@ -9,7 +9,7 @@ class HasPreviousException extends Constraint { - public function __construct(private Throwable $exception) + public function __construct(private readonly Throwable $exception) { } From eac94c4245d289691278a99c0837e991723a32cc Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 13:54:29 -0600 Subject: [PATCH 24/30] Last minute improvements (review using PHPStorm) --- src/CaptchaAnswerInterface.php | 1 + src/HttpClient/HttpClient.php | 2 +- src/HttpClient/HttpClientInterface.php | 2 +- src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php | 7 ++----- .../CaptchaLocalResolver/CaptchaLocalResolverConnector.php | 2 +- src/Resolvers/MultiResolver.php | 5 ++++- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/CaptchaAnswerInterface.php b/src/CaptchaAnswerInterface.php index 62e19cc..caec41d 100644 --- a/src/CaptchaAnswerInterface.php +++ b/src/CaptchaAnswerInterface.php @@ -15,6 +15,7 @@ public function getValue(): string; * Compare the current value to another * * @param Stringable|scalar $value hould be a string or string compatible + * @noinspection PhpMissingParamTypeInspection */ public function equalsTo($value): bool; } diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index 461a439..2d0fc8e 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -70,7 +70,7 @@ public function createJsonRequest(string $method, string $uri, array $headers): return $this->createRequest($method, $uri, array_merge($jsonHeaders, $headers)); } - public function postJson(string $uri, array $headers = [], $data = null): ResponseInterface + public function postJson(string $uri, array $headers = [], mixed $data = null): ResponseInterface { $request = $this->createJsonRequest('POST', $uri, $headers); diff --git a/src/HttpClient/HttpClientInterface.php b/src/HttpClient/HttpClientInterface.php index 7bba3ed..6d50cc7 100644 --- a/src/HttpClient/HttpClientInterface.php +++ b/src/HttpClient/HttpClientInterface.php @@ -13,5 +13,5 @@ interface HttpClientInterface * @param array|object|mixed $data * @throws HttpException */ - public function postJson(string $uri, array $headers = [], $data = null): ResponseInterface; + public function postJson(string $uri, array $headers = [], mixed $data = null): ResponseInterface; } diff --git a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php index f990c71..93bec27 100644 --- a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php +++ b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php @@ -44,11 +44,8 @@ public function getHttpClient(): HttpClientInterface return $this->httpClient; } - /** - * @param string|Stringable $base64Image - * @throws RuntimeException - */ - public function createTask($base64Image): string + /** @throws RuntimeException */ + public function createTask(string|Stringable $base64Image): string { /** @see https://anti-captcha.com/es/apidoc/task-types/ImageToTextTask */ $postData = [ diff --git a/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php b/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php index 3b8f5c1..2a14390 100644 --- a/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php +++ b/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php @@ -42,7 +42,7 @@ public function __construct(string $baseUrl, ?HttpClientInterface $httpClient = /** * @throws RuntimeException if unable to get an answer after seconds * @throws RuntimeException if unable to send image - * @throws RuntimeException if code does not exists + * @throws RuntimeException if code does not exist * @throws RuntimeException if unable to check code * @throws RuntimeException if http transaction error occurs */ diff --git a/src/Resolvers/MultiResolver.php b/src/Resolvers/MultiResolver.php index feb12f4..694c7a4 100644 --- a/src/Resolvers/MultiResolver.php +++ b/src/Resolvers/MultiResolver.php @@ -16,12 +16,15 @@ final class MultiResolver implements CaptchaResolverInterface, Countable /** @var list */ private readonly array $resolvers; + private readonly int $resolversCount; + /** @var list */ private array $lastResults = []; public function __construct(CaptchaResolverInterface ...$resolvers) { $this->resolvers = array_values($resolvers); + $this->resolversCount = count($this->resolvers); } /** @return list */ @@ -50,7 +53,7 @@ public function resolve(CaptchaImageInterface $image): CaptchaAnswerInterface public function count(): int { - return count($this->resolvers); + return $this->resolversCount; } public function clearResults(): void From 7525ea90ad65a0a62ac7acc80c6486b96d39cfd9 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 14:01:33 -0600 Subject: [PATCH 25/30] Bump to PHP version 8.2 --- .github/workflows/build.yml | 2 +- .php-cs-fixer.dist.php | 4 ++-- composer.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2bfe902..6b8b931 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -97,7 +97,7 @@ jobs: runs-on: "ubuntu-latest" strategy: matrix: - php-version: ['8.1', '8.2', '8.3', '8.4'] + php-version: ['8.2', '8.3', '8.4'] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 5fb3399..98cbc5f 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -15,8 +15,8 @@ ->setRules([ '@PSR12' => true, '@PSR12:risky' => true, - '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, + '@PHP82Migration:risky' => true, + '@PHP82Migration' => true, // symfony 'class_attributes_separation' => true, 'whitespace_after_comma_in_array' => true, diff --git a/composer.json b/composer.json index 24acdf0..cb4362a 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "source": "https://github.com/phpcfdi/image-captcha-resolver" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "ext-fileinfo": "*", "ext-json": "*", "php-http/discovery": "^1.14", From 97bc42092e8cfeeddd8b2e5ac2f500b9141fef8d Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 14:16:48 -0600 Subject: [PATCH 26/30] Upgrade to PHPUnit 10.5 --- .github/workflows/build.yml | 2 +- composer.json | 6 +++--- phpunit.xml.dist | 11 ++++++----- tests/Extending/HasPreviousException.php | 2 +- tests/Unit/CaptchaAnswerTest.php | 1 - tests/Unit/CaptchaImageTest.php | 6 +++--- tests/Unit/FakeExpiredTimer.php | 6 +----- 7 files changed, 15 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b8b931..982eeff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -121,4 +121,4 @@ jobs: - name: Install project dependencies run: composer upgrade --no-interaction --no-progress --prefer-dist - name: Tests (phpunit) - run: vendor/bin/phpunit --testdox --verbose + run: vendor/bin/phpunit --testdox diff --git a/composer.json b/composer.json index cb4362a..94a9e84 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "guzzlehttp/psr7": "^2.0", "php-http/guzzle7-adapter": "^1.0", "php-http/mock-client": "^1.4", - "phpunit/phpunit": "^9.5", + "phpunit/phpunit": "^10.5", "symfony/process": "^5.4 || ^6.4 || ^7.1", "symfony/process": "^6.4 || ^7.1", "vlucas/phpdotenv": "^5.3" @@ -68,7 +68,7 @@ "@php tools/phpcs --colors -sp" ], "dev:coverage": [ - "@php -dzend_extension=xdebug.so -dxdebug.mode=coverage vendor/bin/phpunit --verbose --coverage-html build/coverage/html/" + "@php -dzend_extension=xdebug.so -dxdebug.mode=coverage vendor/bin/phpunit --coverage-html build/coverage/html/" ], "dev:fix-style": [ "@php -r 'exit(intval(PHP_VERSION_ID >= 80000));' || $PHP_BINARY tools/composer-normalize normalize", @@ -77,7 +77,7 @@ ], "dev:test": [ "@dev:check-style", - "@php vendor/bin/phpunit --testdox --verbose --stop-on-failure", + "@php vendor/bin/phpunit --testdox --stop-on-failure", "@php tools/phpstan analyse --no-progress --verbose" ] }, diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9ac7d4d..196d12c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,10 +1,11 @@ + defaultTestSuite="default" +> tests @@ -14,9 +15,9 @@ tests/Integration - + - ./src/ + ./src/ - + diff --git a/tests/Extending/HasPreviousException.php b/tests/Extending/HasPreviousException.php index a6314a8..3d171e5 100644 --- a/tests/Extending/HasPreviousException.php +++ b/tests/Extending/HasPreviousException.php @@ -22,7 +22,7 @@ public function toString(): string ); } - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( '%s &%s has previous exception %s &%s', diff --git a/tests/Unit/CaptchaAnswerTest.php b/tests/Unit/CaptchaAnswerTest.php index f75cfac..6947c04 100644 --- a/tests/Unit/CaptchaAnswerTest.php +++ b/tests/Unit/CaptchaAnswerTest.php @@ -48,7 +48,6 @@ public function testEqualsTo(): void $this->assertTrue($answer->equalsTo($answer)); $this->assertTrue($answer->equalsTo(new CaptchaAnswer($value))); - $this->assertFalse($answer->equalsTo($other)); $this->assertFalse($answer->equalsTo($other)); $this->assertFalse($answer->equalsTo(new CaptchaAnswer($other))); diff --git a/tests/Unit/CaptchaImageTest.php b/tests/Unit/CaptchaImageTest.php index cf52a26..17061e5 100644 --- a/tests/Unit/CaptchaImageTest.php +++ b/tests/Unit/CaptchaImageTest.php @@ -57,7 +57,7 @@ public function testNewFromFile(): void } /** @return array */ - public function providerNewFromBase64Malformed(): array + public static function providerNewFromBase64Malformed(): array { return [ 'empty' => ['', 'The captcha image is empty'], @@ -75,9 +75,9 @@ public function testNewFromBase64Malformed(string $contents, string $expectedMes } /** @return array */ - public function providerNewFromInlineImageMalformed(): array + public static function providerNewFromInlineImageMalformed(): array { - $image = CaptchaImage::newFromFile($this->filePath('red-pixel.gif')); + $image = CaptchaImage::newFromFile(self::filePath('red-pixel.gif')); return [ 'empty' => [''], 'no data part' => ["{$image->getMimeType()};base64,{$image->asBase64()}"], diff --git a/tests/Unit/FakeExpiredTimer.php b/tests/Unit/FakeExpiredTimer.php index d1220bc..9a6e239 100644 --- a/tests/Unit/FakeExpiredTimer.php +++ b/tests/Unit/FakeExpiredTimer.php @@ -8,14 +8,10 @@ class FakeExpiredTimer implements TimerInterface { - /** @var int */ - public $expireAfter; - private int|float $waitCount = 0; - public function __construct(int $expireAfter = 0) + public function __construct(public int $expireAfter = 0) { - $this->expireAfter = $expireAfter; } public function getTimeoutSeconds(): int From 2897c8fbf33eefed37ae404b191660523f0709d6 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 19:22:40 -0600 Subject: [PATCH 27/30] Do not check PHP version on run --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 94a9e84..be7c562 100644 --- a/composer.json +++ b/composer.json @@ -63,16 +63,16 @@ "@dev:test" ], "dev:check-style": [ - "@php -r 'exit(intval(PHP_VERSION_ID >= 80000));' || $PHP_BINARY tools/composer-normalize normalize --dry-run", - "@php -r 'exit(intval(PHP_VERSION_ID >= 74000));' || $PHP_BINARY tools/php-cs-fixer fix --dry-run --verbose || true", + "@php tools/composer-normalize normalize --dry-run", + "@php tools/php-cs-fixer fix --dry-run --verbose || true", "@php tools/phpcs --colors -sp" ], "dev:coverage": [ "@php -dzend_extension=xdebug.so -dxdebug.mode=coverage vendor/bin/phpunit --coverage-html build/coverage/html/" ], "dev:fix-style": [ - "@php -r 'exit(intval(PHP_VERSION_ID >= 80000));' || $PHP_BINARY tools/composer-normalize normalize", - "@php -r 'exit(intval(PHP_VERSION_ID >= 74000));' || $PHP_BINARY tools/php-cs-fixer fix --verbose", + "@php tools/composer-normalize normalize", + "@php tools/php-cs-fixer fix --verbose", "@php tools/phpcbf --colors -sp" ], "dev:test": [ From ffaf84f4536f5528e872c7798d45799c494878f1 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Fri, 12 Sep 2025 19:29:14 -0600 Subject: [PATCH 28/30] Apply rector/rector changes (PHP 8.2) --- src/CaptchaAnswer.php | 4 ++-- src/CaptchaImage.php | 6 +++--- src/HttpClient/HttpClient.php | 8 ++++---- src/Internal/TemporaryFile.php | 4 ++-- src/Resolvers/AntiCaptchaResolver.php | 6 +++--- src/Resolvers/CaptchaLocalResolver.php | 6 +++--- src/Resolvers/CommandLineResolver.php | 8 ++++---- src/Resolvers/CommandLineResolver/ProcessResult.php | 10 +++++----- src/Resolvers/ConsoleResolver.php | 4 ++-- 9 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/CaptchaAnswer.php b/src/CaptchaAnswer.php index 0cbfa44..f4f4151 100644 --- a/src/CaptchaAnswer.php +++ b/src/CaptchaAnswer.php @@ -8,9 +8,9 @@ use LogicException; use Throwable; -final class CaptchaAnswer implements JsonSerializable, CaptchaAnswerInterface +final readonly class CaptchaAnswer implements JsonSerializable, CaptchaAnswerInterface { - private readonly string $value; + private string $value; public function __construct(string $value) { diff --git a/src/CaptchaImage.php b/src/CaptchaImage.php index 077c5b4..0cc0610 100644 --- a/src/CaptchaImage.php +++ b/src/CaptchaImage.php @@ -8,11 +8,11 @@ use InvalidArgumentException; use LogicException; -final class CaptchaImage implements CaptchaImageInterface +final readonly class CaptchaImage implements CaptchaImageInterface { - private readonly string $contents; + private string $contents; - private readonly string $mimeType; + private string $mimeType; /** * CaptchaImage constructor. diff --git a/src/HttpClient/HttpClient.php b/src/HttpClient/HttpClient.php index 2d0fc8e..c6cbf4d 100644 --- a/src/HttpClient/HttpClient.php +++ b/src/HttpClient/HttpClient.php @@ -15,12 +15,12 @@ use RuntimeException; use Throwable; -final class HttpClient implements HttpClientInterface +final readonly class HttpClient implements HttpClientInterface { public function __construct( - private readonly ClientInterface $client, - private readonly RequestFactoryInterface $requestFactory, - private readonly StreamFactoryInterface $streamFactory, + private ClientInterface $client, + private RequestFactoryInterface $requestFactory, + private StreamFactoryInterface $streamFactory, ) { } diff --git a/src/Internal/TemporaryFile.php b/src/Internal/TemporaryFile.php index 240d041..90a74a1 100644 --- a/src/Internal/TemporaryFile.php +++ b/src/Internal/TemporaryFile.php @@ -10,9 +10,9 @@ * Class to create a temporary file and remove it on object destruction * @internal */ -final class TemporaryFile +final readonly class TemporaryFile { - private readonly string $path; + private string $path; public function __construct(string $prefix = '', string $directory = '') { diff --git a/src/Resolvers/AntiCaptchaResolver.php b/src/Resolvers/AntiCaptchaResolver.php index 826fd35..2422835 100644 --- a/src/Resolvers/AntiCaptchaResolver.php +++ b/src/Resolvers/AntiCaptchaResolver.php @@ -16,7 +16,7 @@ use RuntimeException; use Throwable; -final class AntiCaptchaResolver implements CaptchaResolverInterface +final readonly class AntiCaptchaResolver implements CaptchaResolverInterface { public const DEFAULT_INITIAL_WAIT = 4; @@ -25,8 +25,8 @@ final class AntiCaptchaResolver implements CaptchaResolverInterface public const DEFAULT_WAIT = 2000; public function __construct( - private readonly AntiCaptchaConnector $connector, - private readonly TimerInterface $timer, + private AntiCaptchaConnector $connector, + private TimerInterface $timer, ) { } diff --git a/src/Resolvers/CaptchaLocalResolver.php b/src/Resolvers/CaptchaLocalResolver.php index dc36f1f..c5f8fdc 100644 --- a/src/Resolvers/CaptchaLocalResolver.php +++ b/src/Resolvers/CaptchaLocalResolver.php @@ -14,7 +14,7 @@ use PhpCfdi\ImageCaptchaResolver\UnableToResolveCaptchaException; use Throwable; -class CaptchaLocalResolver implements CaptchaResolverInterface +readonly class CaptchaLocalResolver implements CaptchaResolverInterface { public const DEFAULT_INITIAL_WAIT = 5; @@ -23,8 +23,8 @@ class CaptchaLocalResolver implements CaptchaResolverInterface public const DEFAULT_WAIT = 500; public function __construct( - private readonly CaptchaLocalResolverConnector $connector, - private readonly TimerInterface $timer, + private CaptchaLocalResolverConnector $connector, + private TimerInterface $timer, ) { } diff --git a/src/Resolvers/CommandLineResolver.php b/src/Resolvers/CommandLineResolver.php index 1c82d30..3cd7b2e 100644 --- a/src/Resolvers/CommandLineResolver.php +++ b/src/Resolvers/CommandLineResolver.php @@ -13,10 +13,10 @@ use RuntimeException; use Throwable; -final class CommandLineResolver implements CaptchaResolverInterface +final readonly class CommandLineResolver implements CaptchaResolverInterface { /** @var string[] */ - private readonly array $command; + private array $command; /** * CommandLineResolver constructor. @@ -25,8 +25,8 @@ final class CommandLineResolver implements CaptchaResolverInterface */ public function __construct( array $command, - private readonly CommandLineResolver\AnswerBuilderInterface $answerBuilder, - private readonly CommandLineResolver\ProcessRunnerInterface $processRunner, + private CommandLineResolver\AnswerBuilderInterface $answerBuilder, + private CommandLineResolver\ProcessRunnerInterface $processRunner, ) { if ([] === $command) { throw new LogicException('Invalid command argument'); diff --git a/src/Resolvers/CommandLineResolver/ProcessResult.php b/src/Resolvers/CommandLineResolver/ProcessResult.php index a8ae9b4..aa5d316 100644 --- a/src/Resolvers/CommandLineResolver/ProcessResult.php +++ b/src/Resolvers/CommandLineResolver/ProcessResult.php @@ -6,11 +6,11 @@ final class ProcessResult { - /** - * @param string[] $output - */ - public function __construct(private readonly int $exitCode, private array $output) - { + /** @param string[] $output */ + public function __construct( + private readonly int $exitCode, + private array $output, + ) { } public function getExitCode(): int diff --git a/src/Resolvers/ConsoleResolver.php b/src/Resolvers/ConsoleResolver.php index 0b9a178..939d9e5 100644 --- a/src/Resolvers/ConsoleResolver.php +++ b/src/Resolvers/ConsoleResolver.php @@ -30,7 +30,8 @@ public function __construct( string $captchaOutputFile = '', private float|int $waitForAnswerSeconds = self::DEFAULT_WAIT, ) { - $this->captchaOutputFile = $captchaOutputFile ?: (getcwd() . '/captcha.png'); + $this->captchaOutputFile = ('' !== $captchaOutputFile) ? $captchaOutputFile : (getcwd() . '/captcha.png'); + $this->waitForAnswerSeconds = min(max(0, $waitForAnswerSeconds), self::MAX_WAIT); } public function resolve(CaptchaImageInterface $image): CaptchaAnswerInterface @@ -80,7 +81,6 @@ protected function openStdInStream() /** @throws RuntimeException */ protected function readLine(): string { - $this->waitForAnswerSeconds = min($this->waitForAnswerSeconds, self::MAX_WAIT); $timeoutSecs = intval(floor($this->waitForAnswerSeconds) ?: 0); $timeoutUsecs = intval(1000000 * ($this->waitForAnswerSeconds - $timeoutSecs)); From a0e96b26e70615483cce7191b2ea59f63e844cdd Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Sat, 13 Sep 2025 13:40:21 -0600 Subject: [PATCH 29/30] Prepare version 0.3.0 --- composer.json | 1 - docs/CHANGELOG.md | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index be7c562..23cd911 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,6 @@ "php-http/guzzle7-adapter": "^1.0", "php-http/mock-client": "^1.4", "phpunit/phpunit": "^10.5", - "symfony/process": "^5.4 || ^6.4 || ^7.1", "symfony/process": "^6.4 || ^7.1", "vlucas/phpdotenv": "^5.3" }, diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b29d59c..d37a62d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,7 +5,7 @@ Usamos [Versionado Semántico 2.0.0](SEMVER.md) por lo que puedes usar esta libr Pueden aparecer cambios no liberados que se integran a la rama principal, pero no ameritan una nueva liberación de versión, aunque sí su incorporación en la rama principal de trabajo. Generalmente, se tratan de cambios en el desarrollo. -## Versión 0.2.5 2025-09-12 +## Versión 0.3.0 2025-09-13 - Se revisa la compatibilidad de PHP 8.4. - Se cambian las definiciones implícitas a tipos *nullables* a explícitas. From c91c32050e3c3ec548b3c72df4f94afc66079661 Mon Sep 17 00:00:00 2001 From: Carlos C Soto Date: Sat, 13 Sep 2025 13:54:16 -0600 Subject: [PATCH 30/30] Upgrade to PHPUnit 11.5 --- composer.json | 2 +- docs/CHANGELOG.md | 1 + phpunit.xml.dist | 7 ++++--- tests/Unit/CaptchaImageTest.php | 5 +++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 23cd911..22ea6ee 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "guzzlehttp/psr7": "^2.0", "php-http/guzzle7-adapter": "^1.0", "php-http/mock-client": "^1.4", - "phpunit/phpunit": "^10.5", + "phpunit/phpunit": "^11.5", "symfony/process": "^6.4 || ^7.1", "vlucas/phpdotenv": "^5.3" }, diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d37a62d..8de17e5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -15,6 +15,7 @@ versión, aunque sí su incorporación en la rama principal de trabajo. Generalm Cambios de mantenimiento al entorno de desarrollo: - Se normalizó `composer.json`. +- Se actualiza a PHPUnit 11.5. - Se actualizan las reglas para `php-cs-fixer` y `phpcs`. - En los flujos de trabajo de GitHub: - Se agrega PHP 8.4 a la matrix de pruebas. diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 196d12c..275c07d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,10 +1,11 @@ - + @@ -17,7 +18,7 @@ - ./src/ + src diff --git a/tests/Unit/CaptchaImageTest.php b/tests/Unit/CaptchaImageTest.php index 17061e5..bec8f3c 100644 --- a/tests/Unit/CaptchaImageTest.php +++ b/tests/Unit/CaptchaImageTest.php @@ -7,6 +7,7 @@ use InvalidArgumentException; use PhpCfdi\ImageCaptchaResolver\CaptchaImage; use PhpCfdi\ImageCaptchaResolver\Tests\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; final class CaptchaImageTest extends TestCase { @@ -66,7 +67,7 @@ public static function providerNewFromBase64Malformed(): array ]; } - /** @dataProvider providerNewFromBase64Malformed */ + #[DataProvider('providerNewFromBase64Malformed')] public function testNewFromBase64Malformed(string $contents, string $expectedMessage): void { $this->expectException(InvalidArgumentException::class); @@ -88,7 +89,7 @@ public static function providerNewFromInlineImageMalformed(): array ]; } - /** @dataProvider providerNewFromInlineImageMalformed */ + #[DataProvider('providerNewFromInlineImageMalformed')] public function testNewFromInlineImageMalformed(string $inlineImage): void { $this->expectException(InvalidArgumentException::class);