diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 429673a..36bb68e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: operating-system: [ ubuntu-latest ] - php-versions: [ '8.2', '8.3', '8.4', '8.5' ] + php-versions: [ '8.4', '8.5' ] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: diff --git a/README.md b/README.md index 019ac65..b147ba5 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A strictly typed array reader for PHP. ## Requirements -* PHP 8.2 - 8.5 +* PHP 8.4 - 8.5 ## Installation diff --git a/composer.json b/composer.json index 1b9045f..6e5eab3 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ ], "homepage": "https://github.com/selective-php/array-reader", "require": { - "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + "php": "~8.4.0 || ~8.5.0" }, "require-dev": { "cakephp/chronos": "^2 || ^3", diff --git a/src/ArrayReader.php b/src/ArrayReader.php index aa039c5..93de210 100644 --- a/src/ArrayReader.php +++ b/src/ArrayReader.php @@ -3,6 +3,8 @@ namespace Selective\ArrayReader; use Cake\Chronos\Chronos; +use DateTimeImmutable; +use DateTimeInterface; use InvalidArgumentException; /** @@ -237,6 +239,46 @@ public function findBool(string $key, ?bool $default = null): ?bool return (bool)$value; } + /** + * Get value as DateTimeImmutable. + * + * @param string $key The key + * @param DateTimeImmutable|null $default The default value + * + * @throws InvalidArgumentException + * + * @return DateTimeImmutable The value + */ + public function getDateTime(string $key, ?DateTimeImmutable $default = null): DateTimeImmutable + { + $value = $this->find($key, $default); + + if ($this->isNullOrBlank($value)) { + throw new InvalidArgumentException(sprintf('No value found for key "%s"', $key)); + } + + return $this->toDateTimeImmutable($key, $value); + } + + /** + * Get value as DateTimeImmutable or null. + * + * @param string $key The key + * @param DateTimeImmutable|null $default The default value + * + * @return DateTimeImmutable|null The value + */ + public function findDateTime(string $key, ?DateTimeImmutable $default = null): ?DateTimeImmutable + { + $value = $this->find($key, $default); + + if ($this->isNullOrBlank($value)) { + return null; + } + + return $this->toDateTimeImmutable($key, $value); + } + /** * Get value as Chronos. * @@ -377,4 +419,35 @@ private function isNullOrBlank(mixed $value): bool { return $value === null || $value === ''; } + + /** + * Cast a value to DateTimeImmutable. + * + * @param string $key The key + * @param mixed $value The value + * + * @throws InvalidArgumentException + * + * @return DateTimeImmutable The date + */ + private function toDateTimeImmutable(string $key, mixed $value): DateTimeImmutable + { + if ($value instanceof DateTimeImmutable) { + return $value; + } + + if ($value instanceof DateTimeInterface) { + return DateTimeImmutable::createFromInterface($value); + } + + if (is_int($value) || (is_float($value) && is_finite($value))) { + return DateTimeImmutable::createFromTimestamp($value); + } + + if (!is_string($value)) { + throw new InvalidArgumentException(sprintf('Invalid date value for key "%s"', $key)); + } + + return new DateTimeImmutable($value); + } } diff --git a/tests/ArrayReaderTest.php b/tests/ArrayReaderTest.php index f92df94..32bdc28 100644 --- a/tests/ArrayReaderTest.php +++ b/tests/ArrayReaderTest.php @@ -2,6 +2,9 @@ namespace Selective\ArrayReader\Test; +use Cake\Chronos\Chronos; +use DateTime; +use DateTimeImmutable; use InvalidArgumentException; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; @@ -429,4 +432,124 @@ public static function providerFindArray(): array [[['key' => 'value'], ['key2' => 'value2']], 2, null, null], ]; } + + /** + * Test. + * + * @param mixed $data The data + * @param string $key The lookup key + * @param mixed $default The default value + * @param mixed $expected The expected value + * + * @return void + */ + #[DataProvider('providerGetDateTime')] + public function testGetDateTime($data, string $key, $default, $expected): void + { + $reader = new ArrayReader($data); + + $this->assertEquals($expected, $reader->findDateTime($key, $default)); + $this->assertEquals($expected, $reader->getDateTime($key, $default)); + } + + /** + * Provider. + * + * @return array[] The test data + */ + public static function providerGetDateTime(): array + { + $date = new DateTimeImmutable('2020-01-02T03:04:05+00:00'); + $fraction = new DateTimeImmutable('2020-01-02T03:04:05.500000+00:00'); + $chronos = new Chronos('2020-01-02T03:04:05+00:00'); + + return [ + [['created' => '2020-01-02T03:04:05+00:00'], 'created', null, $date], + [['created' => $date], 'created', null, $date], + [['created' => new DateTime('2020-01-02T03:04:05+00:00')], 'created', null, $date], + [['created' => 1577934245], 'created', null, $date], + [['created' => 1577934245.5], 'created', null, $fraction], + [['user' => ['created' => '2020-01-02T03:04:05+00:00']], 'user.created', null, $date], + [['created' => $chronos], 'created', null, $chronos], + [[], 'missing', $date, $date], + [['created' => null], 'created', $date, $date], + ]; + } + + /** + * Test. + * + * @param mixed $data The data + * @param string $key The lookup key + * @param mixed $default The default value + * + * @return void + */ + #[DataProvider('providerGetDateTimeError')] + public function testGetDateTimeError($data, string $key, $default): void + { + $reader = new ArrayReader($data); + + $this->expectException(InvalidArgumentException::class); + + $reader->getDateTime($key, $default); + } + + /** + * Provider. + * + * @return array[] The test data + */ + public static function providerGetDateTimeError(): array + { + return [ + [['created' => null], 'created', null], + [['created' => ''], 'created', null], + [[], 'missing', null], + [['created' => ['not-a-date']], 'created', null], + [['created' => INF], 'created', null], + [['created' => NAN], 'created', null], + [['user' => ['created' => null]], 'user.created', null], + ]; + } + + /** + * Test. + * + * @param mixed $data The data + * @param string $key The lookup key + * @param mixed $default The default value + * @param mixed $expected The expected value + * + * @return void + */ + #[DataProvider('providerFindDateTime')] + public function testFindDateTime($data, string $key, $default, $expected): void + { + $reader = new ArrayReader($data); + + $this->assertEquals($expected, $reader->findDateTime($key, $default)); + } + + /** + * Provider. + * + * @return array[] The test data + */ + public static function providerFindDateTime(): array + { + $date = new DateTimeImmutable('2020-01-02T03:04:05+00:00'); + + return [ + [['created' => '2020-01-02T03:04:05+00:00'], 'created', null, $date], + [['created' => null], 'created', null, null], + [['created' => ''], 'created', null, null], + [['created' => ''], 'created', $date, null], + [[], 'missing', null, null], + [[], 'missing', $date, $date], + [['created' => null], 'created', $date, $date], + [['user' => ['created' => '2020-01-02T03:04:05+00:00']], 'user.created', null, $date], + [['user' => ['created' => null]], 'user.created', null, null], + ]; + } }