Problem/Motivation
Fields may have serialized columns that can be NULL if they are not required. A good example from Drupal Core is the Link field which has a nullable options column. SqlContentEntityStorage automatically tries to unserialize these columns. If the value is NULL, then the following deprecation warning is issued:
Deprecated function: unserialize(): Passing null to parameter #1 ($data) of type string is deprecated in Drupal\Core\Entity\Sql\SqlContentEntityStorage->loadFromSharedTables() (line 602
This probably doesn't happen during normal editorial workflows. In the wild NULL values have resulted from migrations and something to do with translatable Paragraphs. See #2871217: Handle NULL URL options in LinkFormatter::buildUrl().
Also note that this has nothing to do with serialized NULL values. This is about empty columns.
Steps to reproduce
- Add a Link field to any node type.
- Create a new node of that type. Put any sample values you want in the Link field.
- Manually edit the site's database and update the Link field's options column to set the value to NULL.
- (optional?) Rebuild the cache.
- View the node. Note that the site will crash with a WSoD due to #2871217, but that's a separate issue.
- Check the site's recent log messages. Look for an entry with the error shown above.
Proposed resolution
Introduce a utility method called handleNullableFieldUnserialize() to replace the current usages of unserialize() in SqlContentEntityStorage. handleNullableFieldUnserialize() should return NULL if the passed value is NULL.
Remaining tasks
Review. Feedback. Commit.
User interface changes
API changes
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|
Issue fork drupal-3300404
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3300404
changes, plain diff MR !8092
- 3300404-deprecated-function-unserialize
changes, plain diff MR !2552
Comments
Comment #3
eduardo morales albertiComment #4
taras.suliatitskiy commentedPatch for drupal version 9.3.19
Comment #5
daffie commentedComment #6
albeorte commentedThe change looks good to me, and seems to fix the issue.
Applied in D9.4
Comment #7
avpadernoComment #8
alexpottThe steps to reproduce in the issue summary are not accurate. We are currently testing Drupal core on PHP 8.1 and this does not occur. This error is likely something else that is wrong and it would be great to understand what that is rather that doing unnecessary string casts everywhere.
Comment #10
bramdriesenSet proper status
Comment #11
daffie commentedComment #8 from @alexpott has not been addressed.
Comment #13
brunodboI'm seeing a similar warning when programmatically loading a profile, when the
datacolumn for a profile's record in theprofile_revisiontable is NULL:Deprecated function: unserialize(): Passing null to parameter #1 ($data) of type string is deprecated in Drupal\Core\Entity\Sql\SqlContentEntityStorage->mapFromStorageRecords()See #3324465: [PHP 8.1] Deprecated function: unserialize(): Passing null to parameter #1 ($data) of type string is deprecated in Drupal\Core\Entity\Sql\SqlContentEntityStorage->mapFromStorageRecords() for more details (I filed a bug report for Profile module, but not sure if that was the right place).
The patch in #4 fixes the warning.
Comment #14
avpadernoComment #15
akoe commentedIt seems the deprecation warning occurs when

$row[$column_name]isnullwhich can be the case for any null-able table columns related to fields that are tried to be loaded here.In the attached xdebug screenshot the columns
link_override__uri,link_override__titleandlink_override__optionstrigger the warning.The handled columns are provided by
TableMapping->getColumnNames(). This method provides all column names, so i guess a check for empty ones could be necessary.Comment #16
akoe commentedJust updated the branch on the related issue fork. @p-neyen's solution maked sense to me and applied it to the method
SqlContentEntityStorage->loadFromSharedTables()Also @alexpott's concerns are addressed there.
Comment #17
metallized commentedIt seems legit to me, updated status.
Comment #18
metallized commentedSorry for the bad status update, i test against the last merge request and what was reported by @brunodbo at #13 is still happening.
Also the cast to string on
unserializefunction seems to works (#4).Comment #19
wranvaud commentedThis is happening on taxonomy terms with an empty description. It looks like checking for the value before unserializing is a good solution though I believe null coalescing operator is better than type casting because casting an array to string would output "Array" rather than throwing an error.
Comment #20
avpadernoComment #21
holo96 commented#19 solves issue.
I am wondering if this issue is Normal priority?
This kind of makes drupal 9.5 & 10 incompatible with >php8.1, at least partially, no one wants warnings all over their website.
PHP 8.1 is recommended.
https://www.drupal.org/docs/system-requirements/php-requirements
Comment #22
holo96 commentedAnd just want to add, 'map' field type from core is affected, I am not sure if it is used in core...
But commerce order uses it.
Basically all fields with serialized property are affected
Comment #23
quietone commentedComment #8 points out that the core is tested on PHP 8.1 and this error is not occurring. In that comment steps to reproduce the problem were asked for and they have not been supplied.
@akoe, can you expand more on #16 where you said that alexpott's concerns are addressed?
This needs steps to reproduce so we can duplicate the problem manually and in a test.
I am setting this back for those steps to be added.
Comment #24
holo96 commentedAs mentioned in #13:
Steps to reproduce:
1. Create new entity type
2. Install newly added entity type
3. Create at least one entity (id: 1)
4. Add map base field to created entity type:
5. load entity
6. Warning will occur
I've tried adding:
but it does not help
Comment #25
holo96 commentedComment #26
smustgrave commentedThis seems like we are fixing a symptom of a possible bigger issue.
But either way it will need a test case showing the issue.
Comment #27
holo96 commentedHere is failing test.
In case #19 patch is applied, test passes.
I've just updated existing test, I am not sure if we should create new test case for this issue?
Comment #28
holo96 commentedHere is combined patch od #27 and #19
Comment #30
holo96 commentedSo #27 proves recreation of error...
And it is fixed in #28
Comment #31
smustgrave commentedWill move this one on and see what the committers think.
Comment #32
quietone commentedThanks for the steps to reproduce. I used code from the examples module to play with this and wasn't able to reproduce the error.
I do agree with #26 that this may be fixing the symptom and not the underlying problem. This also should have a better title, one that describes the action that the issue is fixing or improving.
Comment #33
holo96 commentedYes, something is wrong with initial value...
- In case new entity is added after field installation, value for map field is empty array "a:0:{}" (maybe we can change to that in default value?)
- Empty string does return false when unserialized but so does null and drupal 8 and 9 worked properly whole lifetime
So I guess either we use
Or we fix initial value and create update hook for populating values for backward compatibility.
Edit: also all fields are set to null when empty, so I think for consistency we should be able to set map fields to NULL
Comment #34
smulvih2#28 seems to work for me on 9.5.5
Comment #35
rollins commentedPatch #28 resolved the issue, thank you DavorHorvacki!
Comment #36
catchI think the root cause of this is probably related to #2346019: Handle initial values when creating a new field storage definition - maybe map item doesn't set an initial value in the field storage definition?
It could also be #2883851: Add initial values support for configurable and multi-valued fields though which is unresolved.
If we're able to fix this by setting an initial value, then I think we should. If it's due to #2883851: Add initial values support for configurable and multi-valued fields , then we should add a comment with a @todo to the places adding the null check.
Comment #38
donquixote commentedSo for us this happens with a custom base field of type 'link', that is set to be optional.
The base field adds three columns to `node_field_data` and `node_field_revision` tables:
- my_link_field__uri: varchar, allows NULL
- my_link_field__title: varchar, allows NULL
- my_link_field__options: serialized, allows NULL
Checking the database, I find a bunch of rows where `my_link_field__options` has the value NULL, and other rows where the value is `N;`.
The `changed` column shows that all those with value NULL are old, and those with value `N;` are newer.
So this means that, when the base field is added, the value for that column is initialized as NULL.
But when updating or creating content later, the column is initialized with `N;` (serialized NULL).
So the steps to reproduce would be:
- Create a node, while no custom base fields have been declared yet.
- Declare a custom base field in a module.
- Create another node.
- Load each node (separately), compare the output.
- Also check the database.
Observed behavior:
- Column is NULL for the old node, but 'N;' for the new node.
- Loading the old node produces a warning.
Comment #39
donquixote commented@catch
I think the initial value idea is incomplete at best.
If a database column allows NULL, then we should not allow this to cause a problem when loading the entity.
So we need to do one of two things:
Interestingly, `serialize(NULL)` returns FALSE instead of NULL. This is also the legacy behavior, and this is what the patch does.
However, we probably rather want NULL instead of FALSE.
To achieve that, we could use `unserialize($row[$column_name] ?? 'N;')` instead of `unserialize($row[$column_name] ?? '')`.
But we would need to investigate the side effects.
Comment #40
holo96 commentedFor map field type default value after field is installed is 'a:0:{}' not 'N;', but NULL could work anyway
Comment #41
hockey2112 commentedThe patch is #28 worked for me.
Comment #42
saganakat commentedThe patch is #28 worked for me in drupal 9.5.9
Comment #43
megakeegman commentedPatch #28 also worked for me, in Drupal 9.5.4
Comment #44
holo96 commentedTest only!
I've added link field to test and separated it to new test file.
I won't be working on this issue further because I still think #19 is easiest all around solution, backward compatible.
Comment #45
nagy.balint commentedThanks!
#28 also works for me under 10.1.4
Comment #46
avpadernoComment #47
smustgrave commented#44 appears to be a tests only patch that didn’t run. Could we get also a full patch
Comment #48
holo96 commentedHere is combined patch.
I'll move this to Needs review but #36 stays unresolved.
Comment #49
smustgrave commentedThink to move this forward till #2883851: Add initial values support for configurable and multi-valued fields is to do what @catch suggested and added a todo pointing to #2883851: Add initial values support for configurable and multi-valued fields
Comment #50
kiwad commentedIn case this helps
I had this specific
Deprecated function: unserialize(): Passing null to parameter #1 ($data) of type string is deprecated in Drupal\Core\Entity\Sql\SqlContentEntityStorage->mapFromStorageRecords()
In my case, was related to Profile, maybe some update in the past didnt go through or is missing
Simply loading/saving the entity changed value of data from false to array and message disappeared
Comment #51
pgrandeg commented#48 works perfect on d10.1.4 and PHP8.1
Comment #52
papagrandeFor others' reference, a client had a link field that was triggering this error on six nodes. The status page had no errors about the field and the configuration status was clean.
Resaving the nodes didn't fix the problem. Nor did copying data via SQL from the field table to the revision table. Here's what did:
And after resaving the field, the configuration status still had no changes between database and file.
Comment #53
louis-cuny commentedComment #54
krystalcode commentedI focused on
mapFromStorageRecordssince that was the error that I was encountering. In my case an old profile entity had thedatacolumn set toNULLin the database.From #48:
$values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT] = !empty($column['serialize']) ? unserialize($record->{$column_name} ?? '') : $record->{$column_name};With this
unserializewill returnFALSEwhen passing an empty string. The entity field then ends up having a field item with valueFALSE- which is wrong; the field item list should be empty.I think the following would be better:
Comment #55
socialnicheguru commented@krystalcode could you updte patch so we can test.
In general is there a need to develop tests for this?
Comment #58
rosk0Started new merge request targeting 11.x with:
Comment #59
amanire commentedGood to see progress here! FWIW I just deployed the patch in #48 to a production site and it resolved the deprecation warnings described in https://www.drupal.org/project/profile/issues/3324465. I was a little surprised at the substantial CPU performance improvement for the site, presumably because these were invalidating the varnish cache.
Comment #60
holo96 commentedUpdating NULL values to either 'a:0:{}' (map field) or 'N;' (almost any other) on problematic column(s) once also resolves problem.
No patch needed. You can create update hook for this.
This works because problem is appearing only when you are adding field to entities with existing entries.
Comment #61
smustgrave commentedPart of IS appear to be missing. Thanks
Comment #62
junglePer @krystalcode's comment in #54, I introduced a utility method to replace the current usages of unserialize() in SqlContentEntityStorage
NW for adding tests.
Comment #63
jungleWith tests added
Comment #64
smustgrave commentedThanks for working but would need to be an MR to test
Also was tagged issue summary update which still appears to be needed.
Comment #69
jungleComment #70
smustgrave commentedThanks for a clear issue summary!
Can we get a change record for the new public function that could be used for others. Sure a contrib module out there maybe could leverage it.
Comment #71
oldspot commentedThe changes in MR 8092 worked great testing them now on a D10.3.6 install. Thanks.
Comment #72
joseph.olstadPatch 63 works well on 10.4.0
Comment #73
spokjeAdded a first draft for a CR.
Comment #74
smustgrave commentedCR reads well.
Left some small comments on the MR. But super close and will keep an eye out for this one.
Comment #75
spokjeComment #76
gdaw commentedWorks good on Drupal Version 10.4.1
Comment #77
smustgrave commentedAppears to be 1 open thread, @berdir should this be postponed on #3421234: Json and PHP serializers should throw exception on failure which is also up for review?
Comment #78
dcam commentedThe Link field also has this problem with its serialized options column that can be NULL. See #2871217: Handle NULL URL options in LinkFormatter::buildUrl().
Comment #79
dcam commentedI'm not done working on this, but I need to save my progress because I've been making these changes on my work laptop and I'm about to head home for the weekend.
I agree with the things @berdir said.
Yeah, I don't think we should put utility methods in entity storage classes. That's the kind of thing services are for. And we already have a service for serialization. Let's use it.
No, we shouldn't. We should be doing whatever
PhpSerializedoes. The utility method picked up a few extra things that it did that are outside the scope of this issue. Dropping the invalid values were included. It also included converting empty strings to NULL. That's a behavior change that could impact downstream code and I don't even know what prompted it. I got rid of all of it. Now thesafeUnserialize()function only checks the NULL case, then passes the work off toPhpSerialize.testSafeUnserializehas to be updated because I made that static utility function protected.Also,Edit: I take that back. It does trigger the deprecation warnings when run without the fix. I must have not reset the fix properly the other day.InitializeSerializedPropTestpasses with or without the bug fix. So something about that test is wrong.Comment #80
dcam commentedAlso, I don't think we need to postpone this on #3421234. We just need to use
PhpSerialize. Then it can do whatever it's going to do and the entity storage will benefit from those improvements.Comment #81
dcam commentedImplementing
PhpSerializeand injecting it into the class resulted in hundreds of deprecation errors. I didn't want to deal with that, so I dropped the idea. So now the only thing this change does is check for the NULL case, which is what it was originally about. If someone wants to pick up possible improvements to unserialization that might happen inPhpSerializein the future, then they can do that in a separate issue. Let's keep this about fixing the NULL errors, which is a problem specific to how Drupal interacts with the database.I deleted the tests that were written previously. One was no longer relevant because of the change away from a static function. I felt that the other one wasn't focused enough. It tried to replicate the older steps to reproduce the problem which involved making an entity, then later adding a serialized base field to the entity type. Basically, it was focused on testing the symptom, not the actual problem. I wrote a new test that checks nullable fields in both dedicated and shared tables.
Comment #82
dcam commentedI don't know if the CR is necessary anymore. It was written because a new static helper function was being added, but that's no longer true.
Comment #83
smustgrave commentedLeft a small comment if it can be expanded. Agree though this may not need a CR.
Comment #84
dcam commentedThe feedback was addressed. Thank you to @smustgrave for prioritizing the review of this issue.
Comment #85
smustgrave commentedThanks! Feedback appears to be addressed
Ran the test-only job
Haven't deleted the CR just in case but rest LGTM.
Comment #86
alexpottAdded an MR comment. Sorry I should have spotted this earlier but the method name is problematic as it does nothing to address security issues with unserialize even though it sounds like it does.
Comment #87
dcam commentedFeedback has been addressed. Setting to Needs Review for a 3rd opinion on the change.
Comment #88
alexpottComment #89
alexpottI've updated the issue summary and CR with the new name.
Comment #90
alexpottI've updated the contribution record for this issue.
Comment #91
dcam commentedThank you. Sorry I forgot about doing that.
Comment #92
smustgrave commentedFeedback for a new name appears to be addressed Thanks!
Comment #94
alexpottCommitted 4a32768 and pushed to 11.x. Thanks!
Comment #96
quietone commentedI think the change record needs to clarify that this change is specific to \Drupal\Core\Entity\Sql\SqlContentEntityStorage. As it reads to me it implies that all instances of
unserializein core are affected.Comment #97
dcam commentedI updated the CR. I believe that I made it clear that this change only affects
SqlContentEntityStorage.Comment #99
quietone commented@dcam, thanks for that. I did more work on restructuring the Change Record so that the change is the first thing mentioned and there is a before and after section. None of that should have changed the meaning. If I made a mistake, just edit it as needed.
I published the change record.