Repository navigation
Model: Handle dates in UTC and keep column fields out of message meta - #627
Open
huzaifaalmesbah wants to merge 2 commits into
Open
huzaifaalmesbah wants to merge 2 commits into
huzaifaalmesbah wants to merge 2 commits into
Conversation
Serde read MySQL datetime strings in the PHP default timezone and accepted out of range dates, which PHP silently rolls over. It now reads and writes dates in UTC and rejects invalid dates. `Message::$meta_keys` listed `expires_at`, `severity` and `channel_title`, so `encode_meta()` wrote them into the `meta` JSON. They are removed from `$meta_keys` and kept in `jsonSerialize()`. `Factory\Message` now deserializes `created_at` and `expires_at` with Serde, like the Notification and Subscription factories, so a message loaded from the database no longer throws a `TypeError`. Also removes the unused `Message::validate_message()`. Fixes WordPress#614.
2 of 22 tasks
| 'ISO 8601 format' => array( '2024-01-02T03:04:05+00:00' ), | ||
| 'date only' => array( '2024-01-02' ), | ||
| 'out of range day' => array( '2024-02-30 00:00:00' ), | ||
| 'out of range month' => array( '2024-13-01 00:00:00' ), |
Collaborator
There was a problem hiding this comment.
Optional: The PR description says the MySQL zero date (0000-00-00 00:00:00) now returns null. I tested this locally and it works. What do you think about adding it to this list too? Old MySQL setups sometimes store this value, so a test would protect it in the future.
'mysql zero date' => array( '0000-00-00 00:00:00' ),
Member
Author
There was a problem hiding this comment.
Thanks for testing it! Good idea, I added the MySQL zero date case to the data provider in 5ae27f6.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
Closes #614. Part of #302.
Makes
Helper\Serderead and write dates in UTC, removes column fields fromMessage::$meta_keys, and makesFactory\Messagedeserialize its dates the same way the other factories do.Why?
maybe_deserialize_mysql_date()calledDateTime::createFromFormat()without a timezone. WordPress sets the default to UTC, but anything that changes it shifts every date read from the database. It also accepted dates like2024-02-30, which PHP silently rolls over to March.meta.Message::$meta_keyslistedexpires_at,severityandchannel_title, soencode_meta()wrote them into themetaJSON (expires_atas{"date":…,"timezone_type":3,"timezone":"UTC"}).jsonSerialize()overwrote them afterwards, which hid the problem.Factory\Messagecould not load a message from the database. It passedcreated_at/expires_atstraight to the model, whose properties are typed?DateTime, so a MySQL date string threwTypeError: Cannot assign string to property … of type ?DateTime. Implement querying the database for notifications #307 would hit this first.How?
includes/helper/class-serde.phpmaybe_deserialize_mysql_date()parses with!Y-m-d H:i:sin UTC (!also resets microseconds).2024-02-30, month13, the MySQL zero date) and non-string values now returnnull.DateTimepassed in is converted to UTC without modifying the original.maybe_serialize_json_date()always outputs UTC (+00:00).includes/model/class-message.phpexpires_at,severityandchannel_titlefrom$meta_keys.jsonSerialize()still includeschannel_titleandseverity, so the REST response has the same fields (only the key order changes).validate_message(). Its 255-byte limit would not match theTEXTcolumn planned in Database schema v2 #615.includes/factory/class-message.phpcreated_atandexpires_atgo throughSerde::maybe_deserialize_mysql_date(), matching the Notification and Subscription factories.Tests
tests/phpunit/tests/test-serde.php(14 tests). It sets the PHP default timezone toAmerica/New_Yorkto prove Serde doesn't depend on it, and covers the round trip,DateTimeconversion, no mutation of the input, and invalid input.test-model-message.php: updated the pinned JSON order and added twoencode_meta()tests.test-factory-message.php: added a test that makes a message from MySQL date strings.With the old code, 11 of the new tests fail (10 assertion failures, 1
TypeError).Testing Instructions
npx wp-env startnpx wp-env run tests-cli --env-cwd=wp-content/plugins/wp-feature-notifications vendor/bin/phpunitnpx wp-env run tests-cli --env-cwd=wp-content/plugins/wp-feature-notifications env WP_MULTISITE=1 vendor/bin/phpunitnpx wp-env run tests-cli --env-cwd=wp-content/plugins/wp-feature-notifications vendor/bin/phpunit --filter "Test_Serde|Test_Model_Message|Test_Factory_Message"Local results: 79 tests, 0 failures on single site and multisite (the 24 skipped are the existing REST controller placeholder tests). PHPCS is clean.
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Drafting the fix and unit tests. I chose the approach from the issue, decided to include the
Factory\Messagefix in this PR, reviewed every change, and verified it locally with PHPUnit (single site and multisite) and PHPCS.