Skip to content

Commit

Permalink
Add mutable DateTime support
Browse files Browse the repository at this point in the history
  • Loading branch information
vjik committed Apr 2, 2024
1 parent 5463825 commit bb49dfc
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 52 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## 1.2.0 under development

- New #77: Add `ToDateTimeImmutable` parameter attribute (@vjik)
- New #77: Add `ToDateTime` parameter attribute (@vjik)
- Enh #76: Raise the minimum version of PHP to 8.1 (@vjik)

## 1.1.0 February 09, 2024
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"yiisoft/test-support": "^3.0"
},
"suggest": {
"ext-intl": "Allows using `ToDateTimeImmutable` parameter attribute"
"ext-intl": "Allows using `ToDateTime` parameter attribute"
},
"autoload": {
"psr-4": {
Expand Down
6 changes: 3 additions & 3 deletions docs/guide/en/typecasting.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,16 +139,16 @@ $money = $hydrator->create(Money::class, [
]);
```

To cast a value to `DateTimeImmutable` object explicitly, you can use `ToDateTimeImmutable` attribute:
To cast a value to `DateTimeImmutable` or `DateTime` object explicitly, you can use `ToDateTime` attribute:

```php
use DateTimeImmutable;
use Yiisoft\Hydrator\Attribute\Parameter\ToDateTimeImmutable;
use Yiisoft\Hydrator\Attribute\Parameter\ToDateTime;

class Person
{
public function __construct(
#[ToDateTimeImmutable(locale: 'ru')]
#[ToDateTime(locale: 'ru')]
private ?DateTimeImmutable $birthday = null,
) {}
}
Expand Down
6 changes: 3 additions & 3 deletions docs/guide/ru/typecasting.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,16 +139,16 @@ $money = $hydrator->create(Money::class, [
]);
```

Для приведения значения к объекту `DateTimeImmutable` явно, вы можете использовать атрибут `ToDateTimeImmutable`:
Для приведения значения к объекту `DateTimeImmutable` или `DateTime` явно, вы можете использовать атрибут `ToDateTime`:

```php
use DateTimeImmutable;
use Yiisoft\Hydrator\Attribute\Parameter\ToDateTimeImmutable;
use Yiisoft\Hydrator\Attribute\Parameter\ToDateTime;

class Person
{
public function __construct(
#[ToDateTimeImmutable(locale: 'ru')]
#[ToDateTime(locale: 'ru')]
private ?DateTimeImmutable $birthday = null,
) {}
}
Expand Down
8 changes: 8 additions & 0 deletions infection.json.dist
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,13 @@
"stryker": {
"report": "master"
}
},
"mutators": {
"@default": true,
"FalseValue": {
"ignoreSourceCodeByRegex": [
".*\\$hasMutable = false.*"
]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* @psalm-type IntlDateFormatterFormat = IntlDateFormatter::FULL | IntlDateFormatter::LONG | IntlDateFormatter::MEDIUM | IntlDateFormatter::SHORT | IntlDateFormatter::NONE
*/
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER | Attribute::IS_REPEATABLE)]
final class ToDateTimeImmutable implements ParameterAttributeInterface
final class ToDateTime implements ParameterAttributeInterface
{
/**
* @psalm-param IntlDateFormatterFormat|null $dateType
Expand All @@ -31,6 +31,6 @@ public function __construct(

public function getResolver(): string
{
return ToDateTimeImmutableResolver::class;
return ToDateTimeResolver::class;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@

namespace Yiisoft\Hydrator\Attribute\Parameter;

use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use IntlDateFormatter;
use ReflectionNamedType;
use ReflectionUnionType;
use Yiisoft\Hydrator\AttributeHandling\Exception\UnexpectedAttributeException;
use Yiisoft\Hydrator\AttributeHandling\ParameterAttributeResolveContext;
use Yiisoft\Hydrator\Result;

/**
* @psalm-import-type IntlDateFormatterFormat from ToDateTimeImmutable
* @psalm-import-type IntlDateFormatterFormat from ToDateTime
*/
final class ToDateTimeImmutableResolver implements ParameterAttributeResolverInterface
final class ToDateTimeResolver implements ParameterAttributeResolverInterface
{
/**
* @psalm-param IntlDateFormatterFormat $dateType
Expand All @@ -35,22 +38,19 @@ public function getParameterValue(
ParameterAttributeInterface $attribute,
ParameterAttributeResolveContext $context
): Result {
if (!$attribute instanceof ToDateTimeImmutable) {
throw new UnexpectedAttributeException(ToDateTimeImmutable::class, $attribute);
if (!$attribute instanceof ToDateTime) {
throw new UnexpectedAttributeException(ToDateTime::class, $attribute);
}

if (!$context->isResolved()) {
return Result::fail();
}

$resolvedValue = $context->getResolvedValue();

if ($resolvedValue instanceof DateTimeImmutable) {
return Result::success($resolvedValue);
}
$shouldBeMutable = $this->shouldResultBeMutable($context);

if ($resolvedValue instanceof DateTimeInterface) {
return Result::success(DateTimeImmutable::createFromInterface($resolvedValue));
return $this->createSuccessResult($resolvedValue, $shouldBeMutable);
}

$timeZone = $attribute->timeZone ?? $this->timeZone;
Expand All @@ -60,14 +60,14 @@ public function getParameterValue(

if (is_int($resolvedValue)) {
return Result::success(
$this->makeDateTimeFromTimestamp($resolvedValue, $timeZone)
$this->makeDateTimeFromTimestamp($resolvedValue, $timeZone, $shouldBeMutable)
);
}

if (is_string($resolvedValue) && !empty($resolvedValue)) {
$format = $attribute->format ?? $this->format;
if (is_string($format) && str_starts_with($format, 'php:')) {
return $this->parseWithPhpFormat($resolvedValue, substr($format, 4), $timeZone);
return $this->parseWithPhpFormat($resolvedValue, substr($format, 4), $timeZone, $shouldBeMutable);
}
return $this->parseWithIntlFormat(
$resolvedValue,
Expand All @@ -76,6 +76,7 @@ public function getParameterValue(
$attribute->timeType ?? $this->timeType,
$timeZone,
$attribute->locale ?? $this->locale,
$shouldBeMutable,
);
}

Expand All @@ -85,9 +86,16 @@ public function getParameterValue(
/**
* @psalm-param non-empty-string $resolvedValue
*/
private function parseWithPhpFormat(string $resolvedValue, string $format, ?DateTimeZone $timeZone): Result
private function parseWithPhpFormat(
string $resolvedValue,
string $format,
?DateTimeZone $timeZone,
bool $shouldBeMutable,
): Result
{
$date = DateTimeImmutable::createFromFormat($format, $resolvedValue, $timeZone);
$date = $shouldBeMutable
? DateTime::createFromFormat($format, $resolvedValue, $timeZone)
: DateTimeImmutable::createFromFormat($format, $resolvedValue, $timeZone);
if ($date === false) {
return Result::fail();
}
Expand Down Expand Up @@ -117,6 +125,7 @@ private function parseWithIntlFormat(
int $timeType,
?DateTimeZone $timeZone,
?string $locale,
bool $shouldBeMutable,
): Result {
$formatter = $format === null
? new IntlDateFormatter($locale, $dateType, $timeType, $timeZone)
Expand All @@ -130,12 +139,63 @@ private function parseWithIntlFormat(
$formatter->setLenient(false);
$timestamp = $formatter->parse($resolvedValue);
return is_int($timestamp)
? Result::success($this->makeDateTimeFromTimestamp($timestamp, $timeZone))
? Result::success($this->makeDateTimeFromTimestamp($timestamp, $timeZone, $shouldBeMutable))
: Result::fail();
}

private function makeDateTimeFromTimestamp(int $timestamp, ?DateTimeZone $timeZone): DateTimeImmutable
private function makeDateTimeFromTimestamp(
int $timestamp,
?DateTimeZone $timeZone,
bool $shouldBeMutable
): DateTimeInterface {
/**
* @psalm-suppress InvalidNamedArgument Psalm bug: https://github.com/vimeo/psalm/issues/10872
*/
return $shouldBeMutable
? (new DateTime(timezone: $timeZone))->setTimestamp($timestamp)
: (new DateTimeImmutable(timezone: $timeZone))->setTimestamp($timestamp);
}

private function createSuccessResult(DateTimeInterface $date, bool $shouldBeMutable): Result
{
return (new DateTimeImmutable(timezone: $timeZone))->setTimestamp($timestamp);
if ($shouldBeMutable) {
return Result::success(
$date instanceof DateTime ? $date : DateTime::createFromInterface($date)
);
}
return Result::success(
$date instanceof DateTimeImmutable ? $date : DateTimeImmutable::createFromInterface($date)
);
}

private function shouldResultBeMutable(ParameterAttributeResolveContext $context): bool
{
$type = $context->getParameter()->getType();

if ($type instanceof ReflectionNamedType && $type->getName() === DateTime::class) {
return true;
}

if ($type instanceof ReflectionUnionType) {
$hasMutable = false;
/**
* @psalm-suppress RedundantConditionGivenDocblockType Need for PHP 8.1
*/
foreach ($type->getTypes() as $subType) {
if ($subType instanceof ReflectionNamedType) {
switch ($subType->getName()) {
case DateTime::class:
$hasMutable = true;
break;
case DateTimeImmutable::class:
case DateTimeInterface::class:
return false;
}
}
}
return $hasMutable;
}

return false;
}
}
Loading

0 comments on commit bb49dfc

Please sign in to comment.