Skip to content

Commit

Permalink
Merge pull request #1 from zentlix/feature/spiral-validator
Browse files Browse the repository at this point in the history
Adding PhoneNumberChecker
  • Loading branch information
msmakouz committed Feb 1, 2023
2 parents 2d10eb8 + 6d6b807 commit a3f8f52
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 16 deletions.
21 changes: 11 additions & 10 deletions .gitattributes
@@ -1,10 +1,11 @@
/.github export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/phpunit.xml.dist export-ignore
/tests export-ignore
/.editorconfig export-ignore
/.php_cs.dist.php export-ignore
/psalm.xml export-ignore
/psalm.xml.dist export-ignore
/UPGRADING.md export-ignore
/.github export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/phpunit.xml.dist export-ignore
/tests export-ignore
/.editorconfig export-ignore
/.php_cs.dist.php export-ignore
/.php-cs-fixer.dist.php export-ignore
/psalm.xml export-ignore
/psalm.xml.dist export-ignore
/UPGRADING.md export-ignore
7 changes: 6 additions & 1 deletion CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog

## 1.1.0 - 2023-02-01
- **Other Features**
- Added `Spiral\PhoneNumber\Validator\Checker\PhoneNumberChecker` for the
[Spiral Validator](https://github.com/spiral/validator) package.

## 1.0.0 - 2023-01-08

- initial release
- initial release
44 changes: 44 additions & 0 deletions README.md
Expand Up @@ -123,6 +123,8 @@ $utils->format($phoneNumber, PhoneNumberFormat::RFC3966); // tel:+1-650-253-0000

## Validation

### Symfony Validator

The package provides a `Spiral\PhoneNumber\Validator\Constraints\PhoneNumber` constraint that can be used to validate
phone numbers using the `spiral-packages/symfony-validator` component.

Expand Down Expand Up @@ -165,6 +167,48 @@ class User
}
```

### Spiral Validator

The package provides a `Spiral\PhoneNumber\Validator\Checker\PhoneNumberChecker` checker that can be used to validate
phone numbers using the `spiral/validator` component.

To use the `Spiral\PhoneNumber\Validator\Checker\PhoneNumberChecker` checker, you will first need to make sure that
the `spiral/validator` package is installed and enabled in your Spiral Framework application.

Once the `spiral/validator` package is installed and enabled, you can use the `PhoneNumberChecker`
checker in your code like this:

```php
namespace App\Request;

use Spiral\Filters\Attribute\Input\Post;
use Spiral\Filters\Model\Filter;
use Spiral\Filters\Model\FilterDefinitionInterface;
use Spiral\Filters\Model\HasFilterDefinition;
use Spiral\Validator\FilterDefinition;

final class UserRequest extends Filter implements HasFilterDefinition
{
#[Post]
public string $phone;

public function filterDefinition(): FilterDefinitionInterface
{
return new FilterDefinition([
'phone' => ['phone'],
// or with custom error message
'phone' => [
['phone', 'error' => 'Custom error message.']
]
]);
}
}
```

In this example, the `PhoneNumberChecker` checker is applied to the **$phone** property of the **UserRequest** class.
This will cause the Validator to validate the **$phone** property as a phone number when the UserRequest object
is validated. If the value of the $phone property is not a valid phone number, the validation will fail.

## Serialization

The package provides a `Spiral\PhoneNumber\Serializer\Normalizer\PhoneNumberNormalizer` class that can be used
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Expand Up @@ -22,11 +22,12 @@
"roave/security-advisories": "dev-latest",
"phpunit/phpunit": "^9.5.27",
"friendsofphp/php-cs-fixer": "^3.8",
"spiral/testing": "^2.2.0",
"spiral/testing": "^2.2",
"vimeo/psalm": "^4.30",
"spiral/twig-bridge": "^2.0",
"spiral-packages/symfony-serializer": "^1.0",
"spiral-packages/symfony-validator": "^1.2",
"spiral/validator": "^1.2",
"spiral/nyholm-bridge": "^1.3",
"symfony/yaml": "^6.0"
},
Expand Down
24 changes: 22 additions & 2 deletions src/Bootloader/PhoneNumberBootloader.php
Expand Up @@ -10,16 +10,19 @@
use libphonenumber\PhoneNumberToTimeZonesMapper;
use libphonenumber\PhoneNumberUtil;
use libphonenumber\ShortNumberInfo;
use Spiral\Boot\AbstractKernel;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Config\ConfiguratorInterface;
use Spiral\Core\Container;
use Spiral\Core\FactoryInterface as ContainerFactory;
use Spiral\PhoneNumber\Config\PhoneNumberConfig;
use Spiral\PhoneNumber\Serializer\Normalizer\PhoneNumberNormalizer;
use Spiral\PhoneNumber\Twig\Extension\PhoneNumberExtension;
use Spiral\PhoneNumber\Validator\Checker\PhoneNumberChecker;
use Spiral\Serializer\Symfony\NormalizersRegistry;
use Spiral\Serializer\Symfony\NormalizersRegistryInterface;
use Spiral\Twig\Bootloader\TwigBootloader;
use Spiral\Validator\Bootloader\ValidatorBootloader;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

final class PhoneNumberBootloader extends Bootloader
Expand All @@ -42,10 +45,16 @@ public function init(): void
$this->initConfig();
}

public function boot(PhoneNumberConfig $config, Container $container, ContainerFactory $factory): void
{
public function boot(
PhoneNumberConfig $config,
Container $container,
ContainerFactory $factory,
AbstractKernel $kernel
): void {
$this->registerNormalizer($config, $container, $factory);
$this->registerTwigExtension($container);

$kernel->booted(fn () => $this->registerValidator($container));
}

private function initConfig(): void
Expand Down Expand Up @@ -129,4 +138,15 @@ private function registerTwigExtension(Container $container): void
$twig = $container->get(TwigBootloader::class);
$twig->addExtension(PhoneNumberExtension::class);
}

private function registerValidator(Container $container): void
{
if (!class_exists(ValidatorBootloader::class)) {
return;
}

$validator = $container->get(ValidatorBootloader::class);
$validator->addChecker('phone', PhoneNumberChecker::class);
$validator->addAlias('phone', 'phone::isValid');
}
}
36 changes: 36 additions & 0 deletions src/Validator/Checker/PhoneNumberChecker.php
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Spiral\PhoneNumber\Validator\Checker;

use libphonenumber\PhoneNumber;
use libphonenumber\PhoneNumberUtil;
use Spiral\PhoneNumber\Config\PhoneNumberConfig;
use Spiral\Validator\AbstractChecker;

final class PhoneNumberChecker extends AbstractChecker
{
public function __construct(
private readonly PhoneNumberConfig $config
) {
}

public const MESSAGES = [
'isValid' => '[[This value is not a valid phone number.]]',
];

public function isValid(string $number): bool
{
$phoneUtil = PhoneNumberUtil::getInstance();

try {
/** @var PhoneNumber $phoneNumber */
$phoneNumber = $phoneUtil->parse($number, $this->config->getDefaultRegion());

return $phoneUtil->isValidNumber($phoneNumber);
} catch (\Throwable) {
return false;
}
}
}
30 changes: 30 additions & 0 deletions tests/app/Request/OtherPhoneRequest.php
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Spiral\PhoneNumber\Tests\App\Request;

use Spiral\Filters\Attribute\Input\Post;
use Spiral\Filters\Model\Filter;
use Spiral\Filters\Model\FilterDefinitionInterface;
use Spiral\Filters\Model\HasFilterDefinition;
use Spiral\Validator\FilterDefinition;

final class OtherPhoneRequest extends Filter implements HasFilterDefinition
{
#[Post]
public string $phone;

#[Post(key: 'phone_with_custom_error')]
public string $phoneWithCustomError;

public function filterDefinition(): FilterDefinitionInterface
{
return new FilterDefinition([
'phone' => ['phone'],
'phoneWithCustomError' => [
['phone', 'error' => 'Custom error.']
]
]);
}
}
19 changes: 19 additions & 0 deletions tests/src/Functional/Bootloader/PhoneNumberBootloaderTest.php
Expand Up @@ -17,6 +17,7 @@
use Spiral\Serializer\Symfony\NormalizersRegistryInterface;
use Spiral\Twig\Config\TwigConfig;
use Spiral\Twig\Extension\ContainerExtension;
use Spiral\Validator\Config\ValidatorConfig;

final class PhoneNumberBootloaderTest extends TestCase
{
Expand Down Expand Up @@ -54,6 +55,24 @@ public function testNormalizerShouldBeRegistered(): void
$this->assertTrue($registry->has(PhoneNumberNormalizer::class));
}

public function testValidatorCheckerShouldBeRegistered(): void
{
$container = $this->getApp()->getContainer();
/** @var ValidatorConfig $config */
$config = $container->get(ValidatorConfig::class);

$this->assertTrue($config->hasChecker('phone'));
}

public function testValidatorAliasShouldBeRegistered(): void
{
$container = $this->getApp()->getContainer();
/** @var ValidatorConfig $config */
$config = $container->get(ValidatorConfig::class);

$this->assertSame('phone:isValid', $config->resolveAlias('phone'));
}

public function testTwigExtensionShouldBeRegistered(): void
{
$this->assertConfigHasFragments(
Expand Down
6 changes: 4 additions & 2 deletions tests/src/Functional/TestCase.php
Expand Up @@ -11,7 +11,8 @@
use Spiral\Serializer\Symfony\Bootloader\SerializerBootloader;
use Spiral\Twig\Bootloader\TwigBootloader;
use Spiral\Validation\Bootloader\ValidationBootloader;
use Spiral\Validation\Symfony\Bootloader\ValidatorBootloader;
use Spiral\Validation\Symfony\Bootloader\ValidatorBootloader as SymfonyValidator;
use Spiral\Validator\Bootloader\ValidatorBootloader as SpiralValidator;

abstract class TestCase extends \Spiral\Testing\TestCase
{
Expand All @@ -30,7 +31,8 @@ public function defineBootloaders(): array
NyholmBootloader::class,
FiltersBootloader::class,
ValidationBootloader::class,
ValidatorBootloader::class,
SymfonyValidator::class,
SpiralValidator::class,
];
}
}
53 changes: 53 additions & 0 deletions tests/src/Functional/Validator/Checker/PhoneNumberCheckerTest.php
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Spiral\PhoneNumber\Tests\Functional\Validator\Constraints;

use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;
use Spiral\Filters\Exception\ValidationException;
use Spiral\PhoneNumber\Tests\App\Request\OtherPhoneRequest;
use Spiral\PhoneNumber\Tests\Functional\TestCase;

final class PhoneNumberCheckerTest extends TestCase
{
public function testSuccessValidation(): void
{
$this->getContainer()->bind(
ServerRequestInterface::class,
$this->createRequest(['phone' => '+1 650 253 0000', 'phone_with_custom_error' => '+16502530000'])
);

$filter = $this->getContainer()->get(OtherPhoneRequest::class);

$this->assertSame('+1 650 253 0000', $filter->phone);
$this->assertSame('+16502530000', $filter->phoneWithCustomError);
}

public function testFailValidation(): void
{
$this->getContainer()->bind(
ServerRequestInterface::class,
$this->createRequest(['phone' => 'foo', 'phone_with_custom_error' => 'bar'])
);

$exception = null;
try {
$this->getContainer()->get(OtherPhoneRequest::class);
} catch (ValidationException $e) {
$exception = $e;
}

$this->assertInstanceOf(ValidationException::class, $exception);
$this->assertSame('This value is not a valid phone number.', $exception->errors['phone']);
$this->assertSame('Custom error.', $exception->errors['phone_with_custom_error']);
}

private function createRequest(array $data): ServerRequestInterface
{
$factory = $this->getContainer()->get(ServerRequestFactoryInterface::class);

return $factory->createServerRequest('POST', '/foo')->withParsedBody($data);
}
}

0 comments on commit a3f8f52

Please sign in to comment.