Skip to content

Commit

Permalink
Merge pull request #36 from programmatordev/1.x
Browse files Browse the repository at this point in the history
1.x
  • Loading branch information
andrepimpao committed Sep 6, 2023
2 parents 6b8c69e + 5acd74b commit 3ae68bf
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 6 deletions.
5 changes: 5 additions & 0 deletions docs/03-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [Basic Rules](#basic-rules)
- [Comparison Rules](#comparison-rules)
- [Date Rules](#date-rules)
- [Choice Rules](#choice-rules)
- [Other Rules](#other-rules)

Expand All @@ -18,6 +19,10 @@
- [LessThanOrEqual](03x-rules-less-than-or-equal.md)
- [Range](03x-rules-range.md)

## Date Rules

- [Timezone](03x-rules-timezone.md)

## Choice Rules

- [Choice](03x-rules-choice.md)
Expand Down
4 changes: 2 additions & 2 deletions docs/03x-rules-country.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Validates that a value is a valid country code.
```php
Country(
string $code = 'alpha-2',
string $message = 'The "{{ name }}" value is not a valid country code, "{{ value }}" given.'
string $message = 'The "{{ name }}" value is not a valid "{{ code }}" country code, "{{ value }}" given.'
);
```

Expand Down Expand Up @@ -42,7 +42,7 @@ Available options:

### `message`

type: `string` default: `The "{{ name }}" value is not a valid country code, "{{ value }}" given.`
type: `string` default: `The "{{ name }}" value is not a valid "{{ code }}" country code, "{{ value }}" given.`

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

Expand Down
88 changes: 88 additions & 0 deletions docs/03x-rules-timezone.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Timezone

Validates that a value is a valid timezone identifier.

```php
Timezone(
string $timezoneGroup = \DateTimeZone::ALL,
?string $countryCode = null,
string $message = 'The "{{ name }}" value is not a valid timezone, "{{ value }}" given.'
);
```

## Basic Usage

```php
// All timezone identifiers
Validator::timezone()->validate('Europe/Lisbon'); // true

// Restrict timezone identifiers to a specific geographical zone
Validator::timezone(timezoneGroup: \DateTimeZone::EUROPE)->validate('Europe/Lisbon'); // true
Validator::timezone(timezoneGroup: \DateTimeZone::AFRICA)->validate('Europe/Lisbon'); // false
// Or multiple geographical zones
Validator::timezone(timezoneGroup: \DateTimeZone::AFRICA | \DateTimeZone::EUROPE)->validate('Europe/Lisbon'); // true

// Restrict timezone identifiers to a specific country
Validator::timezone(timezoneGroup: \DateTimeZone::PER_COUNTRY, countryCode: 'pt')->validate('Europe/Lisbon'); // true
Validator::timezone(timezoneGroup: \DateTimeZone::PER_COUNTRY, countryCode: 'en')->validate('Europe/Lisbon'); // false
```

> **Note**
> An `UnexpectedValueException` will be thrown when the `timezoneGroup` value is `\DateTimeZone::PER_COUNTRY`
> and the `countryCode` value is `null` (not provided).
> **Note**
> An `UnexpectedValueException` will be thrown when the `countryCode` value is not valid.
> Only if the `timezoneGroup` value is `\DateTimeZone::PER_COUNTRY`, otherwise it is ignored.
## Options

### `timezoneGroup`

type: `int` default: `\DateTimeZone::ALL`

Set this option to restrict timezone identifiers to a specific geographical zone.

Available timezone groups:

- `\DateTimeZone::AFRICA`
- `\DateTimeZone::AMERICA`
- `\DateTimeZone::ANTARCTICA`
- `\DateTimeZone::ARCTIC`
- `\DateTimeZone::ASIA`
- `\DateTimeZone::ATLANTIC`
- `\DateTimeZone::AUSTRALIA`
- `\DateTimeZone::EUROPE`
- `\DateTimeZone::INDIAN`
- `\DateTimeZone::PACIFIC`

In addition, there are special timezone groups:

- `\DateTimeZone::ALL` all timezones;
- `\DateTimeZone::ALL_WITH_BC` all timezones including deprecated timezones;
- `\DateTimeZone::PER_COUNTRY` timezones per country (must be used together with the [`countryCode`](#countrycode) option);
- `\DateTimeZone::UTC` UTC timezones.

### `countryCode`

type: `?string` default: `null`

If the `timezoneGroup` option value is `\DateTimeZone::PER_COUNTRY`,
this option is required to restrict valid timezone identifiers to the ones that belong to the given country.

Must be a valid alpha-2 country code.
Check the [official country codes](https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes) list for more information.

### `message`

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

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

The following parameters are available:

| Parameter | Description |
|---------------------|---------------------------|
| `{{ value }}` | The current invalid value |
| `{{ name }}` | Name of the invalid value |
| `{{ countryCode }}` | Selected country code |
8 changes: 7 additions & 1 deletion src/ChainedValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function choice(

public function country(
string $code = 'alpha-2',
string $message = 'The "{{ name }}" value is not a valid country code, "{{ value }}" given.'
string $message = 'The "{{ name }}" value is not a valid "{{ code }}" country code, "{{ value }}" given.'
): ChainedValidatorInterface;

public function greaterThan(
Expand Down Expand Up @@ -70,6 +70,12 @@ public function range(

public function rule(RuleInterface $constraint): ChainedValidatorInterface;

public function timezone(
int $timezoneGroup = \DateTimeZone::ALL,
?string $countryCode = null,
string $message = 'The "{{ name }}" value is not a valid timezone, "{{ value }}" given.'
): ChainedValidatorInterface;

public function type(
string|array $constraint,
string $message = 'The "{{ name }}" value should be of type "{{ constraint }}", "{{ value }}" given.'
Expand Down
5 changes: 5 additions & 0 deletions src/Exception/TimezoneException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace ProgrammatorDev\YetAnotherPhpValidator\Exception;

class TimezoneException extends ValidationException {}
2 changes: 1 addition & 1 deletion src/Rule/Country.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Country extends AbstractRule implements RuleInterface

public function __construct(
private readonly string $code = self::ALPHA_2_CODE,
private readonly string $message = 'The "{{ name }}" value is not a valid country code, "{{ value }}" given.'
private readonly string $message = 'The "{{ name }}" value is not a valid "{{ code }}" country code, "{{ value }}" given.'
) {}

public function assert(mixed $value, string $name): void
Expand Down
50 changes: 50 additions & 0 deletions src/Rule/Timezone.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace ProgrammatorDev\YetAnotherPhpValidator\Rule;

use ProgrammatorDev\YetAnotherPhpValidator\Exception\TimezoneException;
use ProgrammatorDev\YetAnotherPhpValidator\Exception\UnexpectedValueException;
use ProgrammatorDev\YetAnotherPhpValidator\Exception\ValidationException;
use ProgrammatorDev\YetAnotherPhpValidator\Validator;

class Timezone extends AbstractRule implements RuleInterface
{
public function __construct(
private readonly int $timezoneGroup = \DateTimeZone::ALL,
private ?string $countryCode = null,
private readonly string $message = 'The "{{ name }}" value is not a valid timezone, "{{ value }}" given.'
) {}

public function assert(mixed $value, string $name): void
{
if ($this->timezoneGroup === \DateTimeZone::PER_COUNTRY) {
// Country code is required when using PER_COUNTRY timezone group
if ($this->countryCode === null) {
throw new UnexpectedValueException(
'A country code is required when timezone group is \DateTimeZone::PER_COUNTRY.'
);
}

// Normalize country code
$this->countryCode = strtoupper($this->countryCode);

try {
Validator::country()->assert($this->countryCode, 'countryCode');
}
catch (ValidationException $exception) {
throw new UnexpectedValueException($exception->getMessage());
}
}

if (!\in_array($value, \DateTimeZone::listIdentifiers($this->timezoneGroup, $this->countryCode), true)) {
throw new TimezoneException(
message: $this->message,
parameters: [
'value' => $value,
'name' => $name,
'countryCode' => $this->countryCode
]
);
}
}
}
8 changes: 7 additions & 1 deletion src/StaticValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static function choice(

public static function country(
string $code = 'alpha-2',
string $message = 'The "{{ name }}" value is not a valid country code, "{{ value }}" given.'
string $message = 'The "{{ name }}" value is not a valid "{{ code }}" country code, "{{ value }}" given.'
): ChainedValidatorInterface;

public static function greaterThan(
Expand Down Expand Up @@ -60,6 +60,12 @@ public static function range(

public static function rule(RuleInterface $constraint): ChainedValidatorInterface;

public static function timezone(
int $timezoneGroup = \DateTimeZone::ALL,
?string $countryCode = null,
string $message = 'The "{{ name }}" value is not a valid timezone, "{{ value }}" given.'
): ChainedValidatorInterface;

public static function type(
string|array $constraint,
string $message = 'The "{{ name }}" value should be of type "{{ constraint }}", "{{ value }}" given.'
Expand Down
2 changes: 1 addition & 1 deletion tests/CountryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static function provideRuleUnexpectedValueData(): \Generator
public static function provideRuleFailureConditionData(): \Generator
{
$exception = CountryException::class;
$message = '/The "(.*)" value is not a valid country code, "(.*)" given./';
$message = '/The "(.*)" value is not a valid "(.*)" country code, "(.*)" given./';

yield 'default' => [new Country(), 'PRT', $exception, $message];
yield 'alpha2' => [new Country(code: 'alpha-2'), 'PRT', $exception, $message];
Expand Down
57 changes: 57 additions & 0 deletions tests/TimezoneTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace ProgrammatorDev\YetAnotherPhpValidator\Test;

use ProgrammatorDev\YetAnotherPhpValidator\Exception\TimezoneException;
use ProgrammatorDev\YetAnotherPhpValidator\Rule\Timezone;
use ProgrammatorDev\YetAnotherPhpValidator\Test\Util\TestRuleFailureConditionTrait;
use ProgrammatorDev\YetAnotherPhpValidator\Test\Util\TestRuleMessageOptionTrait;
use ProgrammatorDev\YetAnotherPhpValidator\Test\Util\TestRuleSuccessConditionTrait;
use ProgrammatorDev\YetAnotherPhpValidator\Test\Util\TestRuleUnexpectedValueTrait;

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

public static function provideRuleUnexpectedValueData(): \Generator
{
$missingCountryCodeMessage = '/A country code is required when timezone group is \\\DateTimeZone::PER_COUNTRY./';
$invalidCountryCodeMessage = '/The "(.*)" value is not a valid "(.*)" country code, "(.*)" given./';

yield 'missing country code' => [new Timezone(\DateTimeZone::PER_COUNTRY), 'Europe/Lisbon', $missingCountryCodeMessage];
yield 'invalid country code' => [new Timezone(\DateTimeZone::PER_COUNTRY, 'PRT'), 'Europe/Lisbon', $invalidCountryCodeMessage];
}

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

yield 'invalid timezone' => [new Timezone(), 'Invalid/Timezone', $exception, $message];
yield 'not of timezone group' => [new Timezone(\DateTimeZone::AFRICA), 'Europe/Lisbon', $exception, $message];
yield 'not of timezone country' => [new Timezone(\DateTimeZone::PER_COUNTRY, 'es'), 'Europe/Lisbon', $exception, $message];
}

public static function provideRuleSuccessConditionData(): \Generator
{
yield 'valid timezone' => [new Timezone(), 'Europe/Lisbon'];
yield 'is of timezone group' => [new Timezone(\DateTimeZone::EUROPE), 'Europe/Lisbon'];
yield 'is of multiple timezone groups' => [new Timezone(\DateTimeZone::EUROPE | \DateTimeZone::UTC), 'UTC'];
yield 'is of timezone country' => [new Timezone(\DateTimeZone::PER_COUNTRY, 'pt'), 'Europe/Lisbon'];
}

public static function provideRuleMessageOptionData(): \Generator
{
yield 'message' => [
new Timezone(
message: 'The "{{ name }}" value "{{ value }}" is not a valid timezone.'
),
'Invalid/Timezone',
'The "test" value "Invalid/Timezone" is not a valid timezone.'
];
}

}

0 comments on commit 3ae68bf

Please sign in to comment.