diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c5e55a2..31e3a97 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -10,8 +10,13 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - php: ['8.1', '8.2'] + php: ['8.1', '8.2', '8.3'] stability: [prefer-lowest, prefer-stable] + include: + - php: '8.4' + flags: "--ignore-platform-req=php" + phpunit-flags: '--no-coverage' + stability: prefer-stable steps: - name: Checkout code uses: actions/checkout@v3 @@ -45,11 +50,16 @@ jobs: - name: Run Unit tests with coverage run: composer phpunit -- ${{ matrix.phpunit-flags }} + if: ${{ matrix.php == '8.3' || matrix.php == '8.2' || matrix.php == '8.1'}} + + - name: Run Unit tests without coverage + run: composer phpunit:min + if: ${{ matrix.php == '8.4'}} - name: Run static analysis run: composer phpstan - if: ${{ matrix.php == '8.2' && matrix.stability == 'prefer-stable'}} + if: ${{ matrix.php == '8.3' && matrix.stability == 'prefer-stable'}} - name: Run Coding style rules run: composer phpcs:fix - if: ${{ matrix.php == '8.1' && matrix.stability == 'prefer-stable'}} + if: ${{ matrix.php == '8.3' && matrix.stability == 'prefer-stable'}} diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index e771f58..99023a2 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -17,7 +17,7 @@ 'import_functions' => true, ], 'list_syntax' => ['syntax' => 'short'], - 'new_with_braces' => true, + 'new_with_parentheses' => true, 'no_blank_lines_after_phpdoc' => true, 'no_empty_phpdoc' => true, 'no_empty_comment' => true, @@ -29,6 +29,7 @@ ], 'no_trailing_comma_in_singleline' => true, 'no_unused_imports' => true, + 'nullable_type_declaration_for_default_null_value' => true, 'ordered_imports' => ['imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha'], 'phpdoc_add_missing_param_annotation' => ['only_untyped' => true], 'phpdoc_align' => true, @@ -39,7 +40,7 @@ 'phpdoc_summary' => true, 'psr_autoloading' => true, 'return_type_declaration' => ['space_before' => 'none'], - 'single_blank_line_before_namespace' => true, + 'blank_lines_before_namespace' => true, 'single_quote' => true, 'space_after_semicolon' => true, 'ternary_operator_spaces' => true, diff --git a/README.md b/README.md index 8cd8756..68dc1b3 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,12 @@ You need: - **PHP >= 7.4** but the latest stable version of PHP is recommended - the `intl` extension +- a copy of the [Public Suffix List](https://publicsuffix.org/) data and/or a copy of the [IANA Top Level Domain List](https://www.iana.org/domains/root/files). Please refer to the [Managing external data source section](#managing-the-package-external-resources) for more information when using this package in production. ## Usage -**If you are upgrading from version 5 please check the [upgrading guide](UPGRADING.md) for known issues.** +> [!WARNING] +> If you are upgrading from version 5 please check the [upgrading guide](UPGRADING.md) for known issues. ### Resolving Domains @@ -115,9 +117,10 @@ These methods resolve the domain against their respective data source using the same rules as the `resolve` method but will instead throw an exception if no valid effective TLD is found or if the submitted domain is invalid. -**All these methods expect as their sole argument a `Pdp\Host` implementing +> [!CAUTION] +> All these methods expect as their sole argument a `Pdp\Host` implementing object, but other types (ie: `string`, `null` and stringable objects) are -supported with predefined conditions as explained in the remaining document.** +supported with predefined conditions as explained in the remaining document. ~~~php [!WARNING] +> You SHOULD never resolve domain name this way in production, without, at +least, a caching mechanism to reduce external resource downloads. +> Using the Public Suffix List to determine what is a valid domain name and what +isn't is dangerous, and MAY lead to errors because of new gTLDs being registered +on a regular basis. +> If you are looking to know the validity of a Top Level Domain, you MUST use +the IANA Top Level Domain List as the proper source for this information or +alternatively the DNS. +> If you MUST use this library for any of the above purposes, you SHOULD consider +integrating an updating mechanism into your software. +> For more information go to the [Managing external data source section](#managing-the-package-external-resources)** ### Resolved domain information. @@ -220,10 +218,11 @@ echo $altResult->domain()->toString(); //display 'foo.bar.test.example'; $altResult->suffix()->isKnown(); //return false; ~~~ -**TIP: Always favor submitting a `Pdp\Suffix` object rather that any other +> [!TIP] +> Always favor submitting a `Pdp\Suffix` object rather that any other supported type to avoid unexpected results. By default, if the input is not a `Pdp\Suffix` instance, the resulting public suffix will be labelled as -being unknown. For more information go to the [Public Suffix section](#public-suffix)** +being unknown. For more information go to the [Public Suffix section](#public-suffix) ### Domain Suffix @@ -339,7 +338,8 @@ $newDomain->clear()->labels(); //return [] echo $domain->slice(2)->toString(); //display 'www' ~~~ -**WARNING: Because of its definition, a domain name can be `null` or a string.** +> [!WARNING] +> Because of its definition, a domain name can be `null` or a string. To distinguish this possibility the object exposes two (2) formatting methods `Domain::value` which can be `null` or a `string` and `Domain::toString` which @@ -396,8 +396,9 @@ is done via two (2) named constructors: At any given moment the `Pdp\Domain` instance can tell you whether it is in `ASCII` mode or not. -**Once instantiated there's no way to tell which algorithm is used to convert -the object from ascii to unicode and vice-versa** +> [!WARNING] +> Once instantiated there's no way to tell which algorithm is used to convert +the object from ascii to unicode and vice-versa ~~~php use Pdp\Domain; @@ -419,10 +420,11 @@ echo $asciiDomain->value(); // display 'fass.de' $asciiDomain->isAscii(); // returns true ~~~ -**TIP: Always favor submitting a `Pdp\Domain` object for resolution rather that a +> [!TIP] +> Always favor submitting a `Pdp\Domain` object for resolution rather that a string or an object that can be cast to a string to avoid unexpected format conversion errors/results. By default, and with lack of information conversion -is done using IDNA 2008 rules.** +is done using IDNA 2008 rules. ### Managing the package external resources @@ -469,7 +471,8 @@ on packagist. #### Refreshing the resource using the provided factories -**THIS IS THE RECOMMENDED WAY OF USING THE LIBRARY** +> [!NOTE] +> THIS IS THE RECOMMENDED WAY OF USING THE LIBRARY For the purpose of this example we will use our PSR powered solution with: @@ -526,12 +529,14 @@ $publicSuffixList = $pslStorage->get(PsrStorageFactory::PUBLIC_SUFFIX_LIST_URI); $topLevelDomains = $rzdStorage->get(PsrStorageFactory::TOP_LEVEL_DOMAIN_LIST_URI); ~~~ -**Be sure to adapt the following code to your own application. +> [!NOTE] +> Be sure to adapt the following code to your own application. The following code is an example given without warranty of it working -out of the box.** +out of the box. -**You should use your dependency injection container to avoid repeating this -code in your application.** +> [!WARNING] +> You should use your dependency injection container to avoid repeating this +code in your application. ### Automatic Updates @@ -560,7 +565,6 @@ Testing - a [PHPUnit](https://phpunit.de) test suite - a code analysis compliance test suite using [PHPStan](https://phpstan.org). -- a code analysis compliance test suite using [Psalm](https://psalm.dev). - a coding style compliance test suite using [PHP CS Fixer](https://cs.symfony.com). To run the tests, run the following command from the project folder. diff --git a/composer.json b/composer.json index 66aafde..558f769 100644 --- a/composer.json +++ b/composer.json @@ -46,16 +46,16 @@ "ext-json": "*" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^v3.13.2", - "guzzlehttp/guzzle": "^7.5", - "guzzlehttp/psr7": "^1.6 || ^2.4.3", - "phpstan/phpstan": "^1.10.3", - "phpstan/phpstan-phpunit": "^1.3.8", - "phpstan/phpstan-strict-rules": "^1.5.0", - "phpunit/phpunit": "^10.0.12", - "psr/http-factory": "^1.0.1", + "friendsofphp/php-cs-fixer": "^3.53.0", + "guzzlehttp/guzzle": "^7.8.1", + "guzzlehttp/psr7": "^1.6 || ^2.6.2", + "phpstan/phpstan": "^1.10.66", + "phpstan/phpstan-phpunit": "^1.3.16", + "phpstan/phpstan-strict-rules": "^1.5.3", + "phpunit/phpunit": "^10.5.15 || ^11.1.1", + "psr/http-factory": "^1.0.2", "psr/simple-cache": "^1.0.1", - "symfony/cache": "^v5.0.0 || ^v6.0.0" + "symfony/cache": "^v5.0.0 || ^6.4.6" }, "suggest": { "psr/http-client-implementation": "To use the storage functionnality which depends on PSR-18", @@ -71,8 +71,9 @@ "scripts": { "phpcs": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix -vvv --diff --dry-run --allow-risky=yes --ansi", "phpcs:fix": "php-cs-fixer fix -vvv --allow-risky=yes --ansi", - "phpstan": "phpstan analyse -l max -c phpstan.neon src --xdebug --memory-limit=256M --ansi", + "phpstan": "phpstan analyse -c phpstan.neon --ansi --memory-limit=192M", "phpunit": "XDEBUG_MODE=coverage phpunit --coverage-text", + "phpunit:min": "phpunit --no-coverage", "test": [ "@phpunit", "@phpstan", diff --git a/phpstan.neon b/phpstan.neon index caa678b..19ea5e3 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,6 +3,9 @@ includes: - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon parameters: + level: max + paths: + - src ignoreErrors: - message: '#has no value type specified in iterable type array.#' path: src/Rules.php @@ -12,4 +15,5 @@ parameters: path: src/Rules.php - message: '#Variable \$line on left side of \?\? always exists and is not nullable.#' path: src/Rules.php + - '#^Parameter \#1 \$callback of function set_error_handler expects#' reportUnmatchedIgnoredErrors: true diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4a02ebf..2403d4e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,22 +1,6 @@ - + - - src - - - src - @@ -31,4 +15,12 @@ + + + src + + + src + + diff --git a/src/Domain.php b/src/Domain.php index 358ebdc..1d0bbf7 100644 --- a/src/Domain.php +++ b/src/Domain.php @@ -86,7 +86,7 @@ public function label(int $key): ?string /** * @return list */ - public function keys(string $label = null): array + public function keys(?string $label = null): array { return $this->registeredName->keys($label); } @@ -155,7 +155,7 @@ public function clear(): self /** * @throws CannotProcessHost */ - public function slice(int $offset, int $length = null): self + public function slice(int $offset, ?int $length = null): self { return $this->newInstance($this->registeredName->slice($offset, $length)); } diff --git a/src/DomainName.php b/src/DomainName.php index c4a4aaf..fd6e3b9 100644 --- a/src/DomainName.php +++ b/src/DomainName.php @@ -49,7 +49,7 @@ public function labels(): array; * * @return list */ - public function keys(string $label = null): array; + public function keys(?string $label = null): array; /** * The external iterator iterates over the DomainInterface labels @@ -119,5 +119,5 @@ public function clear(): self; * * If $length is null it returns all elements from $offset to the end of the Domain. */ - public function slice(int $offset, int $length = null): self; + public function slice(int $offset, ?int $length = null): self; } diff --git a/src/DomainTest.php b/src/DomainTest.php index 3ea5315..e3ea7c0 100644 --- a/src/DomainTest.php +++ b/src/DomainTest.php @@ -168,14 +168,14 @@ public static function toUnicodeProvider(): iterable public function testToAscii( ?string $domain, ?string $expectedDomain, - ?string $expectedAsciiDomain + ?string $expectedIDNDomain ): void { $domain = Domain::fromIDNA2008($domain); self::assertSame($expectedDomain, $domain->value()); /** @var Domain $domainIDN */ $domainIDN = $domain->toAscii(); - self::assertSame($expectedAsciiDomain, $domainIDN->value()); + self::assertSame($expectedIDNDomain, $domainIDN->value()); } /** diff --git a/src/RegisteredName.php b/src/RegisteredName.php index 4350932..cd12eea 100644 --- a/src/RegisteredName.php +++ b/src/RegisteredName.php @@ -190,7 +190,7 @@ public function label(int $key): ?string /** * @return list */ - public function keys(string $label = null): array + public function keys(?string $label = null): array { if (null === $label) { return array_keys($this->labels); @@ -345,7 +345,7 @@ public function clear(): self /** * @throws CannotProcessHost */ - public function slice(int $offset, int $length = null): self + public function slice(int $offset, ?int $length = null): self { $nbLabels = count($this->labels); if ($offset < - $nbLabels || $offset > $nbLabels) { diff --git a/src/ResolvedDomainTest.php b/src/ResolvedDomainTest.php index e6f15f2..12c777b 100644 --- a/src/ResolvedDomainTest.php +++ b/src/ResolvedDomainTest.php @@ -173,8 +173,8 @@ public function testItCanBeConvertedToAscii( ?string $publicSuffix, ?string $expectedDomain, ?string $expectedSuffix, - ?string $expectedAsciiDomain, - ?string $expectedAsciiSuffix + ?string $expectedIDNDomain, + ?string $expectedIDNSuffix ): void { $domain = ResolvedDomain::fromUnknown(Domain::fromIDNA2003($domain), count(Domain::fromIDNA2003($publicSuffix))); self::assertSame($expectedDomain, $domain->value()); @@ -182,8 +182,8 @@ public function testItCanBeConvertedToAscii( /** @var ResolvedDomain $domainIDN */ $domainIDN = $domain->toAscii(); - self::assertSame($expectedAsciiDomain, $domainIDN->value()); - self::assertSame($expectedAsciiSuffix, $domainIDN->suffix()->value()); + self::assertSame($expectedIDNDomain, $domainIDN->value()); + self::assertSame($expectedIDNSuffix, $domainIDN->suffix()->value()); } /** @@ -236,7 +236,7 @@ public static function toAsciiProvider(): iterable } #[DataProvider('withSubDomainWorksProvider')] - public function testItCanHaveItsSubDomainChanged(ResolvedDomain $domain, DomainName|string|null $subdomain, string $expected = null): void + public function testItCanHaveItsSubDomainChanged(ResolvedDomain $domain, DomainName|string|null $subdomain, ?string $expected = null): void { $result = $domain->withSubDomain($subdomain); diff --git a/src/Storage/PsrStorageFactoryTest.php b/src/Storage/PsrStorageFactoryTest.php index b423fb4..efa782f 100644 --- a/src/Storage/PsrStorageFactoryTest.php +++ b/src/Storage/PsrStorageFactoryTest.php @@ -15,9 +15,9 @@ final class PsrStorageFactoryTest extends TestCase public function setUp(): void { - $cache = $this->createStub(CacheInterface::class); - $requestFactory = $this->createStub(RequestFactoryInterface::class); - $client = $this->createStub(ClientInterface::class); + $cache = self::createStub(CacheInterface::class); + $requestFactory = self::createStub(RequestFactoryInterface::class); + $client = self::createStub(ClientInterface::class); $this->factory = new PsrStorageFactory($cache, $client, $requestFactory); } diff --git a/src/Storage/PublicSuffixListPsr16CacheTest.php b/src/Storage/PublicSuffixListPsr16CacheTest.php index c9c8989..f41cfb5 100644 --- a/src/Storage/PublicSuffixListPsr16CacheTest.php +++ b/src/Storage/PublicSuffixListPsr16CacheTest.php @@ -9,6 +9,7 @@ use InvalidArgumentException; use Pdp\Rules; use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\ErrorHandler; use Psr\SimpleCache\CacheException; use Psr\SimpleCache\CacheInterface; use RuntimeException; @@ -18,7 +19,7 @@ final class PublicSuffixListPsr16CacheTest extends TestCase { public function testItReturnsNullIfTheCacheDoesNotExists(): void { - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('get')->willReturn(null); $pslCache = new PublicSuffixListPsr16Cache($cache, 'pdp_', '1 DAY'); @@ -29,7 +30,7 @@ public function testItReturnsNullIfTheCacheDoesNotExists(): void public function testItReturnsAnInstanceIfTheCorrectCacheExists(): void { $rules = Rules::fromPath(dirname(__DIR__, 2).'/test_data/public_suffix_list.dat'); - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('get')->willReturn($rules); $pslCache = new PublicSuffixListPsr16Cache($cache, 'pdp_', 86400); @@ -39,7 +40,7 @@ public function testItReturnsAnInstanceIfTheCorrectCacheExists(): void public function testItReturnsNullIfTheCacheContentContainsInvalidJsonData(): void { - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('get')->willReturn('foobar'); $pslCache = new PublicSuffixListPsr16Cache($cache, 'pdp_', 86400); @@ -48,7 +49,7 @@ public function testItReturnsNullIfTheCacheContentContainsInvalidJsonData(): voi public function testItReturnsNullIfTheCacheContentCannotBeConvertedToTheCorrectInstance(): void { - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('get')->willReturn('{"foo":"bar"}'); $pslCache = new PublicSuffixListPsr16Cache($cache, 'pdp_', new DateTimeImmutable('+1 DAY')); @@ -58,7 +59,7 @@ public function testItReturnsNullIfTheCacheContentCannotBeConvertedToTheCorrectI public function testItCanStoreAPublicSuffixListInstance(): void { - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('set')->willReturn(true); $psl = Rules::fromPath(dirname(__DIR__, 2).'/test_data/public_suffix_list.dat'); @@ -69,7 +70,7 @@ public function testItCanStoreAPublicSuffixListInstance(): void public function testItReturnsFalseIfItCantStoreAPublicSuffixListInstance(): void { - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('set')->willReturn(false); $psl = Rules::fromPath(dirname(__DIR__, 2).'/test_data/public_suffix_list.dat'); @@ -82,7 +83,7 @@ public function testItReturnsFalseIfItCantCacheAPublicSuffixListInstance(): void { $exception = new class('Something went wrong.', 0) extends RuntimeException implements CacheException { }; - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('set')->will(self::throwException($exception)); $psl = Rules::fromPath(dirname(__DIR__, 2).'/test_data/public_suffix_list.dat'); @@ -95,7 +96,7 @@ public function testItWillThrowIfItCantCacheAPublicSuffixListInstance(): void { $exception = new class('Something went wrong.', 0) extends RuntimeException { }; - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('set')->will(self::throwException($exception)); $psl = Rules::fromPath(dirname(__DIR__, 2).'/test_data/public_suffix_list.dat'); @@ -114,7 +115,7 @@ public function testItCanDeleteTheCachedDatabase(): void { $uri = 'http://www.example.com'; - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('delete')->willReturn(true); $instance = new PublicSuffixListPsr16Cache($cache, 'pdp_', new DateInterval('P1D')); @@ -125,7 +126,41 @@ public function testItWillThrowIfTheTTLIsNotParsable(): void { $this->expectException(InvalidArgumentException::class); - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); new PublicSuffixListPsr16Cache($cache, 'pdp_', 'foobar'); } + + protected function restoreExceptionHandler(): void + { + while (true) { + $previousHandler = set_exception_handler(static fn () => null); + restore_exception_handler(); + if (null === $previousHandler) { + break; + } + + restore_exception_handler(); + } + } + + protected function restoreErrorHandler(): void + { + while (true) { + $previousHandler = set_error_handler(static fn (int $errno, string $errstr, ?string $errfile = null, ?int $errline = null) => null); + restore_error_handler(); + $isPhpUnitErrorHandler = ($previousHandler instanceof ErrorHandler); + if (null === $previousHandler || $isPhpUnitErrorHandler) { + break; + } + restore_error_handler(); + } + } + + protected function tearDown(): void + { + parent::tearDown(); + + $this->restoreErrorHandler(); + $this->restoreExceptionHandler(); + } } diff --git a/src/Storage/TimeToLive.php b/src/Storage/TimeToLive.php index 5182f09..3bbe04a 100644 --- a/src/Storage/TimeToLive.php +++ b/src/Storage/TimeToLive.php @@ -9,6 +9,7 @@ use DateTimeInterface; use InvalidArgumentException; use Stringable; +use Throwable; use function filter_var; use const FILTER_VALIDATE_INT; @@ -19,13 +20,26 @@ final class TimeToLive { public static function fromDurationString(string $duration): DateInterval { - set_error_handler(fn () => true); - $interval = DateInterval::createFromDateString($duration); - restore_error_handler(); - if (!$interval instanceof DateInterval) { - throw new InvalidArgumentException( - 'The ttl value "'.$duration.'" can not be parsable by `DateInterval::createFromDateString`.' - ); + try { + set_error_handler(fn () => true); + $interval = DateInterval::createFromDateString($duration); + restore_error_handler(); + if (!$interval instanceof DateInterval) { + throw new InvalidArgumentException( + 'The ttl value "'.$duration.'" can not be parsable by `DateInterval::createFromDateString`.' + ); + } + + } catch (Throwable $exception) { + if (!$exception instanceof InvalidArgumentException) { + throw new InvalidArgumentException( + 'The ttl value "'.$duration.'" can not be parsable by `DateInterval::createFromDateString`.', + 0, + $exception + ); + } + + throw $exception; } return $interval; diff --git a/src/Storage/TimeToLiveTest.php b/src/Storage/TimeToLiveTest.php index 1604d3d..9ca00b7 100644 --- a/src/Storage/TimeToLiveTest.php +++ b/src/Storage/TimeToLiveTest.php @@ -22,9 +22,7 @@ public function testItDoesNotReturnTheAbsoluteInterval(): void self::assertSame(0, TimeToLive::until($tomorrow)->invert); } - /** - * @dataProvider validDurationString - */ + #[DataProvider('validDurationString')] public function testItCanBeInstantiatedFromDurationInput(string $input, DateInterval $expected): void { $now = new DateTimeImmutable(); diff --git a/src/Storage/TopLevelDomainListPsr16CacheTest.php b/src/Storage/TopLevelDomainListPsr16CacheTest.php index 59a0a76..2eb8cb4 100644 --- a/src/Storage/TopLevelDomainListPsr16CacheTest.php +++ b/src/Storage/TopLevelDomainListPsr16CacheTest.php @@ -9,6 +9,7 @@ use InvalidArgumentException; use Pdp\TopLevelDomains; use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\ErrorHandler; use Psr\SimpleCache\CacheException; use Psr\SimpleCache\CacheInterface; use RuntimeException; @@ -18,7 +19,7 @@ final class TopLevelDomainListPsr16CacheTest extends TestCase { public function testItReturnsNullIfTheCacheDoesNotExists(): void { - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('get')->willReturn(null); $instance = new TopLevelDomainListPsr16Cache($cache, 'pdp_', '1 DAY'); @@ -29,7 +30,7 @@ public function testItReturnsNullIfTheCacheDoesNotExists(): void public function testItReturnsAnInstanceIfTheCorrectCacheExists(): void { $topLevelDomainList = TopLevelDomains::fromPath(dirname(__DIR__, 2).'/test_data/tlds-alpha-by-domain.txt'); - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('get')->willReturn($topLevelDomainList); $instance = new TopLevelDomainListPsr16Cache($cache, 'pdp_', 86400); @@ -39,7 +40,7 @@ public function testItReturnsAnInstanceIfTheCorrectCacheExists(): void public function testItReturnsNullIfTheCacheContentContainsInvalidJsonData(): void { - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('get')->willReturn('foobar'); $instance = new TopLevelDomainListPsr16Cache($cache, 'pdp_', new DateInterval('P1D')); @@ -49,7 +50,7 @@ public function testItReturnsNullIfTheCacheContentContainsInvalidJsonData(): voi public function testItReturnsNullIfTheCacheContentCannotBeConvertedToTheCorrectInstance(): void { - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('get')->willReturn('{"foo":"bar"}'); $instance = new TopLevelDomainListPsr16Cache($cache, 'pdp_', new DateTimeImmutable('+1 DAY')); @@ -59,7 +60,7 @@ public function testItReturnsNullIfTheCacheContentCannotBeConvertedToTheCorrectI public function testItCanStoreAPublicSuffixListInstance(): void { - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('set')->willReturn(true); $rzd = TopLevelDomains::fromPath(dirname(__DIR__, 2).'/test_data/tlds-alpha-by-domain.txt'); @@ -70,7 +71,7 @@ public function testItCanStoreAPublicSuffixListInstance(): void public function testItReturnsFalseIfItCantStoreAPublicSuffixListInstance(): void { - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('set')->willReturn(false); $rzd = TopLevelDomains::fromPath(dirname(__DIR__, 2).'/test_data/tlds-alpha-by-domain.txt'); @@ -83,7 +84,7 @@ public function testItReturnsFalseIfItCantCacheATopLevelDomainListInstance(): vo { $exception = new class('Something went wrong.', 0) extends RuntimeException implements CacheException { }; - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('set')->will(self::throwException($exception)); $rzd = TopLevelDomains::fromPath(dirname(__DIR__, 2).'/test_data/tlds-alpha-by-domain.txt'); @@ -96,7 +97,7 @@ public function testItThrowsIfItCantCacheATopLevelDomainListInstance(): void { $exception = new class('Something went wrong.', 0) extends RuntimeException { }; - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('set')->will(self::throwException($exception)); $rzd = TopLevelDomains::fromPath(dirname(__DIR__, 2).'/test_data/tlds-alpha-by-domain.txt'); @@ -111,7 +112,7 @@ public function testItCanDeleteTheCachedDatabase(): void { $uri = 'http://www.example.com'; - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); $cache->method('delete')->willReturn(true); $instance = new TopLevelDomainListPsr16Cache($cache, 'pdp_', new DateInterval('P1D')); @@ -122,7 +123,43 @@ public function testItWillThrowIfTheTTLIsNotParsable(): void { $this->expectException(InvalidArgumentException::class); - $cache = $this->createStub(CacheInterface::class); + $cache = self::createStub(CacheInterface::class); new TopLevelDomainListPsr16Cache($cache, 'pdp_', 'foobar'); } + + + + protected function restoreExceptionHandler(): void + { + while (true) { + $previousHandler = set_exception_handler(static fn () => null); + restore_exception_handler(); + if (null === $previousHandler) { + break; + } + + restore_exception_handler(); + } + } + + protected function restoreErrorHandler(): void + { + while (true) { + $previousHandler = set_error_handler(static fn (int $errno, string $errstr, ?string $errfile = null, ?int $errline = null) => null); + restore_error_handler(); + $isPhpUnitErrorHandler = ($previousHandler instanceof ErrorHandler); + if (null === $previousHandler || $isPhpUnitErrorHandler) { + break; + } + restore_error_handler(); + } + } + + protected function tearDown(): void + { + parent::tearDown(); + + $this->restoreErrorHandler(); + $this->restoreExceptionHandler(); + } } diff --git a/src/UnableToLoadTopLevelDomainList.php b/src/UnableToLoadTopLevelDomainList.php index 94ed824..d2f4e93 100644 --- a/src/UnableToLoadTopLevelDomainList.php +++ b/src/UnableToLoadTopLevelDomainList.php @@ -9,7 +9,7 @@ final class UnableToLoadTopLevelDomainList extends InvalidArgumentException implements CannotProcessHost { - public static function dueToInvalidTopLevelDomain(string $content, Throwable $exception = null): self + public static function dueToInvalidTopLevelDomain(string $content, ?Throwable $exception = null): self { return new self('Invalid Top Level Domain: '.$content, 0, $exception); }