Skip to content

Commit d2929e7

Browse files
committed
Resolve DateTime value using the clock
1 parent c79d4ab commit d2929e7

File tree

5 files changed

+83
-23
lines changed

5 files changed

+83
-23
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@
5555
->tag('controller.argument_value_resolver', ['priority' => 100])
5656

5757
->set('argument_resolver.datetime', DateTimeValueResolver::class)
58+
->args([
59+
service('clock')->nullOnInvalid(),
60+
])
5861
->tag('controller.argument_value_resolver', ['priority' => 100])
5962

6063
->set('argument_resolver.request_attribute', RequestAttributeValueResolver::class)

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.3
5+
---
6+
7+
* Update `DateTimeValueResolver` to accept an instance of `ClockInterface` for the current date
8+
49
6.2
510
---
611

src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver;
1313

14+
use Symfony\Component\Clock\ClockInterface;
1415
use Symfony\Component\HttpFoundation\Request;
1516
use Symfony\Component\HttpKernel\Attribute\MapDateTime;
1617
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
@@ -26,6 +27,11 @@
2627
*/
2728
final class DateTimeValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
2829
{
30+
public function __construct(
31+
private readonly ?ClockInterface $clock = null,
32+
) {
33+
}
34+
2935
/**
3036
* @deprecated since Symfony 6.2, use resolve() instead
3137
*/
@@ -43,16 +49,22 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
4349
}
4450

4551
$value = $request->attributes->get($argument->getName());
52+
$class = \DateTimeInterface::class === $argument->getType() ? \DateTimeImmutable::class : $argument->getType();
4653

47-
if ($value instanceof \DateTimeInterface) {
48-
return [$value];
54+
if (!$value) {
55+
if ($argument->isNullable()) {
56+
return [null];
57+
}
58+
if (!$this->clock) {
59+
return [new $class];
60+
}
61+
$value = $this->clock->withTimeZone(date_default_timezone_get())->now();
4962
}
5063

51-
if ($argument->isNullable() && !$value) {
52-
return [null];
64+
if ($value instanceof \DateTimeInterface) {
65+
return $value instanceof $class ? [$value] : [$class::createFromInterface($value)];
5366
}
5467

55-
$class = \DateTimeInterface::class === $argument->getType() ? \DateTimeImmutable::class : $argument->getType();
5668
$format = null;
5769

5870
if ($attributes = $argument->getAttributes(MapDateTime::class, ArgumentMetadata::IS_INSTANCEOF)) {

src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\HttpKernel\Tests\Controller\ArgumentResolver;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Clock\MockClock;
1516
use Symfony\Component\HttpFoundation\Request;
1617
use Symfony\Component\HttpKernel\Attribute\MapDateTime;
1718
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DateTimeValueResolver;
@@ -34,9 +35,12 @@ protected function tearDown(): void
3435

3536
public function getTimeZones()
3637
{
37-
yield ['UTC'];
38-
yield ['Etc/GMT+9'];
39-
yield ['Etc/GMT-14'];
38+
yield ['UTC', false];
39+
yield ['Etc/GMT+9', false];
40+
yield ['Etc/GMT-14', false];
41+
yield ['UTC', true];
42+
yield ['Etc/GMT+9', true];
43+
yield ['Etc/GMT-14', true];
4044
}
4145

4246
/**
@@ -71,10 +75,10 @@ public function testUnsupportedArgument()
7175
/**
7276
* @dataProvider getTimeZones
7377
*/
74-
public function testFullDate(string $timezone)
78+
public function testFullDate(string $timezone, bool $withClock)
7579
{
7680
date_default_timezone_set($timezone);
77-
$resolver = new DateTimeValueResolver();
81+
$resolver = new DateTimeValueResolver($withClock ? new MockClock() : null);
7882

7983
$argument = new ArgumentMetadata('dummy', \DateTimeImmutable::class, false, false, null);
8084
$request = self::requestWithAttributes(['dummy' => '2012-07-21 00:00:00']);
@@ -90,10 +94,10 @@ public function testFullDate(string $timezone)
9094
/**
9195
* @dataProvider getTimeZones
9296
*/
93-
public function testUnixTimestamp(string $timezone)
97+
public function testUnixTimestamp(string $timezone, bool $withClock)
9498
{
9599
date_default_timezone_set($timezone);
96-
$resolver = new DateTimeValueResolver();
100+
$resolver = new DateTimeValueResolver($withClock ? new MockClock('now', $timezone) : null);
97101

98102
$argument = new ArgumentMetadata('dummy', \DateTimeImmutable::class, false, false, null);
99103
$request = self::requestWithAttributes(['dummy' => '989541720']);
@@ -120,34 +124,69 @@ public function testNullableWithEmptyAttribute()
120124
}
121125

122126
/**
123-
* @dataProvider getTimeZones
127+
* @param class-string<\DateTimeInterface> $class
128+
* @dataProvider getClasses
124129
*/
125-
public function testNow(string $timezone)
130+
public function testNow(string $class)
126131
{
127-
date_default_timezone_set($timezone);
132+
date_default_timezone_set($timezone = 'Etc/GMT+9');
128133
$resolver = new DateTimeValueResolver();
129134

130-
$argument = new ArgumentMetadata('dummy', \DateTime::class, false, false, null, false);
135+
$argument = new ArgumentMetadata('dummy', $class, false, false, null, false);
131136
$request = self::requestWithAttributes(['dummy' => null]);
132137

133138
$results = $resolver->resolve($request, $argument);
134139

135140
$this->assertCount(1, $results);
141+
$this->assertInstanceOf($class, $results[0]);
142+
$this->assertSame($timezone, $results[0]->getTimezone()->getName(), 'Default timezone');
136143
$this->assertEquals('0', $results[0]->diff(new \DateTimeImmutable())->format('%s'));
144+
}
145+
146+
/**
147+
* @param class-string<\DateTimeInterface> $class
148+
* @dataProvider getClasses
149+
*/
150+
public function testNowWithClock(string $class)
151+
{
152+
date_default_timezone_set($timezone = 'Etc/GMT+9');
153+
$clock = new MockClock('2022-02-20 22:20:02');
154+
$resolver = new DateTimeValueResolver($clock);
155+
156+
$argument = new ArgumentMetadata('dummy', $class, false, false, null, false);
157+
$request = self::requestWithAttributes(['dummy' => null]);
158+
159+
$results = $resolver->resolve($request, $argument);
160+
161+
$this->assertCount(1, $results);
162+
$this->assertInstanceOf($class, $results[0]);
137163
$this->assertSame($timezone, $results[0]->getTimezone()->getName(), 'Default timezone');
164+
$this->assertEquals($clock->now(), $results[0]);
165+
}
166+
167+
public static function getClasses()
168+
{
169+
yield [\DateTimeInterface::class];
170+
yield [\DateTime::class];
171+
yield [FooDateTime::class];
138172
}
139173

140-
public function testPreviouslyConvertedAttribute()
174+
/**
175+
* @param class-string<\DateTimeInterface> $class
176+
* @dataProvider getClasses
177+
*/
178+
public function testPreviouslyConvertedAttribute(string $class)
141179
{
142180
$resolver = new DateTimeValueResolver();
143181

144-
$argument = new ArgumentMetadata('dummy', \DateTimeImmutable::class, false, false, null, true);
182+
$argument = new ArgumentMetadata('dummy', $class, false, false, null, true);
145183
$request = self::requestWithAttributes(['dummy' => $datetime = new \DateTimeImmutable()]);
146184

147185
$results = $resolver->resolve($request, $argument);
148186

149187
$this->assertCount(1, $results);
150-
$this->assertSame($datetime, $results[0]);
188+
$this->assertEquals($datetime, $results[0], 'The value is the same, but the class can be modified.');
189+
$this->assertInstanceOf($class, $results[0]);
151190
}
152191

153192
public function testCustomClass()
@@ -168,10 +207,10 @@ public function testCustomClass()
168207
/**
169208
* @dataProvider getTimeZones
170209
*/
171-
public function testDateTimeImmutable(string $timezone)
210+
public function testDateTimeImmutable(string $timezone, bool $withClock)
172211
{
173212
date_default_timezone_set($timezone);
174-
$resolver = new DateTimeValueResolver();
213+
$resolver = new DateTimeValueResolver($withClock ? new MockClock('now', $timezone) : null);
175214

176215
$argument = new ArgumentMetadata('dummy', \DateTimeImmutable::class, false, false, null);
177216
$request = self::requestWithAttributes(['dummy' => '2016-09-08 00:00:00 +05:00']);
@@ -187,10 +226,10 @@ public function testDateTimeImmutable(string $timezone)
187226
/**
188227
* @dataProvider getTimeZones
189228
*/
190-
public function testWithFormat(string $timezone)
229+
public function testWithFormat(string $timezone, bool $withClock)
191230
{
192231
date_default_timezone_set($timezone);
193-
$resolver = new DateTimeValueResolver();
232+
$resolver = new DateTimeValueResolver($withClock ? new MockClock('now', $timezone) : null);
194233

195234
$argument = new ArgumentMetadata('dummy', \DateTimeInterface::class, false, false, null, false, [
196235
MapDateTime::class => new MapDateTime('m-d-y H:i:s'),

src/Symfony/Component/HttpKernel/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
},
2727
"require-dev": {
2828
"symfony/browser-kit": "^5.4|^6.0",
29+
"symfony/clock": "^6.2",
2930
"symfony/config": "^6.1",
3031
"symfony/console": "^5.4|^6.0",
3132
"symfony/css-selector": "^5.4|^6.0",

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy