This commit is contained in:
Marek Rzytki 2026-06-25 16:43:06 +02:00 committed by GitHub
commit 503aeb590e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 93 additions and 31 deletions

63
UPGRADE-API-2.3.md Normal file
View file

@ -0,0 +1,63 @@
# UPGRADE FROM `2.2` TO `2.3`
## DateTime Serialization Format
The `sylius_api.normalizer.date_time` service has been removed. Previously, Sylius registered a custom
`DateTimeNormalizer` that overrode Symfony's built-in normalizer globally, forcing the `Y-m-d H:i:s` format
on all `DateTime` fields across the entire API.
All `DateTime` fields are now serialized using Symfony's default `DateTimeNormalizer`, which produces
the RFC 3339 format (`Y-m-d\TH:i:sP`).
**Before:**
```json
{
"startDate": "2022-01-01 00:00:00",
"endDate": "2022-01-02 00:00:00",
"birthday": "2023-10-24 11:00:00",
"expiresAt": "2020-01-01 12:00:00"
}
```
**After:**
```json
{
"startDate": "2022-01-01T00:00:00+00:00",
"endDate": "2022-01-02T00:00:00+00:00",
"birthday": "2023-10-24T11:00:00+00:00",
"expiresAt": "2020-01-01T12:00:00+00:00"
}
```
**Affected fields (non-exhaustive):** `startDate`, `endDate`, `startsAt`, `endsAt`, `expiresAt`, `birthday`,
`createdAt`, `updatedAt`, `archivedAt` and any other `DateTime` field exposed via the API.
**Migration guide:**
If your API client depends on the old `Y-m-d H:i:s` format, update it to parse RFC 3339 dates instead.
RFC 3339 is the standard format for REST APIs and is natively supported by all modern date libraries.
If you need to restore the old format or apply a custom one globally, register your own `DateTimeNormalizer`
with a higher priority than Symfony's default (`-910`):
```php
// config/services.php
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
$services
->set('app.normalizer.date_time', DateTimeNormalizer::class)
->args([['datetime_format' => 'Y-m-d H:i:s']])
->tag('serializer.normalizer', ['priority' => 1])
;
```
Alternatively, you may set the format for a specific field only by adding a serialization context
to the attribute definition in your serialization XML:
```xml
<attribute name="startDate">
<context>
<entry name="datetime_format">Y-m-d</entry>
</context>
</attribute>
```

View file

@ -860,7 +860,11 @@ final readonly class ManagingCatalogPromotionsContext implements Context
Assert::true(
$this->responseChecker->hasItemWithValues(
$response,
['name' => $catalogPromotion->getName(), 'startDate' => $startDate . ':00', 'endDate' => $endDate . ':00'],
[
'name' => $catalogPromotion->getName(),
'startDate' => (new \DateTime($startDate))->format(\DateTimeInterface::RFC3339),
'endDate' => (new \DateTime($endDate))->format(\DateTimeInterface::RFC3339),
],
),
sprintf(
'Cannot find catalog promotions with name "%s" operating between "%s" and "%s" in the list',
@ -902,15 +906,15 @@ final readonly class ManagingCatalogPromotionsContext implements Context
$response,
[
'name' => $catalogPromotion->getName(),
'startDate' => (new \DateTime('yesterday'))->format('Y-m-d H:i:s'),
'endDate' => (new \DateTime('tomorrow'))->format('Y-m-d H:i:s'),
'startDate' => (new \DateTime('yesterday'))->format(\DateTimeInterface::RFC3339),
'endDate' => (new \DateTime('tomorrow'))->format(\DateTimeInterface::RFC3339),
],
),
sprintf(
'Cannot find catalog promotions with name "%s" operating between "%s" and "%s" in the list',
$catalogPromotion->getName(),
(new \DateTime('yesterday'))->format('Y-m-d H:i:s'),
(new \DateTime('tomorrow'))->format('Y-m-d H:i:s'),
(new \DateTime('yesterday'))->format(\DateTimeInterface::RFC3339),
(new \DateTime('tomorrow'))->format(\DateTimeInterface::RFC3339),
),
);
}

View file

@ -351,7 +351,10 @@ final class ManagingCustomersContext implements Context
#[Then('he should be registered since :registrationDate')]
public function hisRegistrationDateShouldBe(string $registrationDate): void
{
Assert::true($this->responseChecker->hasValue($this->client->getLastResponse(), 'createdAt', $registrationDate));
Assert::same(
(new \DateTime($this->responseChecker->getValue($this->client->getLastResponse(), 'createdAt')))->format(\DateTimeInterface::RFC3339),
(new \DateTime($registrationDate))->format(\DateTimeInterface::RFC3339),
);
}
#[Then('their email should be :email')]

View file

@ -755,7 +755,7 @@ final readonly class ManagingOrdersContext implements Context
Assert::same(
$this->responseChecker->getValue($response, 'shippedAt'),
(new \DateTime($dateTime))->format('Y-m-d H:i:s'),
(new \DateTime($dateTime))->format(\DateTimeInterface::RFC3339),
);
}

View file

@ -269,8 +269,7 @@ final readonly class ManagingPromotionCouponsContext implements Context
#[Then('this coupon should be valid until :date')]
public function thisCouponShouldBeValidUntil(\DateTime $date): void
{
$actualDate = \DateTime::createFromFormat(
'Y-m-d h:i:s',
$actualDate = new \DateTime(
$this->responseChecker->getValue($this->client->getLastResponse(), 'expiresAt'),
);

View file

@ -588,8 +588,8 @@ final readonly class ManagingPromotionsContext implements Context
$this->client->index(Resources::PROMOTIONS),
[
'name' => $promotion->getName(),
'startsAt' => $startsDate->format('Y-m-d H:i:s'),
'endsAt' => $endsDate->format('Y-m-d H:i:s'),
'startsAt' => $startsDate->format(\DateTimeInterface::RFC3339),
'endsAt' => $endsDate->format(\DateTimeInterface::RFC3339),
],
),
);

View file

@ -36,7 +36,6 @@ use Sylius\Bundle\ApiBundle\Serializer\Normalizer\ProductOptionValueNormalizer;
use Sylius\Bundle\ApiBundle\Serializer\Normalizer\ProductVariantNormalizer;
use Sylius\Bundle\ApiBundle\Serializer\Normalizer\ShippingMethodNormalizer;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
return static function (ContainerConfigurator $container) {
$services = $container->services();
@ -173,12 +172,6 @@ return static function (ContainerConfigurator $container) {
->tag('serializer.normalizer', ['priority' => 64])
;
$services
->set('sylius_api.normalizer.date_time', DateTimeNormalizer::class)
->args([['datetime_format' => 'Y-m-d H:i:s']])
->tag('serializer.normalizer')
;
$services
->set('sylius_api.denormalizer.channel_price_history_config', ChannelPriceHistoryConfigDenormalizer::class)
->args([

View file

@ -5,8 +5,8 @@
"id": "@integer@",
"name": "T-Shirts discount",
"code": "tshirts_discount",
"startDate": "2022-01-01 00:00:00",
"endDate": "2022-01-02 00:00:00",
"startDate": "@date@",
"endDate": "@date@",
"priority": 100,
"state": "@string@",
"channels": [

View file

@ -8,7 +8,7 @@
"email": "api@example.com",
"firstName": "John",
"lastName": "Doe",
"birthday": "2023-10-24 11:00:00",
"birthday": "@date@",
"gender": "m",
"group": null,
"phoneNumber": "+48123456789",

View file

@ -13,7 +13,7 @@
"email": "api@example.com",
"firstName": "John",
"lastName": "Doe",
"birthday": "2023-10-24 11:00:00",
"birthday": "@date@",
"gender": "m",
"group": "\/api\/v2\/admin\/customer-groups\/vip",
"phoneNumber": "+48123456789",

View file

@ -13,7 +13,7 @@
"email": "api@example.com",
"firstName": "John",
"lastName": "Lim",
"birthday": "2023-09-24 11:00:00",
"birthday": "@date@",
"gender": "f",
"group": "\/api\/v2\/admin\/customer-groups\/premium",
"phoneNumber": "+48987654321",

View file

@ -14,8 +14,8 @@
"usageLimit": 3,
"used": 0,
"archivedAt": null,
"startsAt": "2023-10-04 12:30:00",
"endsAt": "2023-11-04 12:30:00",
"startsAt": "@date@",
"endsAt": "@date@",
"couponBased": true,
"coupons": [],
"rules": [

View file

@ -9,7 +9,7 @@
"usageLimit": 10,
"used": 0,
"promotion": "\/api\/v2\/admin\/promotions\/50_off",
"expiresAt": "2020-01-01 12:00:00",
"expiresAt": "@date@",
"createdAt": "@date@",
"updatedAt": "@date@"
},
@ -23,7 +23,7 @@
"usageLimit": 10,
"used": 0,
"promotion": "\/api\/v2\/admin\/promotions\/50_off",
"expiresAt": "2020-01-01 12:00:00",
"expiresAt": "@date@",
"createdAt": "@date@",
"updatedAt": "@date@"
},
@ -37,7 +37,7 @@
"usageLimit": 10,
"used": 0,
"promotion": "\/api\/v2\/admin\/promotions\/50_off",
"expiresAt": "2020-01-01 12:00:00",
"expiresAt": "@date@",
"createdAt": "@date@",
"updatedAt": "@date@"
},
@ -51,7 +51,7 @@
"usageLimit": 10,
"used": 0,
"promotion": "\/api\/v2\/admin\/promotions\/50_off",
"expiresAt": "2020-01-01 12:00:00",
"expiresAt": "@date@",
"createdAt": "@date@",
"updatedAt": "@date@"
}

View file

@ -10,5 +10,5 @@
"promotion": "\/api\/v2\/admin\/promotions\/dollar_off",
"createdAt": "@date@",
"updatedAt": "@date@",
"expiresAt": "2020-01-01 12:00:00"
"expiresAt": "@date@"
}

View file

@ -14,6 +14,6 @@
"fullName": "John Wick",
"phoneNumber": "666777888",
"gender": "m",
"birthday": "2023-10-24 11:00:00",
"birthday": "@date@",
"subscribedToNewsletter": true
}