-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Doctrine colum types for mapping
brick/date-time
instances
- Loading branch information
1 parent
e00c522
commit fc1604f
Showing
7 changed files
with
684 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
webservice/src/Infrastructure/Doctrine/DBAL/Type/InstantType.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Infrastructure\Doctrine\DBAL\Type; | ||
|
||
use Brick\DateTime\Instant; | ||
use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
use Doctrine\DBAL\Types\ConversionException; | ||
use Doctrine\DBAL\Types\Type; | ||
use function preg_match; | ||
use function str_pad; | ||
|
||
final class InstantType extends Type | ||
{ | ||
public const NAME = 'instant'; | ||
|
||
/** | ||
* @template T | ||
* | ||
* @param T $value | ||
* | ||
* @return (T is null ? null : string) | ||
*/ | ||
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string | ||
{ | ||
if (null === $value) { | ||
return null; | ||
} | ||
|
||
if (!$value instanceof Instant) { | ||
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', Instant::class]); | ||
} | ||
|
||
if ($value->getEpochSecond() >= 10_000_000_000 || $value->getEpochSecond() <= -10_000_000_000) { | ||
throw ConversionException::conversionFailedFormat($value->toDecimal(), self::NAME, '-10M to 10M seconds around Unix epoch.'); | ||
} | ||
|
||
return $value->toDecimal(); | ||
} | ||
|
||
/** | ||
* @template T | ||
* | ||
* @param T $value | ||
* | ||
* @return (T is null ? null : Instant) | ||
*/ | ||
public function convertToPHPValue($value, AbstractPlatform $platform): ?Instant | ||
{ | ||
if (null === $value) { | ||
return null; | ||
} | ||
|
||
$matches = []; | ||
if (1 === preg_match('@\A(\d{1,10})(?:\.(\d{0,9}))?\z@', (string) $value, $matches)) { | ||
return Instant::of( | ||
(int) $matches[1], | ||
isset($matches[2]) ? (int) str_pad($matches[2], 9, '0') : 0 | ||
); | ||
} | ||
|
||
throw ConversionException::conversionFailedFormat($value, $this->getName(), '/\d{1,10}(\.\d{0,9})?/'); | ||
} | ||
|
||
public function getName(): string | ||
{ | ||
return self::NAME; | ||
} | ||
|
||
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string | ||
{ | ||
return $platform->getDecimalTypeDeclarationSQL([ | ||
'precision' => 19, | ||
'scale' => 9, | ||
]); | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
webservice/src/Infrastructure/Doctrine/DBAL/Type/LocalDateTimeType.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Infrastructure\Doctrine\DBAL\Type; | ||
|
||
use Brick\DateTime\LocalDateTime; | ||
use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
use Doctrine\DBAL\Types\ConversionException; | ||
use Doctrine\DBAL\Types\Type; | ||
use Throwable; | ||
|
||
final class LocalDateTimeType extends Type | ||
{ | ||
public const NAME = 'local_datetime'; | ||
|
||
/** | ||
* @template T | ||
* | ||
* @param T $value | ||
* | ||
* @return (T is null ? null : string) | ||
*/ | ||
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string | ||
{ | ||
if (null === $value) { | ||
return null; | ||
} | ||
|
||
if ($value instanceof LocalDateTime) { | ||
return $value->toISOString(); | ||
} | ||
|
||
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', LocalDateTime::class]); | ||
} | ||
|
||
/** | ||
* @template T | ||
* | ||
* @param T $value | ||
* | ||
* @return (T is null ? null : LocalDateTime) | ||
*/ | ||
public function convertToPHPValue($value, AbstractPlatform $platform): ?LocalDateTime | ||
{ | ||
if (null === $value) { | ||
return null; | ||
} | ||
|
||
try { | ||
return LocalDateTime::parse((string) $value); | ||
} catch (Throwable $ex) { | ||
throw ConversionException::conversionFailedFormat($value, $this->getName(), 'ISO 8601', $ex); | ||
} | ||
} | ||
|
||
public function getName(): string | ||
{ | ||
return self::NAME; | ||
} | ||
|
||
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string | ||
{ | ||
return $platform->getStringTypeDeclarationSQL([ | ||
'length' => 1 + (6 + 1 + 2 + 1 + 2) + 1 + 8 + 1 + 9, | ||
]); | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
webservice/src/Infrastructure/Doctrine/DBAL/Type/LocalTimeType.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Infrastructure\Doctrine\DBAL\Type; | ||
|
||
use Brick\DateTime\LocalTime; | ||
use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
use Doctrine\DBAL\Types\ConversionException; | ||
use Doctrine\DBAL\Types\Type; | ||
use Throwable; | ||
|
||
final class LocalTimeType extends Type | ||
{ | ||
public const NAME = 'local_time'; | ||
|
||
/** | ||
* @template T | ||
* | ||
* @param T $value | ||
* | ||
* @return (T is null ? null : string) | ||
*/ | ||
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string | ||
{ | ||
if (null === $value) { | ||
return null; | ||
} | ||
|
||
if ($value instanceof LocalTime) { | ||
return $value->toISOString(); | ||
} | ||
|
||
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', LocalTime::class]); | ||
} | ||
|
||
/** | ||
* @template T | ||
* | ||
* @param T $value | ||
* | ||
* @return (T is null ? null : LocalTime) | ||
*/ | ||
public function convertToPHPValue($value, AbstractPlatform $platform): ?LocalTime | ||
{ | ||
if (null === $value) { | ||
return null; | ||
} | ||
|
||
try { | ||
return LocalTime::parse((string) $value); | ||
} catch (Throwable $ex) { | ||
throw ConversionException::conversionFailedFormat($value, $this->getName(), 'ISO 8601', $ex); | ||
} | ||
} | ||
|
||
public function getName(): string | ||
{ | ||
return self::NAME; | ||
} | ||
|
||
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string | ||
{ | ||
return $platform->getStringTypeDeclarationSQL([ | ||
'length' => 8 + 1 + 9, | ||
]); | ||
} | ||
} |
160 changes: 160 additions & 0 deletions
160
webservice/tests/Infrastructure/Doctrine/DBAL/Type/InstantTypeTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Tests\Infrastructure\Doctrine\DBAL\Type; | ||
|
||
use App\Infrastructure\Doctrine\DBAL\Type\InstantType; | ||
use Brick\DateTime\Instant; | ||
use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
use Doctrine\DBAL\Platforms\MySQLPlatform; | ||
use Doctrine\DBAL\Types\ConversionException; | ||
use PHPUnit\Framework\Attributes\CoversClass; | ||
use PHPUnit\Framework\Attributes\DataProvider; | ||
use PHPUnit\Framework\Attributes\Small; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
#[CoversClass(InstantType::class)] | ||
#[Small()] | ||
final class InstantTypeTest extends TestCase | ||
{ | ||
/** | ||
* @return array{0: ?string, 1: ?Instant}[] | ||
*/ | ||
public static function convertToDatabaseValueProvider(): array | ||
{ | ||
return [ | ||
[null, null], | ||
['0', Instant::epoch()], | ||
['1234567890.123456789', Instant::of(1234567890, 123456789)], | ||
['-123456789.123456789', Instant::of(-123456789, 123456789)], | ||
['9999999999.999999999', Instant::of(9_999_999_999, 999_999_999)], | ||
['-9999999999.999999999', Instant::of(-9_999_999_999, 999_999_999)], | ||
]; | ||
} | ||
|
||
#[DataProvider('convertToDatabaseValueProvider')] | ||
public function testConvertToDatabaseValue(?string $expected, ?Instant $value): void | ||
{ | ||
$platform = $this->createMock(AbstractPlatform::class); | ||
$type = new InstantType(); | ||
|
||
$result = $type->convertToDatabaseValue($value, $platform); | ||
|
||
self::assertSame($expected, $result); | ||
} | ||
|
||
/** | ||
* @return array{0: mixed}[] | ||
*/ | ||
public static function convertToDatabaseValueExceptionProvider(): array | ||
{ | ||
return [ | ||
[true], | ||
[Instant::min()], | ||
[Instant::of(-10_000_000_000, 0)], | ||
[Instant::of(10_000_000_000, 0)], | ||
[Instant::max()], | ||
]; | ||
} | ||
|
||
#[DataProvider('convertToDatabaseValueExceptionProvider')] | ||
public function testConvertToDatabaseValueException(mixed $value): void | ||
{ | ||
$platform = $this->createMock(AbstractPlatform::class); | ||
$type = new InstantType(); | ||
|
||
self::expectException(ConversionException::class); | ||
|
||
$type->convertToDatabaseValue($value, $platform); | ||
} | ||
|
||
/** | ||
* @return array{0: ?Instant, 1: mixed}[] | ||
*/ | ||
public static function convertToPhpValueProvider(): array | ||
{ | ||
return [ | ||
[null, null], | ||
[Instant::epoch(), 0], | ||
[Instant::epoch(), 0.0], | ||
[Instant::epoch(), '0'], | ||
[Instant::epoch(), '0.'], | ||
[Instant::epoch(), '0.0'], | ||
[Instant::of(42, 125000000), 42.125], | ||
[Instant::of(4711, 250000000), 4711.25], | ||
[Instant::of(0, 123456789), '0.123456789'], | ||
[Instant::of(123456789, 0), '123456789.0'], | ||
[Instant::of(123456, 789000000), '123456.789'], | ||
]; | ||
} | ||
|
||
#[DataProvider('convertToPhpValueProvider')] | ||
public function testConvertToPhpValue(?Instant $expected, mixed $value): void | ||
{ | ||
$platform = $this->createMock(AbstractPlatform::class); | ||
$type = new InstantType(); | ||
|
||
$result = $type->convertToPHPValue($value, $platform); | ||
|
||
if (null === $expected) { | ||
self::assertNull($result); | ||
} else { | ||
self::assertNotNull($result); | ||
self::assertTrue($expected->isEqualTo($result)); | ||
} | ||
} | ||
|
||
/** | ||
* @return array{0: mixed}[] | ||
*/ | ||
public static function convertToPhpValueExceptionProvider(): array | ||
{ | ||
return [ | ||
[''], | ||
['.0'], | ||
['abcdef'], | ||
]; | ||
} | ||
|
||
#[DataProvider('convertToPhpValueExceptionProvider')] | ||
public function testConvertToPhpValueException(mixed $value): void | ||
{ | ||
$platform = $this->createMock(AbstractPlatform::class); | ||
$type = new InstantType(); | ||
|
||
self::expectException(ConversionException::class); | ||
|
||
$type->convertToPHPValue($value, $platform); | ||
} | ||
|
||
public function testGetName(): void | ||
{ | ||
$type = new InstantType(); | ||
|
||
self::assertSame(InstantType::NAME, $type->getName()); | ||
} | ||
|
||
/** | ||
* @return array{0: string, 1: array<string, mixed>, 2: AbstractPlatform}[] | ||
*/ | ||
public static function getSqlDeclarationProvider(): array | ||
{ | ||
$mysql = new MySQLPlatform(); | ||
|
||
return [ | ||
['NUMERIC(19, 9)', [], $mysql], | ||
]; | ||
} | ||
|
||
/** | ||
* @param array<string, mixed> $column | ||
*/ | ||
#[DataProvider('getSqlDeclarationProvider')] | ||
public function testGetSqlDeclaration(string $expected, array $column, AbstractPlatform $platform): void | ||
{ | ||
$type = new InstantType(); | ||
|
||
self::assertSame($expected, $type->getSQLDeclaration($column, $platform)); | ||
} | ||
} |
Oops, something went wrong.