Problem/Motivation
When a module unintentionally sends a NULL argument into the watchdog logs, whilst trying to render it on the watchdog logs, the following fatal exception is thrown:
TypeError: Drupal\Component\Utility\Html::escape(): Argument #1 ($text) must be of type string, null given, called in /var/www/html/repos/drupal/core/lib/Drupal/Component/Render/FormattableMarkup.php on line 238 in Drupal\Component\Utility\Html::escape() (line 433 of core/lib/Drupal/Component/Utility/Html.php).
Drupal\Component\Render\FormattableMarkup::placeholderEscape() (Line: 211)
Drupal\Component\Render\FormattableMarkup::placeholderFormat() (Line: 78)
Drupal\Component\Render\FormattableMarkup->__toString() (Line: 1302)
Drupal\views\Plugin\views\field\FieldPluginBase->renderText() (Line: 1241)
Drupal\views\Plugin\views\field\FieldPluginBase->advancedRender() (Line: 230)
template_preprocess_views_view_field()
call_user_func_array() (Line: 308)
This is because \Drupal\Component\Utility\Html::escape() requires a string argument, but \Drupal\Component\Render\FormattableMarkup::placeholderEscape may potentially pass the NULL argument directly to the Html::escape().
Steps to reproduce
Create a log entry along the lines of this:
$destination = NULL;
\Drupal::logger('file')->notice('The upload directory %directory could not be created or is not accessible.', ['%directory' => $destination]);
Then try to view it on /admin/reports/dblog
Proposed resolution
Rather than allowing NULL to reach Html::escape(), placeholderFormat() now checks NULL values for @ and % placeholders before the switch statement. When a NULL value is encountered, a PHP E_USER_NOTICE is triggered, which includes the placeholder key and the original string for developers to fix the issue. In result the empty string is substituted for the page to render.
: placeholders already handled NULL silently via $value ?? '' (appropriate for URL attributes where NULL is a common legitimate value) and this behaviour is unchanged.
Remaining tasks
Provide MR
| Comment | File | Size | Author |
|---|---|---|---|
| #4 | 3554974-drupal--html-escape-null.diff | 719 bytes | robloach |
Issue fork drupal-3554974
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:
Comments
Comment #2
codebymikey commentedNot working on this at the moment, just thought to document it.
Comment #3
cilefen commentedComment #4
robloachThis adds the type-hint, and checks if the input is NULL.
Comment #5
smustgrave commentedWill need test coverage for this one. Also may want to investigate #3 if how much they overlap.
Comment #6
grgcrlsn321 commentedCan confirm the patch in #4 solved my issue for the same error I had when creating new media content when I upgraded to Drupal 11.2.9. Thank you @robloach
Comment #8
grgcrlsn321 commentedCreated an Issue fork and merge request including the code from #4 and added test coverage for placeholder set to NULL.
I found that The existing patch in #4 only partially fixes this issue. It handles NULL values in `placeholderEscape()` for `@` and `%` placeholders, but misses the `:` placeholder case (used for URLs).
Setting to needs review.
Comment #9
grgcrlsn321 commentedThe NULL handling in FormattableMarkup revealed some edge cases where code was passing NULL values to placeholders. I've fixed these as part of this issue:
1. **MediaLibraryWidget.php** (lines 430, 437): Now checks that `$media_item->label()` is not NULL before using it in placeholders. Falls back to generic "Remove media" / "Removing media." messages when label is NULL.
2. **BlockEntitySettingTrayForm.php** (lines 40, 84): Now checks that `admin_label` exists before using it in placeholders. Falls back to "Configure block" / "Save block" when admin_label is NULL or empty.
These changes ensure that when FormattableMarkup receives NULL placeholder values, the calling code provides appropriate fallback messages rather than producing incomplete output like "Configure " or "Removing .".
The updated test coverage in FormattableMarkupTest::testNullPlaceholder() verifies that NULL values are handled correctly for all placeholder types (@, %, :).
Comment #10
smustgrave commentedLeft some comments on the MR
Thanks!
Comment #11
grgcrlsn321 commentedOkay, I've taken note of the comments and updated the code accordingly. Setting back to Needs Review. Thanks @smustgrave
Comment #13
smustgrave commentedComment #14
grgcrlsn321 commentedI cleaned up my code changes and removed what was out of scope that I originally had to make the tests pass. I wrote better testing to test the NULL placeholder values and test NULL values in @ and % placeholder to trigger deprecation.
Comment #15
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #17
grgcrlsn321 commentedOkay, fixed php unit tests errors and set the correct branch to 11.x for MR. Setting to Needs Review.
Comment #18
grgcrlsn321 commentedUpdating target version to 11.x since this adds a deprecation (not removes code).
This will be included in Drupal 11.4.0 and the deprecated code path will be removed in a follow-up issue for Drupal 12.0.0.
Comment #19
smustgrave commentedissues are actually landing in main first now.
Comment #20
grgcrlsn321 commentedOkay, I couldn't get the tests to run on the pipeline on the Merge Request so I switched it to 11.x and it worked for me. Not sure if that was the right move?
Comment #21
smustgrave commentedThey should be in sync. But yes will have to be mergable to main. That’s what everything has been getting merged to for a few weeks
Comment #22
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #25
samitk commentedHello all, I have changed the targeted branch to main.
Comment #26
dcam commentedI haven't done a thorough review of the MR, but I skimmed it and noticed the use of
#[Group('legacy')]in the new tests.#[IgnoreDeprecations]should be used instead. See #3549970: Use #[IgnoreDeprecations] instead of #[Group('legacy')].Comment #27
samitk commentedComment #28
dcam commentedThere's no need for apology. I was reviewing the code, not actively working on it. Thank you for your work to improve Drupal.
Please see my previous comment where I set the status to Needs Work. We no longer use
#[Group('legacy')]for deprecation tests. We use#[IgnoreDeprecations]instead.Also, the parameter type hints should be added to the function declarations too, e.g.
public function testNullPlaceholderDeprecated(string $string, array $arguments, string $expected): void {.Comment #29
samitk commentedHello @dcam, Good catch, I have updated
#[Group('legacy')]with#[IgnoreDeprecations]. Also, the parameter type hints are already included for the functiontestNullPlaceholderDeprecated.Please review it.
Comment #30
grgcrlsn321 commentedI added the type hinting on the function params. Thanks @samitk @dcam for your contrib. I'll set this to review.
Comment #31
grgcrlsn321 commentedComment #32
davidiio commentedWe had a similar error with a view that was ok in frontend but could not edit in admin after upgrading from drupal 10 to 11.
We have applied this merge request plain diff as a patch on Drupal 11.3.3 and it solved our problem.
We were then able to edit the view and see a warning
User deprecated function: Passing NULL as a placeholder value is deprecated in drupal:11.4.0 and is removed from drupal:12.0.0Turned out the problem was a NULL "item per page" on the mini pager. When using "0" the warning disapear and view is working fine.
I tried setting items per page to NULL and I could save the form and see the warning from this issue again, that's another issue but maybe views should not allow to save the form if "items per page" is NULL?
EDIT: Yes I have found the issue with the "item per page" problem https://www.drupal.org/project/drupal/issues/3564838
Comment #33
sharonho commentedApplied https://www.drupal.org/project/drupal/issues/3554974#comment-16338092 to my recent D11 upgrade to 11.3.5. Works fine on mine. Thanks
Comment #34
smustgrave commentedResolved the open threads I opened. To me this seems like it could be ready @dcam what do you think?
Comment #35
dcam commentedI started a code review on the MR, but eventually realized that this issue has deeper problems.
placeholderEscape()to sanitize bad input. And per the backward compatibility policy we should be able to add type hints to it at any time without deprecation to enforce good input because it's internal.placeholderFormat()is the function that parses out the arguments and callsplaceholderEscape(). That is where we should be checking that the arguments meet expectations, at the top of itsforeachloop. Assuming that we decideFormattableMarkupshould be responsible for sanitization*, then that's where you would do it. At that point having a deprecation no longer makes sense because we aren't intending to remove that sanitization in the future and we aren't changing any type hints.* I think it should throw an
InvalidArgumentExceptionif an improper value is encountered and force calling code to fix their inputs, but that's just me.Comment #36
dcam commentedDiscussed with @smustgrave that if we move forward with having
placeholderFormat()sanitize values, then it should log something. I'm not sure what log level without looking at the PSR documentation, maybe "notice" or "debug". Because otherwise there's no information at all that this is happening. But if we start logging things, then maybe we can get fixes pushed to code that calls this with improper input.Comment #37
grgcrlsn321 commentedComment #38
grgcrlsn321 commentedI made changes based on the comments from @dcam.
placeholderFormat()away from the internal placeholderEscape() call.E_USER_NOTICEas a log level onFormattableMarkupthat is inDrupal\Component.Comment #39
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #40
grgcrlsn321 commentedFixed phpcs
Comment #41
grgcrlsn321 commentedsetting back to needs review
Comment #42
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #43
grgcrlsn321 commentedFixeed phpstan errors.
Comment #44
longwaveInstead of triggering a notice, could we assert(), which will cause an error for developers but then we can fall back to empty string after that in production where assertions should be disabled?
Comment #45
smustgrave commentedSeems like a plan @longwave.
Also MR should be rebased being 500+ back please
Comment #46
grgcrlsn321 commentedOkay, only took 2 rebases to get it right :). I updated the branch and switch to using assert rather than a notice. Tested using commands below.
Production:
Development: