Html::decodeEntities() and Html::escape() can only take strings as their parameters because they pass the value to PHP functions that can only take strings. Sometimes, NULL is passed, leading to errors in PHP 8.1. By adding type declarations, the source of these errors can be identified and fixed.

Original report

In FillPDF tests on PHP 8.1, I am seeing errors like this:

1) Drupal\Tests\fillpdf\Functional\HandlePdfControllerTest::testSaveAction
Exception: Deprecated function: htmlspecialchars(): Passing null to parameter #1 ($string) of type string is deprecated
Drupal\Component\Utility\Html::escape()() (Line: 424)

The FillPDF code that triggers this is an ordinary call to drupalPostForm(). Parameter $form_html_id is NULL, but this value does not end up getting passed to Html::escape().

It does not look to me like there is any way I could fix this within FillPDF. So, I think this is a core bug. Similar errors were part of #3241280: Fix PHP 8.1 deprecations caused by views code.

Example test result.

Issue fork drupal-3271517

Command icon 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

Liam Morland created an issue. See original summary.

liam morland’s picture

Title: Passing null to parameter of type string is deprecated in Html::escape() » Add type declarations to Html::decodeEntities() and Html::escape()
Issue summary: View changes
Status: Active » Needs review

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bserem made their first commit to this issue’s fork.

bserem’s picture

bserem’s picture

Added a second MR against core 9.4.x

smustgrave’s picture

Version: 9.4.x-dev » 10.1.x-dev
Status: Needs review » Needs work
Issue tags: +Needs tests

Moving to 10.1.x as this seems like a PHP8 issue and D9 still supports php7.4 not sure if this fix will break those versions.

Also could use a test case.

liam morland’s picture

Status: Needs work » Needs review

I have rebased my fix.

Adding ?? '' will clear the error but not solve the underlying problem. Adding the type declarations means the error reporting will say what other code is sending non-strings into these functions.

What kind of test could be added? This gets tested by the existing tests that test these functions.

smustgrave’s picture

If this is a bug then the test cases should of caught it. So could use one that would.

liam morland’s picture

What would the test look like? Will it allow a test that deliberately breaks a type declaration and checks that an error is raised?

smustgrave’s picture

Sound right. If you pass in a non string verify the return matches the error

liam morland’s picture

Pass NULL to the function and the test has to pass if PHP throws as error. How would one write a test for that?

liam morland’s picture

@bserem If this is working for you, perhaps you could mark it RTCB.

borisson_’s picture

Pass NULL to the function and the test has to pass if PHP throws as error. How would one write a test for that?

You can assert for a TypeError to appear, but I'm not sure if we should actually go trough with this issue. See https://www.drupal.org/project/drupal/issues/3050720#comment-13092605 for example, adding type hints are bc breaks so we will have first decided on when and in what manner we should add them. I'd suggest we first get that issue moving again and then create tickets to do it in bigger blocks? Doing it method-per-method or even with just one class feels like this is too small. I'd suggest we do this by adding a rector or phpcs rule instead and automate as much as possible?

liam morland’s picture

This one is not a BC break. As it is, if you pass NULL to decodeEntities() or escape(), you will get an error, but the error will not be very useful because it will just say that decodeEntities() or escape() called html_entity_decode() or htmlspecialchars() with NULL. By adding the type declaration, the error will be reported at the place were decodeEntities() or escape() was called. This allows those places to be fixed.

A comprehensive patch to add type declarations everywhere would be a very big undertaking and will take a long time. In the meantime, there will be errors about NULL being passed into a string and it will be hard to figure out where they are coming from. Fixing this issue now will make those errors easy to fix and demonstrate the value of type declarations.

neclimdul’s picture

Seems like y'all are just rehashing the discussions on #3255637: Deprecate NULL values in Html::escape(), ::decodeEntities(), and FormattableMarkup::placeholderFormat() to make it easier to upgrade to PHP 8. This just has decodeEntities and misses placeholder escape. Feels like we should close this and continue over there since its further along.

smustgrave’s picture

Agree with #20

liam morland’s picture

One important difference is that the other one is more complicated and involves union types. This one is very simple. It just exposes the type requirements that exist anyway. It does not cause any backwards-compatibility problems. Committing this fix would be very helpful while not impeding the work on the other issue.

neclimdul’s picture

It only has union types because this issue didn't address that the escape change affects placeholderEscape. So it is not complete and as mentioned behind the other patch. I discussed why even this patch _actually_ breaks BC in the other issue. Summary, today tests fail. With this patch sites fatal.

liam morland’s picture

Tests pass for the changes in merge request 2130. This merge request is targeting Drupal 10, so PHP 8.1 is required. This patch is only surfacing type constraints that already exist. This does not cause any new error messages to be generated; it only causes the error messages to be more useful.

Even without this patch, passing a non-string to decodeEntities() or escape() would cause an error. But the error would be reported inside whichever of those functions was called, making it hard to track down where the problem comes from. With the patch, the error would be reported at the caller of decodeEntities() or escape().

emil stoianov’s picture

StatusFileSize
new549 bytes

Providing the same fix as in MR !2547 (for 9.4.x) but as a patch

alvarito75’s picture

I confirmed this works, see below my setup:

  • Drupal Version: 9.4.7
  • PHP: 8.1.4
longwave’s picture

Re #24 in PHP 8.1, passing a NULL to those methods currently causes a deprecation notice, but not an error - code continues to execute:

>>> \Drupal\Component\Utility\Html::escape(NULL);
PHP Deprecated:  htmlspecialchars(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/html/drupal/core/lib/Drupal/Component/Utility/Html.php on line 424
=> ""

But once the MR is applied, this becomes a fatal error:

>>> \Drupal\Component\Utility\Html::escape(NULL);
TypeError: Drupal\Component\Utility\Html::escape(): Argument #1 ($text) must be of type string, null given on line 1

MR!2130 passes because core never passes NULL in normal operation - but almost certainly, contrib and custom code does. We should add a test to cover this case!

So: as #18 says, this is a BC break. The better, albeit slower, way to deal with it is in #3255637: Deprecate NULL values in Html::escape(), ::decodeEntities(), and FormattableMarkup::placeholderFormat() to make it easier to upgrade to PHP 8 and I think this should be closed as a duplicate of that.

liam morland’s picture

Status: Needs review » Closed (duplicate)

I agree. Sorry for overlooking that the MR changes the deprecation notice to a fatal error.

Duplicate of #3255637: Deprecate NULL values in Html::escape(), ::decodeEntities(), and FormattableMarkup::placeholderFormat() to make it easier to upgrade to PHP 8.