Skip to content

Commit

Permalink
Merge pull request #65 from programmatordev/YAPV-45-create-locale-rule
Browse files Browse the repository at this point in the history
Create Locale rule
  • Loading branch information
andrepimpao authored Apr 30, 2024
2 parents 738bd98 + 30ee115 commit ee89ba2
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 4 deletions.
9 changes: 5 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "programmatordev/yet-another-php-validator",
"description": "PHP validator with expressive error messages",
"type": "library",
"keywords": ["PHP", "PHP8", "Validator", "Validation"],
"keywords": ["php", "php8", "validator", "validation"],
"license": "MIT",
"authors": [
{
Expand All @@ -14,13 +14,14 @@
"require": {
"php": ">=8.1",
"egulias/email-validator": "^4.0",
"symfony/intl": "^6.3",
"symfony/intl": "^6.4",
"symfony/polyfill-ctype": "^1.27",
"symfony/polyfill-intl-grapheme": "^1.29"
"symfony/polyfill-intl-grapheme": "^1.29",
"symfony/polyfill-intl-icu": "^1.29"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"symfony/var-dumper": "^6.3"
"symfony/var-dumper": "^6.4"
},
"autoload": {
"psr-4": {
Expand Down
1 change: 1 addition & 0 deletions docs/03-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
- [Choice](03-rules_choice.md)
- [Country](03-rules_country.md)
- [Language](03-rules_language.md)
- [Locale](03-rules_locale.md)

## Iterable Rules

Expand Down
56 changes: 56 additions & 0 deletions docs/03-rules_locale.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Locale

Validates that a value is a valid locale code.

```php
Locale(
bool $canonicalize = false,
?string $message = null
);
```

## Basic Usage

```php
// by default, code should be the language code (pt, en, ...)
// or the language code followed by an underscore and the uppercased country code (pt_PT, en_US, ...)
Validator::locale()->validate('pt'); // true
Validator::locale()->validate('pt_PT'); // true
Validator::locale()->validate('pt_pt'); // false
Validator::locale()->validate('pt-PT'); // false
Validator::locale()->validate('pt_PRT'); // false

// canonicalize value before validation
Validator::language(canonicalize: true)->validate('pt_pt'); // true
Validator::language(canonicalize: true)->validate('pt-PT'); // true
Validator::language(canonicalize: true)->validate('pt_PRT'); // true
Validator::language(canonicalize: true)->validate('PT-pt.utf8'); // true
```

> [!NOTE]
> An `UnexpectedValueException` will be thrown when the input value is not a `string`.
## Options

### `canonicalize`

type: `bool` default: `false`

If `true`, the input value will be normalized before validation, according to the following [documentation](https://unicode-org.github.io/icu/userguide/locale/#canonicalization).

### `message`

type: `?string` default: `The {{ name }} value is not a valid locale, {{ value }} given.`

Message that will be shown if the input value is not a valid locale code.

The following parameters are available:

| Parameter | Description |
|---------------|---------------------------|
| `{{ value }}` | The current invalid value |
| `{{ name }}` | Name of the invalid value |

## Changelog

- `1.1.0` Created
5 changes: 5 additions & 0 deletions src/ChainedValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ public function lessThanOrEqual(
?string $message = null
): ChainedValidatorInterface&Validator;

public function locale(
bool $canonicalize = false,
?string $message = null
): ChainedValidatorInterface&Validator;

public function notBlank(
?callable $normalizer = null,
?string $message = null
Expand Down
5 changes: 5 additions & 0 deletions src/Exception/LocaleException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace ProgrammatorDev\Validator\Exception;

class LocaleException extends ValidationException {}
44 changes: 44 additions & 0 deletions src/Rule/Locale.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace ProgrammatorDev\Validator\Rule;

use ProgrammatorDev\Validator\Exception\LocaleException;
use ProgrammatorDev\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Intl\Locales;

class Locale extends AbstractRule implements RuleInterface
{
private string $message = 'The {{ name }} value is not a valid locale, {{ value }} given.';

public function __construct(
private readonly bool $canonicalize = false,
?string $message = null
)
{
$this->message = $message ?? $this->message;
}

public function assert(mixed $value, ?string $name = null): void
{
if (!\is_string($value)) {
throw new UnexpectedTypeException('string', get_debug_type($value));
}

// keep original value for parameters
$input = $value;

if ($this->canonicalize) {
$input = \Locale::canonicalize($input);
}

if (!Locales::exists($input)) {
throw new LocaleException(
message: $this->message,
parameters: [
'name' => $name,
'value' => $value
]
);
}
}
}
5 changes: 5 additions & 0 deletions src/StaticValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ public static function lessThanOrEqual(
?string $message = null
): ChainedValidatorInterface&Validator;

public static function locale(
bool $canonicalize = false,
?string $message = null
): ChainedValidatorInterface&Validator;

public static function notBlank(
?callable $normalizer = null,
?string $message = null
Expand Down
56 changes: 56 additions & 0 deletions tests/LocaleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace ProgrammatorDev\Validator\Test;

use ProgrammatorDev\Validator\Exception\LocaleException;
use ProgrammatorDev\Validator\Rule\Locale;
use ProgrammatorDev\Validator\Test\Util\TestRuleFailureConditionTrait;
use ProgrammatorDev\Validator\Test\Util\TestRuleMessageOptionTrait;
use ProgrammatorDev\Validator\Test\Util\TestRuleSuccessConditionTrait;
use ProgrammatorDev\Validator\Test\Util\TestRuleUnexpectedValueTrait;

class LocaleTest extends AbstractTest
{
use TestRuleUnexpectedValueTrait;
use TestRuleFailureConditionTrait;
use TestRuleSuccessConditionTrait;
use TestRuleMessageOptionTrait;

public static function provideRuleUnexpectedValueData(): \Generator
{
$unexpectedTypeMessage = '/Expected value of type "string", (.*) given\./';

yield 'invalid type' => [new Locale(), 123, $unexpectedTypeMessage];
}

public static function provideRuleFailureConditionData(): \Generator
{
$exception = LocaleException::class;
$message = '/The (.*) value is not a valid locale, (.*) given\./';

yield 'invalid' => [new Locale(), 'invalid', $exception, $message];
yield 'uncanonicalized 1' => [new Locale(), 'pt_pt', $exception, $message];
yield 'uncanonicalized 2' => [new Locale(), 'pt-PT', $exception, $message];
yield 'uncanonicalized 3' => [new Locale(), 'PT-pt.utf8', $exception, $message];
}

public static function provideRuleSuccessConditionData(): \Generator
{
yield 'language code' => [new Locale(), 'pt'];
yield 'language and country code' => [new Locale(), 'pt_PT'];
yield 'canonicalized 1' => [new Locale(canonicalize: true), 'pt_pt'];
yield 'canonicalized 2' => [new Locale(canonicalize: true), 'pt-PT'];
yield 'canonicalized 3' => [new Locale(canonicalize: true), 'PT-pt.utf8'];
}

public static function provideRuleMessageOptionData(): \Generator
{
yield 'message' => [
new Locale(
message: '{{ name }} {{ value }}'
),
'invalid',
'test "invalid"'
];
}
}

0 comments on commit ee89ba2

Please sign in to comment.