8000 feature #48098 [HttpKernel]  Resolve DateTime value using the Clock (… · symfony/symfony@dfcb811 · GitHub
[go: up one dir, main page]

Skip to content

Commit dfcb811

Browse files
feature #48098 [HttpKernel]  Resolve DateTime value using the Clock (GromNaN)
This PR was merged into the 6.3 branch. Discussion ---------- [HttpKernel]  Resolve DateTime value using the Clock | Q | A | ------------- | --- | Branch? | 6.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | TODO In order to mock the current time in functional test cases by injecting a `Symfony\Component\Clock\MockClock` as `clock` service. This is necessary when a `DateTimeInterface` argument is not nullable and we expect the current date time by default. Example when 2 routes are configured on the same controller with an optional parameter. ```php class TvGridController { #[Route('/', name: 'prime_now')] #[Route('/{date}', name: 'prime_date'] public function prime(\DateTimeInterface $date) { return new Response('Grid for date: '.$date->format('Y-m-d')); } } ``` Commits ------- 4917528 Resolve DateTime value using the clock
2 parents 85590ec + 4917528 commit dfcb811

File tree

5 files changed

+67
-21
lines changed

5 files changed

+67
-21
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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Deprecate parameters `container.dumper.inline_factories` and `container.dumper.inline_class_loader`, use `.container.dumper.inline_factories` and `.container.dumper.inline_class_loader` instead
88
* `FileProfilerStorage` removes profiles automatically after two days
99
* Add `#[HttpStatus]` for defining status codes for exceptions
10+
* Use an instance of `Psr\Clock\ClockInterface` to generate the current date time in `DateTimeValueResolver`
1011

1112
6.2
1213
---

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 Psr\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
*/
@@ -45,12 +51,18 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
4551
$value = $request->attributes->get($argument->getName());
4652
$class = \DateTimeInterface::class === $argument->getType() ? \DateTimeImmutable::class : $argument->getType();
4753

48-
if ($value instanceof \DateTimeInterface) {
49-
return [$value instanceof $class ? $value : $class::createFromInterface($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->now();
5062
}
5163

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

5668
$format = null;
@@ -71,7 +83,7 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
7183
$value = '@'.$value;
7284
}
7385
try {
74-
$date = new $class($value ?? 'now');
86+
$date = new $class($value);
7587
} catch (\Exception) {
7688
$date = false;
7789
}

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

Lines changed: 45 additions & 16 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 static 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
public static function getClasses()
@@ -78,10 +82,10 @@ public function testUnsupportedArgument()
7882
/**
7983
* @dataProvider getTimeZones
8084
*/
81-
public function testFullDate(string $timezone)
85+
public function testFullDate(string $timezone, bool $withClock)
8286
{
8387
date_default_timezone_set($timezone);
84-
$resolver = new DateTimeValueResolver();
88+
$resolver = new DateTimeValueResolver($withClock ? new MockClock() : null);
8589

8690
$argument = new ArgumentMetadata('dummy', \DateTimeImmutable::class, false, false, null);
8791
$request = self::requestWithAttributes(['dummy' => '2012-07-21 00:00:00']);
@@ -97,10 +101,10 @@ public function testFullDate(string $timezone)
97101
/**
98102
* @dataProvider getTimeZones
99103
*/
100-
public function testUnixTimestamp(string $timezone)
104+
public function testUnixTimestamp(string $timezone, bool $withClock)
101105
{
102106
date_default_timezone_set($timezone);
103-
$resolver = new DateTimeValueResolver();
107+
$resolver = new DateTimeValueResolver($withClock ? new MockClock('now', $timezone) : null);
104108

105109
$argument = new ArgumentMetadata('dummy', \DateTimeImmutable::class, false, false, null);
106110
$request = self::requestWithAttributes(['dummy' => '989541720']);
@@ -127,21 +131,46 @@ public function testNullableWithEmptyAttribute()
127131
}
128132

129133
/**
130-
* @dataProvider getTimeZones
134+
* @param class-string<\DateTimeInterface> $class
135+
*
136+
* @dataProvider getClasses
131137
*/
132-
public function testNow(string $timezone)
138+
public function testNow(string $class)
133139
{
134-
date_default_timezone_set($timezone);
140+
date_default_timezone_set($timezone = 'Etc/GMT+9');
135141
$resolver = new DateTimeValueResolver();
136142

137-
$argument = new ArgumentMetadata('dummy', \DateTime::class, false, false, null, false);
143+
$argument = new ArgumentMetadata('dummy', $class, false, false, null, false);
138144
$request = self::requestWithAttributes(['dummy' => null]);
139145

140146
$results = $resolver->resolve($request, $argument);
141147

142148
$this->assertCount(1, $results);
143-
$this->assertEquals('0', $results[0]->diff(new \DateTimeImmutable())->format('%s'));
149+
$this->assertInstanceOf($class, $results[0]);
144150
$this->assertSame($timezone, $results[0]->getTimezone()->getName(), 'Default timezone');
151+
$this->assertEquals('0', $results[0]->diff(new \DateTimeImmutable())->format('%s'));
152+
}
153+
154+
/**
155+
* @param class-string<\DateTimeInterface> $class
156+
*
157+
* @dataProvider getClasses
158+
*/
159+
public function testNowWithClock(string $class)
160+
{
161+
date_default_timezone_set('Etc/GMT+9');
162+
$clock = new MockClock('2022-02-20 22:20:02');
163+
$resolver = new DateTimeValueResolver($clock);
164+
165+
$argument = new ArgumentMetadata('dummy', $class, false, false, null, false);
166+
$request = self::requestWithAttributes(['dummy' => null]);
167+
168+
$results = $resolver->resolve($request, $argument);
169+
170+
$this->assertCount(1, $results);
171+
$this->assertInstanceOf($class, $results[0]);
172+
$this->assertSame('UTC', $results[0]->getTimezone()->getName(), 'Default timezone');
173+
$this->assertEquals($clock->now(), $results[0]);
145174
}
146175

147176
/**
@@ -181,10 +210,10 @@ public function testCustomClass()
181210
/**
182211
* @dataProvider getTimeZones
183212
*/
184-
public function testDateTimeImmutable(string $timezone)
213+
public function testDateTimeImmutable(string $timezone, bool $withClock)
185214
{
186215
date_default_timezone_set($timezone);
187-
$resolver = new DateTimeValueResolver();
216+
$resolver = new DateTimeValueResolver($withClock ? new MockClock('now', $timezone) : null);
188217

189218
$argument = new ArgumentMetadata('dummy', \DateTimeImmutable::class, false, false, null);
190219
$request = self::requestWithAttributes(['dummy' => '2016-09-08 00:00:00 +05:00']);
@@ -200,10 +229,10 @@ public function testDateTimeImmutable(string $timezone)
200229
/**
201230
* @dataProvider getTimeZones
202231
*/
203-
public function testWithFormat(string $timezone)
232+
public function testWithFormat(string $timezone, bool $withClock)
204233
{
205234
date_default_timezone_set($timezone);
206-
$resolver = new DateTimeValueResolver();
235+
$resolver = new DateTimeValueResolver($withClock ? new MockClock('now', $timezone) : null);
207236

208237
$argument = new ArgumentMetadata('dummy', \DateTimeInterface::class, false, false, null, false, [
209238
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)
0