Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update dependency to Chronos 3.x #21 #22

Merged
merged 5 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"ext-fileinfo": "*",
"ext-json": "*",
"ext-pdo": "*",
"cakephp/chronos": "^2.4",
"cakephp/chronos": "^3.0.3",
"doctrine/dbal": "^3.6",
"doctrine/migrations": "^3.6",
"ecodev/graphql-doctrine": "^9.0",
Expand Down
23 changes: 10 additions & 13 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions src/Api/Scalar/TimeType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace Ecodev\Felix\Api\Scalar;

use Cake\Chronos\ChronosTime;
use GraphQL\Error\Error;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils;
use UnexpectedValueException;

final class TimeType extends ScalarType
{
public ?string $description = 'A time of the day (local time, no timezone).';

/**
* Serializes an internal value to include in a response.
*/
public function serialize(mixed $value): mixed
{
if ($value instanceof ChronosTime) {
return $value->format('H:i:s.u');
}

return $value;
}

/**
* Parses an externally provided value (query variable) to use as an input.
*/
public function parseValue(mixed $value): ?ChronosTime
{
if (!is_string($value)) {
throw new UnexpectedValueException('Cannot represent value as Chronos time: ' . Utils::printSafe($value));
}

if ($value === '') {
return null;
}

$time = new ChronosTime($value);

return $time;
}

/**
* Parses an externally provided literal value to use as an input (e.g. in Query AST).
*/
public function parseLiteral(Node $valueNode, ?array $variables = null): ?ChronosTime
{
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
// error location in query:
if (!($valueNode instanceof StringValueNode)) {
throw new Error('Query error: Can only parse strings got: ' . $valueNode->kind, $valueNode);
}

return $this->parseValue($valueNode->value);
}
}
27 changes: 26 additions & 1 deletion src/DBAL/Types/ChronosType.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,44 @@
use Cake\Chronos\Chronos;
use DateTimeInterface;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\DateTimeType;

final class ChronosType extends DateTimeType
{
/**
* @param null|DateTimeInterface|int|string $value
* @return ($value is null ? null : string)
*/
public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
{
if ($value === null) {
return $value;
}

if ($value instanceof DateTimeInterface) {
return $value->format($platform->getDateTimeFormatString());
}

throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'Chronos']);
}

/**
* @return ($value is null ? null : Chronos)
*/
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?Chronos
{
if ($value === null || $value instanceof Chronos) {
return $value;
}

if (!is_string($value) && !$value instanceof DateTimeInterface) {
throw ConversionException::conversionFailedFormat(
$value,
$this->getName(),
$platform->getDateTimeFormatString(),
);
}

$val = new Chronos($value);

return $val;
Expand Down
28 changes: 26 additions & 2 deletions src/DBAL/Types/DateType.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,44 @@
namespace Ecodev\Felix\DBAL\Types;

use Cake\Chronos\ChronosDate;
use DateTimeInterface;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;

final class DateType extends \Doctrine\DBAL\Types\DateType
{
/**
* @param null|DateTimeInterface|int|string $value
* @return ($value is null ? null : string)
*/
public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): ?string
{
if ($value === null) {
return $value;
}

if ($value instanceof ChronosDate) {
return $value->format($platform->getDateFormatString());
}

throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'ChronosDate']);
}

/**
* @return ($value is null ? null : ChronosDate)
*/
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?ChronosDate
{
if ($value === null || $value instanceof ChronosDate) {
return $value;
}

if (!is_string($value)) {
throw ConversionException::conversionFailedFormat(
$value,
$this->getName(),
$platform->getDateFormatString(),
);
}

$val = new ChronosDate($value);

return $val;
Expand Down
26 changes: 26 additions & 0 deletions src/DBAL/Types/TimeType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Ecodev\Felix\DBAL\Types;

use Cake\Chronos\ChronosTime;
use DateTimeInterface;
use Doctrine\DBAL\Platforms\AbstractPlatform;

final class TimeType extends \Doctrine\DBAL\Types\TimeType
{
/**
* @param null|ChronosTime|DateTimeInterface|string $value
*/
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?ChronosTime
{
if ($value === null || $value instanceof ChronosTime) {
return $value;
}

$val = new ChronosTime($value);

return $val;
}
}
57 changes: 57 additions & 0 deletions tests/Api/Scalar/TimeTypeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace EcodevTests\Felix\Api\Scalar;

use Cake\Chronos\ChronosTime;
use Ecodev\Felix\Api\Scalar\TimeType;
use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\StringValueNode;
use PHPUnit\Framework\TestCase;

final class TimeTypeTest extends TestCase
{
public function testSerialize(): void
{
$type = new TimeType();
$time = new ChronosTime('14:30:25');
$actual = $type->serialize($time);
self::assertSame('14:30:25.000000', $actual);

// Test serialize with microseconds
$time = new ChronosTime('23:59:59.1254');
$actual = $type->serialize($time);
self::assertSame('23:59:59.001254', $actual);
}

/**
* @dataProvider providerValues
*/
public function testParseLiteral(string $input, ?string $expected): void
{
$type = new TimeType();
$ast = new StringValueNode(['value' => $input]);

$actual = $type->parseLiteral($ast);
self::assertInstanceOf(ChronosTime::class, $actual);
self::assertSame($expected, $actual->format('H:i:s.u'));
}

public function testParseLiteralAsInt(): void
{
$type = new TimeType();
$ast = new IntValueNode(['value' => '123']);

$this->expectExceptionMessage('Query error: Can only parse strings got: IntValue');
$type->parseLiteral($ast);
}

public static function providerValues(): array
{
return [
'normal timr' => ['14:30:25', '14:30:25.000000'],
'time with milliseconds' => ['23:45:13.300', '23:45:13.000300'],
];
}
}
71 changes: 71 additions & 0 deletions tests/DBAL/Types/ChronosTypeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace EcodevTests\Felix\DBAL\Types;

use Cake\Chronos\Chronos;
use DateTimeImmutable;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Ecodev\Felix\DBAL\Types\ChronosType;
use PHPUnit\Framework\TestCase;

class ChronosTypeTest extends TestCase
{
private ChronosType $type;

private AbstractPlatform $platform;

protected function setUp(): void
{
$this->type = new ChronosType();
$this->platform = new MySQLPlatform();
}

public function testConvertToDatabaseValue(): void
{
self::assertSame('DATETIME', $this->type->getSqlDeclaration(['foo'], $this->platform));
self::assertFalse($this->type->requiresSQLCommentHint($this->platform));

$actual = $this->type->convertToDatabaseValue(new Chronos('2016-01-01 15:58:59'), $this->platform);
self::assertSame('2016-01-01 15:58:59', $actual, 'support Chronos');

$actual = $this->type->convertToDatabaseValue(new DateTimeImmutable('2016-01-01 15:58:59'), $this->platform);
self::assertSame('2016-01-01 15:58:59', $actual, 'support DateTimeImmutable');

self::assertNull($this->type->convertToDatabaseValue(null, $this->platform), 'support null values');
}

public function testConvertToPHPValue(): void
{
$actualPhp = $this->type->convertToPHPValue('2016-01-01 15:58:59', $this->platform);
self::assertInstanceOf(Chronos::class, $actualPhp);
self::assertSame('2016-01-01 15:58:59', $actualPhp->__toString(), 'support string');

$actualPhp = $this->type->convertToPHPValue(new Chronos('2016-01-01 15:58:59'), $this->platform);
self::assertInstanceOf(Chronos::class, $actualPhp);
self::assertSame('2016-01-01 15:58:59', $actualPhp->__toString(), 'support Chronos');

$actualPhp = $this->type->convertToPHPValue(new DateTimeImmutable('2016-01-01 15:58:59'), $this->platform);
self::assertInstanceOf(Chronos::class, $actualPhp);
self::assertSame('2016-01-01 15:58:59', $actualPhp->__toString(), 'support DateTimeImmutable');

self::assertNull($this->type->convertToPHPValue(null, $this->platform), 'support null values');
}

public function testConvertToPHPValueThrowsWithInvalidValue(): void
{
$this->expectException(ConversionException::class);

$this->type->convertToPHPValue(123, $this->platform);
}

public function testConvertToDatabaseValueThrowsWithInvalidValue(): void
{
$this->expectException(ConversionException::class);

$this->type->convertToDatabaseValue(123, $this->platform);
}
}
Loading