Problem/Motivation

If an entity type returns NULL from label(), then the controller title callbacks for the edit and delete pages will result in the following errors as shown in the test-only output:

1) Drupal\Tests\Core\Entity\Controller\EntityControllerTest::testDeleteEditTitleCallbacks with data set #1 ('deleteTitle', null, 'Delete')
TypeError: Drupal\Component\Utility\Html::escape(): Argument #1 ($text) must be of type string, null given, called in /var/www/html/core/lib/Drupal/Component/Render/FormattableMarkup.php on line 238

/var/www/html/core/lib/Drupal/Component/Utility/Html.php:433
/var/www/html/core/lib/Drupal/Component/Render/FormattableMarkup.php:238
/var/www/html/core/lib/Drupal/Component/Render/FormattableMarkup.php:211
/var/www/html/core/lib/Drupal/Core/StringTranslation/TranslatableMarkup.php:195
/var/www/html/core/lib/Drupal/Component/Utility/ToStringTrait.php:15
/var/www/html/core/tests/Drupal/Tests/Core/Entity/Controller/EntityControllerTest.php:55

2) Drupal\Tests\Core\Entity\Controller\EntityControllerTest::testDeleteEditTitleCallbacks with data set #3 ('editTitle', null, 'Edit')
TypeError: Drupal\Component\Utility\Html::escape(): Argument #1 ($text) must be of type string, null given, called in /var/www/html/core/lib/Drupal/Component/Render/FormattableMarkup.php on line 238

/var/www/html/core/lib/Drupal/Component/Utility/Html.php:433
/var/www/html/core/lib/Drupal/Component/Render/FormattableMarkup.php:238
/var/www/html/core/lib/Drupal/Component/Render/FormattableMarkup.php:211
/var/www/html/core/lib/Drupal/Core/StringTranslation/TranslatableMarkup.php:195
/var/www/html/core/lib/Drupal/Component/Utility/ToStringTrait.php:15
/var/www/html/core/tests/Drupal/Tests/Core/Entity/Controller/EntityControllerTest.php:55

Steps to reproduce

- create entity class with drush gen content-entity
- remove label field from code
- open edit entity form

Proposed resolution

Return only "Delete" and "Edit" from the respective title callbacks in the case of an entity label() returning NULL.

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

Issue fork drupal-3319212

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

Andrew Answer created an issue. See original summary.

andrew answer’s picture

StatusFileSize
new721 bytes

Patch provided.

bhanu951’s picture

Status: Active » Needs review

Setting it to need review for test bot and review.

joachim’s picture

> return $this->t('Edit %label', ['%label' => $entity->label() ?? '']);

That'll produce a stray space - 'Edit ' instead of 'Edit'.

andrew answer’s picture

StatusFileSize
new760 bytes

@joachim fixed.

joachim’s picture

That should probably just be:

$entity->label() ?

because that way it also covers the case where label() returns an empty string.

andrew answer’s picture

StatusFileSize
new761 bytes

@joachim,

usually a label is required (if it exists), so it cannot be an empty string. Anyway, if label is an empty string, is this my case?
is_null is mandatory because this way we can detect the absence of a label field.

The patch should have space and two CR at the end, added.

andrew answer’s picture

StatusFileSize
new762 bytes
andrew answer’s picture

andrew answer’s picture

penyaskito’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

I agree with @joachim, if empty we should return "Edit" and not "Edit " (extra space).
This needs tests.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

othermachines’s picture

Status: Needs work » Needs review
StatusFileSize
new780 bytes

Here's a re-rolled patch (11.1.x.)

borisson_’s picture

Status: Needs review » Needs work

Still needs tests, the remark in #11 seems to be fixed.

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

dcam’s picture

Title: Custom entity without label field generates warning for PHP 8.1 » Entities without labels cause TypeError in EntityController title callbacks
Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs tests

I converted the patch from #13 to an MR and added a Unit test. Then I realized that EntityController::deleteTitle() has the exact same problem. So I expanded the scope to include it.

It's a bit odd. ::deleteTitle() returns an empty string if the entity can't be loaded. Whereas ::editTitle() returns nothing (a NULL value). My new test does not cover these cases, partly because it would require different mocking and partly because I feel like making the two callbacks behave the same maybe ought to be handled in a follow-up issue. In which case the NULL entity test cases can be written at that time. Not my call though and that's WAY out of scope for this bug fix.

geek-merlin’s picture

Hope this can be addressed in the related issue. See comment #3504477-20: Allow all Renderable values in SDC slots.

UPS: I suppose this is the wrong issue.

geek-merlin’s picture

joachim’s picture

Good catch on the other callbacks!

And nice job making the test a unit test.

But I'm really not keen on the mixing of if/else and ternary that there is now.

We could maybe use the nullsafe operator like this (pseudocode):

$entity = getEntity()
$label = $entity?->label();

// Then we are back in the case where label is either a label or NULL.
dcam’s picture

But I'm really not keen on the mixing of if/else and ternary that there is now.

Yeah, it was kind of ugly. Unfortunately, the nullsafe operator doesn't help here because the functions don't return values based only on the label. They also return something in the case of NULL entities. Still, I didn't try to clean up the callbacks before, so I did it now. I'm not sure that I like this much either, but at least it should be easy to understand.

joachim’s picture

> Unfortunately, the nullsafe operator doesn't help here because the functions don't return values based only on the label. They also return something in the case of NULL entities.

Sorry, I don't follow.

We start with:

$entity = $this->doGetEntity($route_match, $_entity)

We have either an EntityInterface, or NULL, according to the docs for that method.

If it's NULL, then $entity?->label() is NULL.
If it's not NULL, then $entity?->label() returns the label or NULL according to its docs.

So in all cases, $entity?->label() is NULL or the label.

So we can do:

$label = $entity?->label();
if ($label) {
// This also covers for the wacky case where the entity label() returns an empty string.
}
else {
}
dcam’s picture

Currently, if the $entity is NULL then editTitle() returns NULL and deleteTitle() returns an empty string. This is separate from what happens if label() returns a value or NULL. If we want to maintain the current behavior of the functions, then we have to handle the case of a missing entity.

joachim’s picture

Ah right, thanks for explaining.

When editTitle() returns NULL it'll cause the same TypeError when it's output, so that's problematic too. We should always be returning a string, an empty one at worst.

It's going to be a really rare pathway though - you'd need to be on a route that has say node/42/edit as a parent path, and node 42 doesn't exist, so that the path breadcrumb builder tries to get the page title for node/42/edit.

pingwin4eg’s picture

I get a similar issue with deleted entities that had no labels, it is a kind of mix of the issue described here and #3554974: FormattableMarkup: NULL @ and % placeholder values should log a notice and use empty string. EntityDeleteFormTrait and DeleteMultipleForm use label() method while logging.

So I'm thinking, would it be better to return an ID from the default label() method (in ContentEntityBase or even in EntityBase class)? EntityDeleteFormTrait already uses IDs in messages and strings presented to the user (but not in the log message for some reason):

    return $this->t('Are you sure you want to delete the @entity-type %label?', [
      '@entity-type' => $this->getEntity()->getEntityType()->getSingularLabel(),
      '%label' => $this->getEntity()->label() ?? $this->getEntity()->id(),
    ]);

I'd only prepend an ID with the # character, but this is a personal preference, also an ID can be non-numeric.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Needs Review Queue Initiative

Echoing the sentiment about the unit tests, pasting the test-only run here too for others https://git.drupalcode.org/issue/drupal-3319212/-/jobs/8306385

From what I can tell there are no open threads and the changes make sense to me. Don't mind marking for you.

  • longwave committed d0b94dfc on 11.3.x
    fix: #3319212 Entities without labels cause TypeError in...
longwave’s picture

Version: main » 11.3.x-dev
Status: Reviewed & tested by the community » Fixed

@pingwin4eg I think that is worth exploring in a followup - should all entities have (non empty) labels by default?

In the meantime let's solve this immediate issue by committing this. Doesn't backport cleanly to 10.6, so this is going in 11.3 and up.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

  • longwave committed 250cd38c on 11.x
    fix: #3319212 Entities without labels cause TypeError in...

  • longwave committed 20de7941 on main
    fix: #3319212 Entities without labels cause TypeError in...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.