diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1aaa294..982eeff 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:
@@ -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: ['8.2', '8.3', '8.4']
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -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/.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 }}
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 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index d0f7f78..98cbc5f 100644
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -15,14 +15,15 @@
->setRules([
'@PSR12' => true,
'@PSR12:risky' => true,
- '@PHP71Migration:risky' => true,
- '@PHP73Migration' => true,
+ '@PHP82Migration:risky' => true,
+ '@PHP82Migration' => 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', 'match', 'arguments', 'parameters']],
'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/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
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
diff --git a/composer.json b/composer.json
index adb6ac7..22ea6ee 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",
@@ -16,7 +15,7 @@
"source": "https://github.com/phpcfdi/image-captcha-resolver"
},
"require": {
- "php": ">=7.3",
+ "php": ">=8.2",
"ext-fileinfo": "*",
"ext-json": "*",
"php-http/discovery": "^1.14",
@@ -30,8 +29,8 @@
"guzzlehttp/psr7": "^2.0",
"php-http/guzzle7-adapter": "^1.0",
"php-http/mock-client": "^1.4",
- "phpunit/phpunit": "^9.5",
- "symfony/process": "^5.4 || ^6.4 || ^7.1",
+ "phpunit/phpunit": "^11.5",
+ "symfony/process": "^6.4 || ^7.1",
"vlucas/phpdotenv": "^5.3"
},
"suggest": {
@@ -63,21 +62,21 @@
"@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 --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",
- "@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": [
"@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/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 90bced6..8de17e5 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -5,6 +5,24 @@ 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.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.
+- 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 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.
+ - 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`.
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 @@
-
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 9ac7d4d..275c07d 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,10 +1,12 @@
-
+
+ defaultTestSuite="default"
+ colors="true"
+ displayDetailsOnAllIssues="true"
+>
tests
@@ -14,9 +16,9 @@
tests/Integration
-
+
- ./src/
+ src
-
+
diff --git a/src/CaptchaAnswer.php b/src/CaptchaAnswer.php
index 39f66bd..f4f4151 100644
--- a/src/CaptchaAnswer.php
+++ b/src/CaptchaAnswer.php
@@ -8,10 +8,9 @@
use LogicException;
use Throwable;
-final class CaptchaAnswer implements JsonSerializable, CaptchaAnswerInterface
+final readonly class CaptchaAnswer implements JsonSerializable, CaptchaAnswerInterface
{
- /** @var string */
- private $value;
+ private string $value;
public function __construct(string $value)
{
@@ -31,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/CaptchaAnswerInterface.php b/src/CaptchaAnswerInterface.php
index 4bc6687..caec41d 100644
--- a/src/CaptchaAnswerInterface.php
+++ b/src/CaptchaAnswerInterface.php
@@ -15,7 +15,7 @@ public function getValue(): string;
* Compare the current value to another
*
* @param Stringable|scalar $value hould be a string or string compatible
- * @return bool
+ * @noinspection PhpMissingParamTypeInspection
*/
public function equalsTo($value): bool;
}
diff --git a/src/CaptchaImage.php b/src/CaptchaImage.php
index 599da5d..0cc0610 100644
--- a/src/CaptchaImage.php
+++ b/src/CaptchaImage.php
@@ -6,14 +6,13 @@
use finfo;
use InvalidArgumentException;
+use LogicException;
-final class CaptchaImage implements CaptchaImageInterface
+final readonly class CaptchaImage implements CaptchaImageInterface
{
- /** @var string */
- private $contents;
+ private string $contents;
- /** @var string */
- private $mimeType;
+ private string $mimeType;
/**
* CaptchaImage constructor.
@@ -33,8 +32,8 @@ public function __construct(string $contents)
throw new InvalidArgumentException('The captcha image is not base64 encoded');
}
- $mimeType = self::finfo()->buffer($binary, FILEINFO_MIME_TYPE) ?: '';
- if ('image/' !== substr($mimeType, 0, 6)) {
+ $mimeType = $this->finfo()->buffer($binary, FILEINFO_MIME_TYPE) ?: '';
+ if (! str_starts_with($mimeType, 'image/')) {
throw new InvalidArgumentException('The captcha image is not an image');
}
@@ -66,13 +65,16 @@ 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;
if (null === $finfo) {
$finfo = new finfo(); // @codeCoverageIgnore
}
+ if (! $finfo instanceof finfo) {
+ throw new LogicException('Unable to create a finfo instance.');
+ }
return $finfo;
}
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 edbbcb0..c6cbf4d 100644
--- a/src/HttpClient/HttpClient.php
+++ b/src/HttpClient/HttpClient.php
@@ -15,25 +15,13 @@
use RuntimeException;
use Throwable;
-final class HttpClient implements HttpClientInterface
+final readonly class HttpClient implements HttpClientInterface
{
- /** @var ClientInterface */
- private $client;
-
- /** @var RequestFactoryInterface */
- private $requestFactory;
-
- /** @var StreamFactoryInterface */
- private $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;
}
/**
@@ -47,7 +35,7 @@ public static function discover(): self
return new self(
Psr18ClientDiscovery::find(),
Psr17FactoryDiscovery::findRequestFactory(),
- Psr17FactoryDiscovery::findStreamFactory()
+ Psr17FactoryDiscovery::findStreamFactory(),
);
// @codeCoverageIgnoreStart
} catch (Throwable $exception) {
@@ -57,10 +45,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
{
@@ -74,10 +59,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
{
@@ -88,20 +70,18 @@ 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);
$request = $request->withBody(
- $this->streamFactory->createStream(json_encode($data) ?: '')
+ $this->streamFactory->createStream(json_encode($data) ?: ''),
);
return $this->send($request);
}
/**
- * @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..6d50cc7 100644
--- a/src/HttpClient/HttpClientInterface.php
+++ b/src/HttpClient/HttpClientInterface.php
@@ -9,11 +9,9 @@
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;
+ public function postJson(string $uri, array $headers = [], mixed $data = null): ResponseInterface;
}
diff --git a/src/HttpClient/HttpException.php b/src/HttpClient/HttpException.php
index de282fb..e081b35 100644
--- a/src/HttpClient/HttpException.php
+++ b/src/HttpClient/HttpException.php
@@ -12,20 +12,15 @@
class HttpException extends RuntimeException
{
- /** @var RequestInterface */
- private $request;
-
- /** @var ResponseInterface|null */
- private $response;
+ private readonly RequestInterface $request;
public function __construct(
RequestInterface $request,
- ResponseInterface $response = null,
- Throwable $previous = null
+ private readonly ?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/Internal/TemporaryFile.php b/src/Internal/TemporaryFile.php
index 4db41dd..90a74a1 100644
--- a/src/Internal/TemporaryFile.php
+++ b/src/Internal/TemporaryFile.php
@@ -10,10 +10,9 @@
* Class to create a temporary file and remove it on object destruction
* @internal
*/
-final class TemporaryFile
+final readonly 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 4c5b0bb..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;
@@ -24,18 +24,10 @@ final class AntiCaptchaResolver implements CaptchaResolverInterface
public const DEFAULT_WAIT = 2000;
- /** @var AntiCaptchaConnector */
- private $connector;
-
- /** @var TimerInterface */
- private $timer;
-
public function __construct(
- AntiCaptchaConnector $connector,
- TimerInterface $timer
+ private AntiCaptchaConnector $connector,
+ private TimerInterface $timer,
) {
- $this->connector = $connector;
- $this->timer = $timer;
}
public function getConnector(): AntiCaptchaConnector
@@ -51,22 +43,17 @@ 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(
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),
- 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 c0deeeb..93bec27 100644
--- a/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php
+++ b/src/Resolvers/AntiCaptchaResolver/AntiCaptchaConnector.php
@@ -22,22 +22,15 @@ class AntiCaptchaConnector
{
public const BASE_URL = 'https://api.anti-captcha.com/';
- /** @var string */
- private $clientKey;
-
- /** @var HttpClientInterface */
- private $httpClient;
+ private readonly HttpClientInterface $httpClient;
/**
* AntiCaptchaConnector constructor.
*
- * @param string $clientKey
- * @param HttpClientInterface|null $httpClient
* @throws UndiscoverableClientException
*/
- public function __construct(string $clientKey, HttpClientInterface $httpClient = null)
+ public function __construct(private readonly string $clientKey, ?HttpClientInterface $httpClient = null)
{
- $this->clientKey = $clientKey;
$this->httpClient = $httpClient ?? HttpClient::discover();
}
@@ -51,12 +44,8 @@ public function getHttpClient(): HttpClientInterface
return $this->httpClient;
}
- /**
- * @param string|Stringable $base64Image
- * @return string
- * @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 = [
@@ -67,37 +56,59 @@ 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;
}
/**
- * @param string $taskId
- * @return string
* @throws RuntimeException
*/
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 ?? ''));
}
/**
- * @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
@@ -115,10 +126,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/src/Resolvers/CaptchaLocalResolver.php b/src/Resolvers/CaptchaLocalResolver.php
index 21f2e74..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;
@@ -22,37 +22,22 @@ class CaptchaLocalResolver implements CaptchaResolverInterface
public const DEFAULT_WAIT = 500;
- /** @var CaptchaLocalResolverConnector */
- private $connector;
-
- /** @var TimerInterface */
- private $timer;
-
public function __construct(
- CaptchaLocalResolverConnector $connector,
- TimerInterface $timer
+ private CaptchaLocalResolverConnector $connector,
+ private TimerInterface $timer,
) {
- $this->connector = $connector;
- $this->timer = $timer;
}
- /**
- * @param string $baseUrl
- * @param int $initialWaitSeconds
- * @param int $timeoutSeconds
- * @param int $sleepMilliseconds
- * @return self
- * @throws UndiscoverableClientException
- */
+ /** @throws UndiscoverableClientException */
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),
- new Timer($initialWaitSeconds, $timeoutSeconds, $sleepMilliseconds)
+ new Timer($initialWaitSeconds, $timeoutSeconds, $sleepMilliseconds),
);
}
diff --git a/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php b/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php
index 1a903ef..2a14390 100644
--- a/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php
+++ b/src/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnector.php
@@ -23,31 +23,26 @@
class CaptchaLocalResolverConnector
{
/** @var string Full URL to access service, by example http://localhost:9095 */
- private $baseUrl;
+ private readonly string $baseUrl;
- /** @var HttpClientInterface */
- private $httpClient;
+ private readonly HttpClientInterface $httpClient;
/**
* 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)
+ public function __construct(string $baseUrl, ?HttpClientInterface $httpClient = null)
{
$this->baseUrl = rtrim($baseUrl, '/');
$this->httpClient = $httpClient ?? HttpClient::discover();
}
/**
- * @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
+ * @throws RuntimeException if code does not exist
* @throws RuntimeException if unable to check code
* @throws RuntimeException if http transaction error occurs
*/
@@ -70,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
@@ -86,7 +79,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 +87,9 @@ 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 +110,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
diff --git a/src/Resolvers/CommandLineResolver.php b/src/Resolvers/CommandLineResolver.php
index b4ceced..3cd7b2e 100644
--- a/src/Resolvers/CommandLineResolver.php
+++ b/src/Resolvers/CommandLineResolver.php
@@ -13,28 +13,20 @@
use RuntimeException;
use Throwable;
-final class CommandLineResolver implements CaptchaResolverInterface
+final readonly class CommandLineResolver implements CaptchaResolverInterface
{
/** @var string[] */
- private $command;
-
- /** @var CommandLineResolver\AnswerBuilderInterface */
- private $answerBuilder;
-
- /** @var CommandLineResolver\ProcessRunnerInterface */
- private $processRunner;
+ private array $command;
/**
* CommandLineResolver constructor.
*
* @param string[] $command
- * @param CommandLineResolver\AnswerBuilderInterface $answerBuilder
- * @param CommandLineResolver\ProcessRunnerInterface $processRunner
*/
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');
@@ -43,25 +35,20 @@ public function __construct(
throw new LogicException('Command cannot be "{file}"');
}
$this->command = $command;
- $this->answerBuilder = $answerBuilder;
- $this->processRunner = $processRunner;
}
/**
* @param string[] $command
- * @param CommandLineResolver\AnswerBuilderInterface|null $answerBuilder
- * @param CommandLineResolver\ProcessRunnerInterface|null $processRunner
- * @return static
*/
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,
$answerBuilder ?? new CommandLineResolver\LastLineAnswerBuilder(),
- $processRunner ?? new CommandLineResolver\SymfonyProcessRunner()
+ $processRunner ?? new CommandLineResolver\SymfonyProcessRunner(),
);
}
@@ -104,8 +91,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 1afcf2e..aa5d316 100644
--- a/src/Resolvers/CommandLineResolver/ProcessResult.php
+++ b/src/Resolvers/CommandLineResolver/ProcessResult.php
@@ -6,20 +6,11 @@
final class ProcessResult
{
- /** @var int */
- private $exitCode;
-
- /** @var string[] */
- private $output;
-
- /**
- * @param int $exitCode
- * @param string[] $output
- */
- public function __construct(int $exitCode, array $output)
- {
- $this->exitCode = $exitCode;
- $this->output = $output;
+ /** @param string[] $output */
+ public function __construct(
+ private readonly int $exitCode,
+ private array $output,
+ ) {
}
public function getExitCode(): int
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/Resolvers/CommandLineResolver/SymfonyProcessRunner.php b/src/Resolvers/CommandLineResolver/SymfonyProcessRunner.php
index 81b03f9..ed9ec51 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 readonly int|float $timeoutSeconds;
public function __construct(float $timeoutSeconds = self::DEFAULT_TIMEOUT)
{
@@ -40,7 +39,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/src/Resolvers/ConsoleResolver.php b/src/Resolvers/ConsoleResolver.php
index e8a1b25..939d9e5 100644
--- a/src/Resolvers/ConsoleResolver.php
+++ b/src/Resolvers/ConsoleResolver.php
@@ -24,16 +24,14 @@ class ConsoleResolver implements CaptchaResolverInterface
public const MAX_WAIT = 300;
- /** @var string */
- private $captchaOutputFile;
-
- /** @var float|int */
- private $waitForAnswerSeconds;
-
- public function __construct(string $captchaOutputFile = '', float $waitForAnswerSeconds = self::DEFAULT_WAIT)
- {
- $this->captchaOutputFile = $captchaOutputFile ?: (getcwd() . '/captcha.png');
- $this->waitForAnswerSeconds = $waitForAnswerSeconds;
+ private readonly string $captchaOutputFile;
+
+ public function __construct(
+ string $captchaOutputFile = '',
+ private float|int $waitForAnswerSeconds = self::DEFAULT_WAIT,
+ ) {
+ $this->captchaOutputFile = ('' !== $captchaOutputFile) ? $captchaOutputFile : (getcwd() . '/captcha.png');
+ $this->waitForAnswerSeconds = min(max(0, $waitForAnswerSeconds), self::MAX_WAIT);
}
public function resolve(CaptchaImageInterface $image): CaptchaAnswerInterface
@@ -83,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));
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..694c7a4 100644
--- a/src/Resolvers/MultiResolver.php
+++ b/src/Resolvers/MultiResolver.php
@@ -13,18 +13,21 @@
final class MultiResolver implements CaptchaResolverInterface, Countable
{
- /** @var CaptchaResolverInterface[] */
- private $resolvers;
+ /** @var list */
+ private readonly array $resolvers;
- /** @var array */
- private $lastResults = [];
+ private readonly int $resolversCount;
+
+ /** @var list */
+ private array $lastResults = [];
public function __construct(CaptchaResolverInterface ...$resolvers)
{
- $this->resolvers = $resolvers;
+ $this->resolvers = array_values($resolvers);
+ $this->resolversCount = count($this->resolvers);
}
- /** @return CaptchaResolverInterface[] */
+ /** @return list */
public function getResolvers(): array
{
return $this->resolvers;
@@ -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
@@ -58,7 +61,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 27ae1fc..9aa6fca 100644
--- a/src/Timer/Timer.php
+++ b/src/Timer/Timer.php
@@ -6,23 +6,13 @@
final class Timer implements TimerInterface
{
- /** @var int */
- private $initialSeconds;
+ private float $untilTime = 0;
- /** @var int */
- private $timeoutSeconds;
-
- /** @var int */
- private $waitMilliseconds;
-
- /** @var float */
- private $untilTime = 0;
-
- public function __construct(int $initialSeconds, int $timeoutSeconds, int $waitMilliseconds)
- {
- $this->initialSeconds = $initialSeconds;
- $this->timeoutSeconds = $timeoutSeconds;
- $this->waitMilliseconds = $waitMilliseconds;
+ public function __construct(
+ private readonly int $initialSeconds,
+ private readonly int $timeoutSeconds,
+ private readonly int $waitMilliseconds,
+ ) {
}
public function getInitialSeconds(): int
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/src/UnableToResolveCaptchaException.php b/src/UnableToResolveCaptchaException.php
index 047b29b..b4db39c 100644
--- a/src/UnableToResolveCaptchaException.php
+++ b/src/UnableToResolveCaptchaException.php
@@ -9,20 +9,12 @@
class UnableToResolveCaptchaException extends RuntimeException
{
- /** @var CaptchaResolverInterface */
- private $resolver;
-
- /** @var CaptchaImageInterface */
- private $image;
-
public function __construct(
- CaptchaResolverInterface $resolver,
- CaptchaImageInterface $image,
- Throwable $previous = null
+ private readonly CaptchaResolverInterface $resolver,
+ private readonly 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 72f469d..fb9fa7c 100644
--- a/tests/Extending/AssertHasPreviousExceptionTrait.php
+++ b/tests/Extending/AssertHasPreviousExceptionTrait.php
@@ -12,12 +12,12 @@ trait AssertHasPreviousExceptionTrait
public static function assertHasPreviousException(
Throwable $expectedException,
Throwable $exception,
- string $message = ''
+ string $message = '',
): void {
Assert::assertThat(
$expectedException,
static::hasPreviousException($exception),
- $message
+ $message,
);
}
diff --git a/tests/Extending/HasPreviousException.php b/tests/Extending/HasPreviousException.php
index 6e20fd9..3d171e5 100644
--- a/tests/Extending/HasPreviousException.php
+++ b/tests/Extending/HasPreviousException.php
@@ -9,37 +9,32 @@
class HasPreviousException extends Constraint
{
- /** @var Throwable */
- private $exception;
-
- public function __construct(Throwable $exception)
+ public function __construct(private readonly Throwable $exception)
{
- $this->exception = $exception;
}
public function toString(): string
{
return sprintf(
' is part of previous exception chain of %s &%s',
- get_class($this->exception),
- spl_object_hash($this->exception)
+ $this->exception::class,
+ spl_object_hash($this->exception),
);
}
- protected function failureDescription($other): string
+ protected function failureDescription(mixed $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) : '',
);
}
/**
* @param Throwable $other
- * @return bool
*/
protected function matches($other): bool
{
diff --git a/tests/HttpTestCase.php b/tests/HttpTestCase.php
index 4c0e572..0d77e0a 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
@@ -47,7 +44,6 @@ protected function createHttpClient(ClientInterface $client): HttpClientInterfac
/**
* @param array $data
- * @return ResponseInterface
*/
protected function createJsonRespose(array $data): ResponseInterface
{
@@ -56,7 +52,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/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php b/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php
index ea5c305..a228fd8 100644
--- a/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php
+++ b/tests/Integration/CaptchaLocalResolver/CaptchaLocalResolverUsageTest.php
@@ -11,8 +11,7 @@
final class CaptchaLocalResolverUsageTest extends TestCase
{
- /** @var string */
- private $localResolverUrl;
+ private string $localResolverUrl;
protected function setUp(): void
{
@@ -31,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/TestCase.php b/tests/TestCase.php
index 364f907..96cf0c5 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -18,9 +18,9 @@ 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'));
+ $timeoutSeconds ??= intval(ini_get('default_socket_timeout'));
$socket = fsockopen($hostname, $port, $errorNumber, $errorMessage, $timeoutSeconds);
if (false === $socket) {
throw new RuntimeException($errorMessage, $errorNumber);
diff --git a/tests/Unit/CaptchaAnswerTest.php b/tests/Unit/CaptchaAnswerTest.php
index 2b94e54..6947c04 100644
--- a/tests/Unit/CaptchaAnswerTest.php
+++ b/tests/Unit/CaptchaAnswerTest.php
@@ -23,12 +23,11 @@ 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),
);
}
/**
- * @param string $value
* @testWith [""]
* ["\n"]
*/
@@ -49,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 2e0d771..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
{
@@ -29,7 +30,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),
);
}
@@ -57,7 +58,7 @@ public function testNewFromFile(): void
}
/** @return array */
- public function providerNewFromBase64Malformed(): array
+ public static function providerNewFromBase64Malformed(): array
{
return [
'empty' => ['', 'The captcha image is empty'],
@@ -66,7 +67,7 @@ public function providerNewFromBase64Malformed(): array
];
}
- /** @dataProvider providerNewFromBase64Malformed */
+ #[DataProvider('providerNewFromBase64Malformed')]
public function testNewFromBase64Malformed(string $contents, string $expectedMessage): void
{
$this->expectException(InvalidArgumentException::class);
@@ -75,9 +76,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()}"],
@@ -88,7 +89,7 @@ public function providerNewFromInlineImageMalformed(): array
];
}
- /** @dataProvider providerNewFromInlineImageMalformed */
+ #[DataProvider('providerNewFromInlineImageMalformed')]
public function testNewFromInlineImageMalformed(string $inlineImage): void
{
$this->expectException(InvalidArgumentException::class);
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
*/
diff --git a/tests/Unit/FakeExpiredTimer.php b/tests/Unit/FakeExpiredTimer.php
index d7dac54..9a6e239 100644
--- a/tests/Unit/FakeExpiredTimer.php
+++ b/tests/Unit/FakeExpiredTimer.php
@@ -8,16 +8,10 @@
class FakeExpiredTimer implements TimerInterface
{
- /** @var int */
- public $expireAfter;
+ private int|float $waitCount = 0;
- /** @var int */
- private $waitCount;
-
- public function __construct(int $expireAfter = 0)
+ public function __construct(public int $expireAfter = 0)
{
- $this->expireAfter = $expireAfter;
- $this->waitCount = 0;
}
public function getTimeoutSeconds(): int
diff --git a/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php b/tests/Unit/Resolvers/AntiCaptchaResolver/AntiCaptchaConnectorTest.php
index 89907a2..eecf32b 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;
@@ -18,10 +19,9 @@ 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
+ public function createConnector(?HttpClientInterface $client = null): AntiCaptchaConnector
{
return new AntiCaptchaConnector($this->clientKey, $client);
}
@@ -55,12 +55,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
@@ -82,7 +83,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));
@@ -112,17 +113,18 @@ 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(
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',
);
}
@@ -130,7 +132,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 254ef99..26016a0 100644
--- a/tests/Unit/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnectorTest.php
+++ b/tests/Unit/Resolvers/CaptchaLocalResolver/CaptchaLocalResolverConnectorTest.php
@@ -13,14 +13,14 @@
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
{
use AssertHasPreviousExceptionTrait;
- /** @var string */
- private $baseUrl = 'http://localhost:9095';
+ private string $baseUrl = 'http://localhost:9095';
public function createConnectorWithMockClient(HttpClientInterface $client): CaptchaLocalResolverConnector
{
@@ -57,11 +57,12 @@ public function testSendImage(): void
$code = $connector->sendImage($image);
+ /** @var RequestInterface $lastRequest */
$lastRequest = $phpHttpMockClient->getLastRequest();
$this->assertSame($serviceCode, $code);
$this->assertJsonStringEqualsJsonString(
json_encode(['image' => $image->asBase64()]) ?: '',
- (string) $lastRequest->getBody()
+ (string) $lastRequest->getBody(),
);
}
@@ -105,11 +106,12 @@ public function testCheckCodeWithAnswer(): void
$answer = $connector->checkCode($serviceCode);
+ /** @var RequestInterface $lastRequest */
$lastRequest = $phpHttpMockClient->getLastRequest();
$this->assertSame($serviceAnswer, $answer);
$this->assertJsonStringEqualsJsonString(
json_encode(['code' => $serviceCode]) ?: '',
- (string) $lastRequest->getBody()
+ (string) $lastRequest->getBody(),
);
}
@@ -125,11 +127,12 @@ public function testCheckCodeWithoutAnswer(): void
$answer = $connector->checkCode($serviceCode);
+ /** @var RequestInterface $lastRequest */
$lastRequest = $phpHttpMockClient->getLastRequest();
$this->assertSame($serviceAnswer, $answer);
$this->assertJsonStringEqualsJsonString(
json_encode(['code' => $serviceCode]) ?: '',
- (string) $lastRequest->getBody()
+ (string) $lastRequest->getBody(),
);
}