mirror of
https://github.com/Respect/Validation.git
synced 2026-03-14 22:35:45 +01:00
Containerize sokil databases
The main focus of this change is to make those optional dependencies more testable. Unfortunately, some phpstan-ignores had to be included, since ::set is not a PsrContainer method. We're only using it on tests though, so it's fine. It targets our php-di container for testing purposes only. The real implementation only relies on ::get. This change also has the side effect of improving the performance of those validators by not instantiating their databases each time a iso validator is built, achieving massive improvements in those scenarios. A small benchmark with no assertions was added to track that improvement.
This commit is contained in:
parent
7f4a4c2035
commit
d8e31dbc3a
11 changed files with 219 additions and 38 deletions
|
|
@ -18,6 +18,8 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Respect\Validation\ContainerRegistry;
|
||||
use Respect\Validation\Exceptions\InvalidValidatorException;
|
||||
use Respect\Validation\Exceptions\MissingComposerDependencyException;
|
||||
use Respect\Validation\Message\Template;
|
||||
|
|
@ -25,7 +27,6 @@ use Respect\Validation\Result;
|
|||
use Respect\Validation\Validator;
|
||||
use Sokil\IsoCodes\Database\Countries;
|
||||
|
||||
use function class_exists;
|
||||
use function in_array;
|
||||
use function is_string;
|
||||
|
||||
|
|
@ -43,14 +44,6 @@ final readonly class CountryCode implements Validator
|
|||
private string $set = 'alpha-2',
|
||||
Countries|null $countries = null,
|
||||
) {
|
||||
if (!class_exists(Countries::class)) {
|
||||
throw new MissingComposerDependencyException(
|
||||
'SubdivisionCode rule requires PHP ISO Codes',
|
||||
'sokil/php-isocodes',
|
||||
'sokil/php-isocodes-db-only',
|
||||
);
|
||||
}
|
||||
|
||||
$availableOptions = ['alpha-2', 'alpha-3', 'numeric'];
|
||||
if (!in_array($set, $availableOptions, true)) {
|
||||
throw new InvalidValidatorException(
|
||||
|
|
@ -60,7 +53,15 @@ final readonly class CountryCode implements Validator
|
|||
);
|
||||
}
|
||||
|
||||
$this->countries = $countries ?? new Countries();
|
||||
try {
|
||||
$this->countries = $countries ?? ContainerRegistry::getContainer()->get(Countries::class);
|
||||
} catch (NotFoundExceptionInterface) {
|
||||
throw new MissingComposerDependencyException(
|
||||
'CountryCode rule requires PHP ISO Codes',
|
||||
'sokil/php-isocodes',
|
||||
'sokil/php-isocodes-db-only',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function evaluate(mixed $input): Result
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Respect\Validation\ContainerRegistry;
|
||||
use Respect\Validation\Exceptions\InvalidValidatorException;
|
||||
use Respect\Validation\Exceptions\MissingComposerDependencyException;
|
||||
use Respect\Validation\Message\Template;
|
||||
|
|
@ -22,7 +24,6 @@ use Respect\Validation\Result;
|
|||
use Respect\Validation\Validator;
|
||||
use Sokil\IsoCodes\Database\Currencies;
|
||||
|
||||
use function class_exists;
|
||||
use function in_array;
|
||||
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
|
|
@ -39,14 +40,6 @@ final readonly class CurrencyCode implements Validator
|
|||
private string $set = 'alpha-3',
|
||||
Currencies|null $currencies = null,
|
||||
) {
|
||||
if (!class_exists(Currencies::class)) {
|
||||
throw new MissingComposerDependencyException(
|
||||
'CurrencyCode rule requires PHP ISO Codes',
|
||||
'sokil/php-isocodes',
|
||||
'sokil/php-isocodes-db-only',
|
||||
);
|
||||
}
|
||||
|
||||
$availableSets = ['alpha-3', 'numeric'];
|
||||
if (!in_array($set, $availableSets, true)) {
|
||||
throw new InvalidValidatorException(
|
||||
|
|
@ -56,7 +49,15 @@ final readonly class CurrencyCode implements Validator
|
|||
);
|
||||
}
|
||||
|
||||
$this->currencies = $currencies ?? new Currencies();
|
||||
try {
|
||||
$this->currencies = $currencies ?? ContainerRegistry::getContainer()->get(Currencies::class);
|
||||
} catch (NotFoundExceptionInterface) {
|
||||
throw new MissingComposerDependencyException(
|
||||
'CurrencyCode rule requires PHP ISO Codes',
|
||||
'sokil/php-isocodes',
|
||||
'sokil/php-isocodes-db-only',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function evaluate(mixed $input): Result
|
||||
|
|
|
|||
|
|
@ -15,15 +15,15 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Respect\Validation\ContainerRegistry;
|
||||
use Respect\Validation\Exceptions\InvalidValidatorException;
|
||||
use Respect\Validation\Exceptions\MissingComposerDependencyException;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
use Sokil\IsoCodes\Database\Countries;
|
||||
use Sokil\IsoCodes\Database\Languages;
|
||||
|
||||
use function class_exists;
|
||||
use function in_array;
|
||||
use function is_string;
|
||||
|
||||
|
|
@ -41,14 +41,6 @@ final readonly class LanguageCode implements Validator
|
|||
private readonly string $set = 'alpha-2',
|
||||
Languages|null $languages = null,
|
||||
) {
|
||||
if (!class_exists(Countries::class)) {
|
||||
throw new MissingComposerDependencyException(
|
||||
'LanguageCode rule requires PHP ISO Codes',
|
||||
'sokil/php-isocodes',
|
||||
'sokil/php-isocodes-db-only',
|
||||
);
|
||||
}
|
||||
|
||||
$availableSets = ['alpha-2', 'alpha-3'];
|
||||
if (!in_array($set, $availableSets, true)) {
|
||||
throw new InvalidValidatorException(
|
||||
|
|
@ -58,7 +50,15 @@ final readonly class LanguageCode implements Validator
|
|||
);
|
||||
}
|
||||
|
||||
$this->languages = $languages ?? new Languages();
|
||||
try {
|
||||
$this->languages = $languages ?? ContainerRegistry::getContainer()->get(Languages::class);
|
||||
} catch (NotFoundExceptionInterface) {
|
||||
throw new MissingComposerDependencyException(
|
||||
'LanguageCode rule requires PHP ISO Codes',
|
||||
'sokil/php-isocodes',
|
||||
'sokil/php-isocodes-db-only',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function evaluate(mixed $input): Result
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ namespace Respect\Validation\Validators;
|
|||
use Attribute;
|
||||
use libphonenumber\NumberParseException;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Respect\Validation\ContainerRegistry;
|
||||
use Respect\Validation\Exceptions\InvalidValidatorException;
|
||||
use Respect\Validation\Exceptions\MissingComposerDependencyException;
|
||||
use Respect\Validation\Message\Template;
|
||||
|
|
@ -64,7 +66,9 @@ final class Phone implements Validator
|
|||
return;
|
||||
}
|
||||
|
||||
if (!class_exists(Countries::class)) {
|
||||
try {
|
||||
$countries ??= ContainerRegistry::getContainer()->get(Countries::class);
|
||||
} catch (NotFoundExceptionInterface) {
|
||||
throw new MissingComposerDependencyException(
|
||||
'Phone rule with country code requires PHP ISO Codes',
|
||||
'sokil/php-isocodes',
|
||||
|
|
@ -72,7 +76,6 @@ final class Phone implements Validator
|
|||
);
|
||||
}
|
||||
|
||||
$countries ??= new Countries();
|
||||
$this->country = $countries->getByAlpha2($countryCode);
|
||||
if ($this->country === null) {
|
||||
throw new InvalidValidatorException('Invalid country code %s', $countryCode);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Respect\Validation\ContainerRegistry;
|
||||
use Respect\Validation\Exceptions\InvalidValidatorException;
|
||||
use Respect\Validation\Exceptions\MissingComposerDependencyException;
|
||||
use Respect\Validation\Helpers\CanValidateUndefined;
|
||||
|
|
@ -21,8 +23,6 @@ use Respect\Validation\Validator;
|
|||
use Sokil\IsoCodes\Database\Countries;
|
||||
use Sokil\IsoCodes\Database\Subdivisions;
|
||||
|
||||
use function class_exists;
|
||||
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be a subdivision code of {{countryName|trans}}',
|
||||
|
|
@ -41,7 +41,11 @@ final readonly class SubdivisionCode implements Validator
|
|||
Countries|null $countries = null,
|
||||
Subdivisions|null $subdivisions = null,
|
||||
) {
|
||||
if (!class_exists(Countries::class) || !class_exists(Subdivisions::class)) {
|
||||
try {
|
||||
$container = ContainerRegistry::getContainer();
|
||||
$countries ??= $container->get(Countries::class);
|
||||
$this->subdivisions = $subdivisions ?? $container->get(Subdivisions::class);
|
||||
} catch (NotFoundExceptionInterface) {
|
||||
throw new MissingComposerDependencyException(
|
||||
'SubdivisionCode rule requires PHP ISO Codes',
|
||||
'sokil/php-isocodes',
|
||||
|
|
@ -49,14 +53,12 @@ final readonly class SubdivisionCode implements Validator
|
|||
);
|
||||
}
|
||||
|
||||
$countries ??= new Countries();
|
||||
$country = $countries->getByAlpha2($countryCode);
|
||||
if ($country === null) {
|
||||
throw new InvalidValidatorException('"%s" is not a supported country code', $countryCode);
|
||||
}
|
||||
|
||||
$this->country = $country;
|
||||
$this->subdivisions = $subdivisions ?? new Subdivisions();
|
||||
}
|
||||
|
||||
public function evaluate(mixed $input): Result
|
||||
|
|
|
|||
69
tests/benchmark/IsoCodesBench.php
Normal file
69
tests/benchmark/IsoCodesBench.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* SPDX-FileCopyrightText: (c) Respect Project Contributors
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation\Benchmarks;
|
||||
|
||||
use PhpBench\Attributes as Bench;
|
||||
use Respect\Validation\Test\SmokeTestProvider;
|
||||
use Respect\Validation\ValidatorBuilder;
|
||||
|
||||
class IsoCodesBench
|
||||
{
|
||||
use SmokeTestProvider;
|
||||
|
||||
#[Bench\Iterations(10)]
|
||||
#[Bench\RetryThreshold(5)]
|
||||
#[Bench\Revs(5)]
|
||||
#[Bench\Warmup(1)]
|
||||
#[Bench\Subject]
|
||||
public function subdivisionCode(): void
|
||||
{
|
||||
ValidatorBuilder::subdivisionCode('US')->evaluate('CA');
|
||||
}
|
||||
|
||||
#[Bench\Iterations(10)]
|
||||
#[Bench\RetryThreshold(5)]
|
||||
#[Bench\Revs(5)]
|
||||
#[Bench\Warmup(1)]
|
||||
#[Bench\Subject]
|
||||
public function countryCode(): void
|
||||
{
|
||||
ValidatorBuilder::countryCode()->evaluate('US');
|
||||
}
|
||||
|
||||
#[Bench\Iterations(10)]
|
||||
#[Bench\RetryThreshold(5)]
|
||||
#[Bench\Revs(5)]
|
||||
#[Bench\Warmup(1)]
|
||||
#[Bench\Subject]
|
||||
public function currencyCode(): void
|
||||
{
|
||||
ValidatorBuilder::currencyCode()->evaluate('USD');
|
||||
}
|
||||
|
||||
#[Bench\Iterations(10)]
|
||||
#[Bench\RetryThreshold(5)]
|
||||
#[Bench\Revs(5)]
|
||||
#[Bench\Warmup(1)]
|
||||
#[Bench\Subject]
|
||||
public function languageCode(): void
|
||||
{
|
||||
ValidatorBuilder::languageCode()->evaluate('en');
|
||||
}
|
||||
|
||||
#[Bench\Iterations(10)]
|
||||
#[Bench\RetryThreshold(5)]
|
||||
#[Bench\Revs(5)]
|
||||
#[Bench\Warmup(1)]
|
||||
#[Bench\Subject]
|
||||
public function phone(): void
|
||||
{
|
||||
ValidatorBuilder::phone('US')->evaluate('+1 202-555-0125');
|
||||
}
|
||||
}
|
||||
|
|
@ -14,10 +14,13 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Validators;
|
||||
|
||||
use DI;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\ContainerRegistry;
|
||||
use Respect\Validation\Exceptions\InvalidValidatorException;
|
||||
use Respect\Validation\Exceptions\MissingComposerDependencyException;
|
||||
use Respect\Validation\Test\RuleTestCase;
|
||||
|
||||
#[Group('validator')]
|
||||
|
|
@ -36,6 +39,24 @@ final class CountryCodeTest extends RuleTestCase
|
|||
new CountryCode('whatever');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldThrowWhenMissingComponent(): void
|
||||
{
|
||||
$mainContainer = ContainerRegistry::getContainer();
|
||||
ContainerRegistry::setContainer((new DI\ContainerBuilder())->useAutowiring(false)->build());
|
||||
try {
|
||||
new CountryCode('alpha-3');
|
||||
$this->fail('Expected MissingComposerDependencyException was not thrown.');
|
||||
} catch (MissingComposerDependencyException $e) {
|
||||
$this->assertStringContainsString(
|
||||
'CountryCode rule requires PHP ISO Codes',
|
||||
$e->getMessage(),
|
||||
);
|
||||
} finally {
|
||||
ContainerRegistry::setContainer($mainContainer);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return iterable<array{CountryCode, mixed}> */
|
||||
public static function providerForValidInput(): iterable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,10 +14,13 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Validators;
|
||||
|
||||
use DI;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\ContainerRegistry;
|
||||
use Respect\Validation\Exceptions\InvalidValidatorException;
|
||||
use Respect\Validation\Exceptions\MissingComposerDependencyException;
|
||||
use Respect\Validation\Test\RuleTestCase;
|
||||
|
||||
#[Group('validator')]
|
||||
|
|
@ -36,6 +39,24 @@ final class CurrencyCodeTest extends RuleTestCase
|
|||
new CurrencyCode('whatever');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldThrowWhenMissingComponent(): void
|
||||
{
|
||||
$mainContainer = ContainerRegistry::getContainer();
|
||||
ContainerRegistry::setContainer((new DI\ContainerBuilder())->useAutowiring(false)->build());
|
||||
try {
|
||||
new CurrencyCode('alpha-3');
|
||||
$this->fail('Expected MissingComposerDependencyException was not thrown.');
|
||||
} catch (MissingComposerDependencyException $e) {
|
||||
$this->assertStringContainsString(
|
||||
'CurrencyCode rule requires PHP ISO Codes',
|
||||
$e->getMessage(),
|
||||
);
|
||||
} finally {
|
||||
ContainerRegistry::setContainer($mainContainer);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return iterable<array{CurrencyCode, mixed}> */
|
||||
public static function providerForValidInput(): iterable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,10 +13,13 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Validators;
|
||||
|
||||
use DI;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\ContainerRegistry;
|
||||
use Respect\Validation\Exceptions\InvalidValidatorException;
|
||||
use Respect\Validation\Exceptions\MissingComposerDependencyException;
|
||||
use Respect\Validation\Test\RuleTestCase;
|
||||
|
||||
#[Group('validator')]
|
||||
|
|
@ -35,6 +38,24 @@ final class LanguageCodeTest extends RuleTestCase
|
|||
new LanguageCode('whatever');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldThrowWhenMissingComponent(): void
|
||||
{
|
||||
$mainContainer = ContainerRegistry::getContainer();
|
||||
ContainerRegistry::setContainer((new DI\ContainerBuilder())->useAutowiring(false)->build());
|
||||
try {
|
||||
new LanguageCode('alpha-3');
|
||||
$this->fail('Expected MissingComposerDependencyException was not thrown.');
|
||||
} catch (MissingComposerDependencyException $e) {
|
||||
$this->assertStringContainsString(
|
||||
'LanguageCode rule requires PHP ISO Codes',
|
||||
$e->getMessage(),
|
||||
);
|
||||
} finally {
|
||||
ContainerRegistry::setContainer($mainContainer);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return iterable<array{LanguageCode, mixed}> */
|
||||
public static function providerForValidInput(): iterable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,11 +17,14 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Validators;
|
||||
|
||||
use DI;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\ContainerRegistry;
|
||||
use Respect\Validation\Exceptions\InvalidValidatorException;
|
||||
use Respect\Validation\Exceptions\MissingComposerDependencyException;
|
||||
use Respect\Validation\Test\TestCase;
|
||||
use stdClass;
|
||||
|
||||
|
|
@ -66,6 +69,24 @@ final class PhoneTest extends TestCase
|
|||
new Phone('BRR');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldThrowWhenMissingComponent(): void
|
||||
{
|
||||
$mainContainer = ContainerRegistry::getContainer();
|
||||
ContainerRegistry::setContainer((new DI\ContainerBuilder())->useAutowiring(false)->build());
|
||||
try {
|
||||
new Phone('US');
|
||||
$this->fail('Expected MissingComposerDependencyException was not thrown.');
|
||||
} catch (MissingComposerDependencyException $e) {
|
||||
$this->assertStringContainsString(
|
||||
'Phone rule with country code requires PHP ISO Codes',
|
||||
$e->getMessage(),
|
||||
);
|
||||
} finally {
|
||||
ContainerRegistry::setContainer($mainContainer);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array<array{mixed}> */
|
||||
public static function providerForValidInputWithoutCountryCode(): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,10 +12,13 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Validators;
|
||||
|
||||
use DI;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\ContainerRegistry;
|
||||
use Respect\Validation\Exceptions\InvalidValidatorException;
|
||||
use Respect\Validation\Exceptions\MissingComposerDependencyException;
|
||||
use Respect\Validation\Test\RuleTestCase;
|
||||
|
||||
#[Group('validator')]
|
||||
|
|
@ -31,6 +34,24 @@ final class SubdivisionCodeTest extends RuleTestCase
|
|||
new SubdivisionCode('whatever');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldThrowWhenMissingComponent(): void
|
||||
{
|
||||
$mainContainer = ContainerRegistry::getContainer();
|
||||
ContainerRegistry::setContainer((new DI\ContainerBuilder())->useAutowiring(false)->build());
|
||||
try {
|
||||
new SubdivisionCode('US');
|
||||
$this->fail('Expected MissingComposerDependencyException was not thrown.');
|
||||
} catch (MissingComposerDependencyException $e) {
|
||||
$this->assertStringContainsString(
|
||||
'SubdivisionCode rule requires PHP ISO Codes',
|
||||
$e->getMessage(),
|
||||
);
|
||||
} finally {
|
||||
ContainerRegistry::setContainer($mainContainer);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return iterable<array{SubdivisionCode, mixed}> */
|
||||
public static function providerForValidInput(): iterable
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue