Skip to content

[Serializer] Respect default context in DateTimeNormalizer::denormalize #43329

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

Merged
merged 1 commit into from
Jul 28, 2022

Conversation

hultberg
Copy link
Contributor

@hultberg hultberg commented Oct 5, 2021

Q A
Branch? 4.4
Bug fix? yes
New feature? no
Deprecations? no
Tickets Fix #29030
License MIT
Doc PR

Hey there. 👋

I'm bringing my first code patch to symfony today and it's for fixing a bug I encountered today while working with DateTimeNormalizer. In my project I have set a default date format in the service container and I noticed that the denormalized date did not follow my set date format (I enforce time but the serialized date did not contain the time stamp which should have failed when using DateTime::createFromDate). After some debugging I discovered the issue described in issue #29030

DateTimeNormalizer::normalize respects the date time format in context or default context while DateTimeNormalizer::denormalize only respects the former. This patch adds logic to denormalize too attempt create a DateTime object from the default context format (by default set to \DateTime::RFC3339) with createFromFormat and then if that fails continue on with the current behaviour. In my opinion this patch does not break the symfony BC promise as it simply adds another way to denormalize the date time.

One issue: There are no error handling when creating the date time from default context as the logic just continues on which might give the impression to the developer that the denormalization with the specific format worked correctly. I was not able to find a way to handle any error and keeping BC.
Future tough: At some point the new DateTime call in denormalize should be deprecated since creating a date time with createFromFormat is considered "safer" in my eyes as it's very explicit while the call to new DateTime is implicit about the date format and must perform parsing and guessing of the intended format (explained over at https://www.php.net/manual/en/datetime.formats.php). Another thing about new DateTime is the terriable error handling as you must catch a \Exception which means you catch "what ever" exception that can exist, even \ErrorException since \ErrorException extends \Exception.

Fixes #29030
Alternative PR (closed) #33813

@carsonbot

This comment has been minimized.

@hultberg hultberg marked this pull request as ready for review October 5, 2021 18:05
@hultberg hultberg requested a review from dunglas as a code owner October 5, 2021 18:05
@carsonbot carsonbot added this to the 5.4 milestone Oct 5, 2021
@hultberg hultberg changed the base branch from 5.4 to 4.4 October 5, 2021 18:15
@hultberg
Copy link
Contributor Author

hultberg commented Oct 5, 2021

I targeted the wrong branch so the milestone is now wrong.

@derrabus derrabus modified the milestones: 5.4, 4.4 Oct 5, 2021
@ro0NL
Copy link
Contributor

ro0NL commented Oct 5, 2021

There are no error handling when creating the date time from default context as the logic just continues on which might give the impression to the developer that the denormalization with the specific format worked correctly. I was not able to find a way to handle any error and keeping BC.

We could preserve $defaultDateTimeErrors, and hint about it when the constructor fallback fails too.

At some point the new DateTime call in denormalize should be deprecated since creating a date time with createFromFormat is considered "safer" in my eyes as it's very explicit while the call to new DateTime is implicit about the date format

We could argue the formatted value in a raw payload is already validated, before being passed to a deserializer :)

That said, i think the datetime-constructor-as-fallback is a generally fine approach, for what is an implementation detail. If you get a DateTime, your value is denormalized.

edit: it also enables robustness-principle by default :)

@hultberg
Copy link
Contributor Author

hultberg commented Oct 5, 2021

@ro0NL

We could argue the formatted value in a raw payload is already validated, before being passed to a deserializer :)

Can you elaborate on that the raw payload is already validated? Are there a "normal apporch" to use a validator with the raw payload? In my real world example I deserialize some JSON payload into a object then I validate it with symfony validator. I have had to override DateTimeNormalizer to catch any errors which are later cough by a custom date validator I made.

We could preserve $defaultDateTimeErrors, and hint about it when the constructor fallback fails too.

That seems like a good solution to at the least provide some hints about the default context format failing.

@ro0NL
Copy link
Contributor

ro0NL commented Oct 5, 2021

Are there a "normal apporch" to use a validator with the raw payload?

Im still on a mission to shift the paradigm :) #43047 (comment)

"validation before hydration" benefits object design IMHO.

@@ -113,6 +113,16 @@ public function denormalize($data, $type, $format = null, array $context = [])
throw new NotNormalizableValueException(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count'])."\n".implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])));
}

$defaultDateTimeFormat = $this->defaultContext[self::FORMAT_KEY] ?? null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why don't you patch L97 to this instead?
$dateTimeFormat = $context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY] ?? null;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we allow createFromFormat from defaultContext[FORMAT_KEY] to fail due BC, but generally we can't assume the default format fits all payloads

Copy link
Contributor

@ogizanagi ogizanagi Feb 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we trigger a deprecation in case the fallback path is triggered, so we can remove it in Symfony 7+ to enforce the defaultContext[FORMAT_KEY] is honored?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Symfony 7 is released. Is it planned to remove it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet as far as I can see. The first step to remove something in the next major release would be to deprecate it in a minor release before.

Copy link
Contributor

@ogizanagi ogizanagi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With additional deprecation as mentioned in https://github.com/symfony/symfony/pull/43329/files#r812202044

@fabpot
Copy link
Member

fabpot commented Jul 23, 2022

@hultberg Are you still interested in finishing this PR? It looks like we are almost there.

@ogizanagi
Copy link
Contributor

Well, this targets 4.4 as a bug fix, so we cannot add the deprecation I suggested. So this should be ready to merge as is.

@ogizanagi
Copy link
Contributor

I added the deprecation and opened the discussion in #47095

@nicolas-grekas
Copy link
Member

Thank you @hultberg.

@nicolas-grekas nicolas-grekas merged commit 57e5141 into symfony:4.4 Jul 28, 2022
This was referenced Jul 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

DateTimeNormalizer ignores format when denormalize
10 participants