Problem/Motivation
When uninstalling the comment module on a system running PHP7.4 the following notices are generated:
Notice: Trying to access array offset on value of type null in field_delete_instance() (line 772 of /drupal-7.69/modules/field/field.crud.inc). Notice: Trying to access array offset on value of type null in field_delete_instance() (line 773 of /drupal-7.69/modules/field/field.crud.inc). Notice: Trying to access array offset on value of type null in field_delete_instance() (line 774 of /drupal-7.69/modules/field/field.crud.inc). Notice: Trying to access array offset on value of type null in field_delete_instance() (line 781 of /drupal-7.69/modules/field/field.crud.inc). Notice: Trying to access array offset on value of type null in field_delete_instance() (line 782 of /drupal-7.69/modules/field/field.crud.inc). Notice: Trying to access array offset on value of type null in field_delete_instance() (line 782 of /drupal-7.69/modules/field/field.crud.inc). Notice: Trying to access array offset on value of type null in image_field_delete_instance() (line 480 of /drupal-7.69/modules/image/image.module). Notice: Trying to access array offset on value of type bool in image_field_delete_instance() (line 481 of /drupal-7.69/modules/image/image.module).
This is because of the change to raise a notice on Array-style access of non-arrays.
Proposed resolution
Remove the call to field_delete_field() for the 'comment_body' field from the uninstall hook.
Add tests to verify that the field and its instances are marked for deletion. N.B. there's a related bug which means the instances are not actually removed when field_purge_batch() is called on cron. An earlier patch in this issue included a fix for that, but it'd be best to address that bug in a separate issue - probably #1340390: "Deleted" but "inactive" fields and instances cannot be removed during cron cleanup (e.g. for uninstalled entities).
Remaining tasks
Evaluate if the patch is the right thing to do.
User interface changes
None.
API changes
None.
Data model changes
The 'comment_body' may not be fully uninstalled if it used elsewhere.
Release notes snippet
Original report
comment_uninstall() first calls field_delete_field() and then calls field_attach_delete_bundle() which tries to do this again producing multiple PHP notices on PHP 7.4.
| Comment | File | Size | Author |
|---|---|---|---|
| #30 | interdiff-3085163-27-30.txt | 1.56 KB | mcdruid |
| #30 | 3085163-30.patch | 3.77 KB | mcdruid |
| #30 | 3085163-30_test_only.patch | 3.32 KB | mcdruid |
Comments
Comment #2
taran2lComment #3
taran2lComment #4
avpadernoComment #5
steven jones commented@Taran2L what are the notices and are they PHP7.4 specific?
Comment #6
naresh_bavaskarComment #7
hardik_patel_12 commentedComment #8
hardik_patel_12 commented@Taran2L thankyou for patch, patch in comment #2 LGTM.
Comment #9
steven jones commentedComment #10
steven jones commentedDigging into this one a bit more, the issue is not that comment module tries to delete it's fields more than once, it's that comment module tries to delete its field by calling
field_delete_field, which happily tries to delete the fields off of the disabled entity type: comment. This causes the notice.Essentially the notice is caused by this code:
that call to
field_info_instanceis returning NULL because the entity type (comment) doesn't really 'exist' any more in the running Drupal.I think the comment in https://www.drupal.org/project/drupal/issues/998048#comment-3826740 is spot on, but the documentation hasn't been updated (we shouldn't be calling
field_delete_fieldin an uninstall hook.) Given that this patch does remove the call though and because it does indeed iterate over all instances of the field and delete them, the field will eventually get marked for deletion either way. So it's all good!Comment #11
steven jones commentedComment #12
mcdruid commentedA quick manual test suggests that it's okay to remove the call to
field_delete_field('comment_body');Steps:
$ drush site-installSave settings on a content type e.g. /admin/structure/types/manage/article
So that all looks good. However, there are still some entries left over in the db:
The row in field_config is marked as "deleted" after the uninstall, whereas the two in field_name_instance are not AFAICS. I'm not sure whether that's a bug?
Either way, the outcome is the same with and without the patch.
It would be good to have a test which covers this; there isn't one yet.
D8 does this:
...but D7 obviously doesn't have
entityTypeManageretc..Comment #13
mcdruid commentedActually that was wrong.
It's
field_delete_field('comment_body');which marks comment_body for deletion (the actual deletion typically happens on a cron run).I'm still not sure whether it's a bug that the instances don't seem to be marked for deletion, but it seems like it might be.
I don't think we can just drop the call to field_delete_field from the uninstall hook though.
Comment #14
mcdruid commentedI think it is a bug that the instances are not being marked for deletion.
If we move the call to field_delete_field to the end of the uninstall hook, it looks to me like this works as it should.
The field and the instances are all marked for deletion, and the call to field_delete_field doesn't cause a Notice because the bundles have already been deleted by the time it's called, so it doesn't iterate over bundles for which field_info_instance will return null.
Let's see if the PHP 7.4 tests agree.
I am inclined to say we should add a test which verifies this (and would prove that without this change D7 has a bug here). It shouldn't be a difficult test to add.
Comment #15
mcdruid commentedHmm, oh yeah - there are no tests for the uninstall, so we won't get the Notices.
I'll have a look at adding test coverage.
Comment #16
mcdruid commentedI wrote some tests, but AFAICS what they prove is the correctness of the comment @Steven Jones linked to above about the Drupal WTF with inactive vs. deleted field instances.
Changing the order in which the comment module's uninstall hook deletes the instances and the field does make a difference, in that if we call field_delete_field('comment_body') last, the instances do get marked for deletion (which they don't with the existing order).
However, when cron runs
field_cron()callsfield_purge_batch()which we'd hope would delete the instances, but they get filtered out byfield_read_instances()because the field that they're associated with is now inactive:https://git.drupalcode.org/project/drupal/-/blob/7.x/modules/field/field...
So the instances don't get purged despite the fact they've been marked
deleted = 1, which is frustrating.It seems like we might want to specify
include_deletedin that call tofield_read_instances(), which looks like it would "fix" this cleanup step, but that's probably too dramatic a change to make at this stage.I'll include the tests I came up with FWIW but the last check for the deletion of the instances is not going to pass with or without the recent patches.
I think moving the call to
field_delete_field()to the end of the hook should still fix the Notice(s) which brought us here in the first place, and these tests should hopefully help prove that... but we're going to have to remove (/ tweak) the final assertion if we want the tests to pass without making more drastic changes to the fields system.So, we expect both of these patches to fail tests. They should show, however, that the PHP 7.4 Notices go away if we move
field_delete_field()to the end of the hook.Within the scope of this issue, that's probably all we can do, so we'll need to tweak the tests so that they don't fail as a result of the orphaned field instances.
Comment #19
mcdruid commentedThe output comparing exceptions from the tests is not very clear, but I'm fairly sure the new tests are effective at "surfacing" the Notices we're concerned with here.
It looks like moving
field_delete_field()to the end of the hook reduces the number of notices overall, but also introduces two new ones:These are the result of the invocation of hooks within
field_delete_field()AFAICS; likely because the return value offield_info_field($field_name)is not an array with all of the expected elements e.g.$field['type']and$field['storage']['module'].There are quite a few more of these exceptions between modules/field/field.crud.inc and modules/image/image.module which all look fairly similar.
I'm going to review some of the other child issues in #3081386: [META] Fully support PHP 7.4 in Drupal 7 to see whether the two new ones we're introducing here would be addressed by any of those, and will come back to this issue.
We'll also want to fix the new tests here so they don't fail; I'm thinking we should file a followup (if there's not one already) pointing out the problem with orphan field instances being left in the db, and make the tests here pass e.g. by using
field_read_instances()to check that the instances have been purged (it won't return the orphaned instances) or by explicitly checking that there are no instances left in the db which have not at least been flagged as deleted = 1. Neither feel great, but fixing the underlying problem is not within the scope of this issue.Comment #20
mcdruid commentedComment #21
mcdruid commentedHaving said this was out-of-scope of this issue, it looks like making this change may fix the issue for uninstalling comment module:
...in that we don't end up with orphaned instances which have been marked for deletion but will actually never be purged because they're associated with an inactive field / module.
I am curious whether this will break other things in the test suite.
Of course, even if it does not, that doesn't mean it wouldn't break things "out in the wild", but perhaps it's not too risky a thing to do; this should only be acting on instances which have been marked as ready to be deleted, IIUC. I'm not sure what legitimate reason we'd ever have to skip purging those?
Comment #22
mcdruid commentedOk, so yes it looks like this now passes with the new tests which try to ensure no cruft is left behind by the comment module's field(s).
The patch does introduce a couple of new exceptions in PHP 7.4 (but fixes several others), as noted in #19 but I think we need to fix quite a few of those between
modules/field/field.crud.incandmodules/image/image.modulewhich all look fairly similar. I am not certain those are being addressed in any other PHP 7.4 issue that I've come across yet.We could fix the ones that this patch causes here, and the rest in another issue... or perhaps create one new issue for all of them that we know about (including the two new ones the patches here introduce). Not much different either way, but I don't really want to commit a patch in this issue which introduces new exceptions.
Comment #23
mcdruid commentedAdded fixes for the two new exceptions in field and image modules (see interdiff).
Comment #24
mcdruid commentedI would appreciate any other reviews of #23 before I mark it as RTBC / Pending Drupal 7 commit.
You could certainly argue trying to fix the orphaned field instances problem is out of scope of addressing PHP 7.4 compatibility, but in some cases the language becoming stricter is revealing underlying bugs.
I think there's little question that the existing functionality is broken, and it feels like it would be a hack to try to resolve the PHP 7.4 exceptions in this uninstall hook without addressing the brokenness to some extent.
With the additional tests this now seems a fairly safe change to me.
FWIW comparing all of the fails/exceptions from the latest test on #23 it looks like an improvement on the current "baseline" test at #3081386-70: [META] Fully support PHP 7.4 in Drupal 7 although a diff of the results isn't that easy to read.
baseline:
test from #23
I'll address the overall progress on these tests in the parent issue.
Comment #25
mcdruid commentedComment #26
taran2lhi @mcdruid,
I've given it another spin and here are my findings:
Incorrect usage of
field_delete_field()inhook_uninstall()The original patch from #2 does fix notices in PHP7.4+ and does work as expected in the scope of the issue. As @Steven Jones has mentioned in #10
field_delete_field()cannot be used for inactive fields.With patch #2 I see that both field and field instances (two for standard install profile) are marked to be deleted.
So, all is good. Not sure how you ended up with instances not being marked as deleted in #12
Underlying bug in
field_purge_batch()As you mentioned in #16 and #21
field_purge_batch()is not able to delete field instances of inactive fields. This requires a separate issue IMHO.I believe all other Field API related notices/issues are not related to the scope of this issue and actually described in other issues.
Summary
field_purge_batch()in a separate issueComment #27
taran2lSo, attaching slightly modified patch from #21, addressing both issues from the previous comment
Comment #28
mcdruid commented(cross-posted with your last comment)
Thanks @Taran2L - it's probably a good idea to address the field bug in a separate issue.
However, if I follow your steps I get a different result after the uninstall:
...which is what the test failures in #16 illustrate, e.g. https://www.drupal.org/pift-ci-job/1658015
That's how I ended up going down the rabbit hole of addressing that problem here.
Not sure how/why you're seeing a different outcome to that with your manual test?
Comment #29
taran2lhi @mcdruid,
Patch from #26 is green on PHP7.2 including the new comment uninstall test, so I guess
field_delete_field()is not needed.My PHP7.4 setup is the following:
Baseline:
Patch #26:
All other field API related issues are addressed as well:
Comment #30
mcdruid commentedOk, but #27 (which I the patch I think you meant) also makes the change to
field_purge_batch()so that the inactive instances aren't skipped and do get purged from the db.Interesting that the test can pass with the call to
field_delete_field()completely removed :) Will take a closer look.We need to decide whether we're going to address the purging of instances here.
In fact without the call to
field_delete_field()in the uninstall hook, the field and the instances get marked for deletion, but the instances won't actually be removed on cron (unless we changefield_purge_batch()).So if we want to separate the fixes but retain the new tests, we could change the test so that it'll accept instances being left over if they're marked for deletion.
We can then address the instances not being purged separately.
I think that's what these patches should show - the test only one should fail as with the
field_delete_field()still in the uninstall hook, the instances are never marked for deletion. With it taken out, they are marked as deleted (but won't actually be purged by cron - we'll deal with that problem elsewhere though).Comment #32
mcdruid commentedOk, so I think that's the result we expected / wanted but this patch doesn't make the change to
field_purge_batch()which we've said should be dealt with elsewhere.Looks like this resolves the vast majority of the field.crud.inc exceptions:
How does #30 look to you @Taran2L ?
Comment #33
taran2lhi @mcdruid, yes this is exactly what I was looking for. Let's close the issue with instances deletion in #1340390: "Deleted" but "inactive" fields and instances cannot be removed during cron cleanup (e.g. for uninstalled entities).
+1 RTBC
Comment #34
taran2lMore to having #30 committed:
Patch on #30 produces the following issues:
There are 3 related to the field API:
All of them are resolved in other issues with RTBC statuses:
So after committing patch #30 and the abovementioned 3 patches there will be no PHP7.4 notices related to the field API.
Comment #35
mcdruid commentedGreat, thanks for the review and for all your work on these and other PHP 7.4 issues @Taran2L
Comment #36
mcdruid commentedComment #37
fabianx commentedRTBM - thanks all!
Comment #39
mcdruid commentedThanks everyone!