Problem/Motivation
An entity label is "the human-readable name of the entity type." It is defined through the 'label' element of the @ContentEntityType or @ConfigEntityType annotation.
The core Entity API allows an entity to define a function to be used to return the entity label, as an alternative to using the content of the 'label' annotation. This is done through the (undocumented) annotation 'label_callback'. The function named in the 'label_callback' annotation takes as an argument an instance of the entity (although the formal parameters of this function are almost completely undocumented, unless you count the text in the API documentation for EntityType::getLabelCallback()), which allows/implies that this function is a procedural function and not a method defined in the class of the entity.
By default 'label_callback' is a very odd notion.
If an entity wants to provide a different label, then overriding EntityInterface::label() is the proper OO way to do this.
The implied alternatives are:
-
- Define a 'label_callback' in the annotation. (Functionality in annotations is not a desireable thing, IMO). This 'label_callback' declares a function used to provide the label.
- Then declare a procedural function for the label. (Why support a procedural function?)
- Or declare an entity method for the label. Then why do we need to pass the Entity instance to this function, when $this will do?
- The function implementation in either case would be the same as the function implementation if you simply overrode EntityInterface::label(). So supporting 'label_callback' in the annotation does nothing more than support procedural functions.
-
- Override the protected variable EntityType::$label_callback.
- This performs an "implicit" override of EntityType::label(), and as such it obscures what is actually going on. One might think that EntityType::label() is still controlling the entity label, but it's not. This is IMO extremely bad programming practice.
-
- Call setLabelCallback() in your entity constructor. Then provide a label callback method to take the place of the inherited label() method. Again, this obscures what is going on and provides no added benefit over simply overriding EntityInterface::label().
or
or
The only core usage of the 'label_callback' annotation is in the user module, and this usage is deprecated. See #2311219: Fix hook_user_format_name_alter() documentation and stop referring to user_format_name().
Proposed resolution
- Remove 'label_callback' as a valid (but undocumented) annotation element.
- Remove the EntityTypeInterface::getLabelCallback(), EntityType:Interface:setLabelCallback(), EntityTypeInterface::hasLabelCallback(), EntityType::getLabelCallback(), EntityType::setLabelCallback(), EntityType::hasLabelCallback() methods and the EntityType::$label_callback instance variable.
- Remove test system/tests/modules/entity_test/src/Entity/EntityTestLabelCallback.php
- Rewrite Entity::label() to remove references to getLabelCallback()
- Remove deprecated user_format_name(() as in #2311219: Fix hook_user_format_name_alter() documentation and stop referring to user_format_name()
Remaining tasks
I will roll a patch, if no one objects to this proposal.
User interface changes
None.
API changes
Removal of getLableCallback(), setLabelCallback(), and hasLabelCallback() constitutes an API change.
Core does not use these functions.
Drupal 8 is not released yet, so no existing contributed modules rely on these functions.
Beta phase evaluation
Issue category | Task because things work without this change. |
---|---|
Issue priority | Major because it will be an API change. |
Disruption | Disruptive to contrib and custom code, because modules will need to override label() if they have specific needs for the label, rather than specifying label_callback. |
Postponed until
Comment | File | Size | Author |
---|---|---|---|
#65 | 2450793-65-interdiff.txt | 11.34 KB | Berdir |
#65 | 2450793-65.patch | 16.07 KB | Berdir |
#60 | 2450793-60.patch | 9.9 KB | Wim Leers |
#60 | interdiff.txt | 1.74 KB | Wim Leers |
Comments
Comment #1
Mile23+1 on doing something about this.
I'm working on #2311219: Fix hook_user_format_name_alter() documentation and stop referring to user_format_name() and it's really a pain to sort this out.
If
label_callback
is to go on existing, it needs to be able to define a true Callable in annotation. That is, an array of an object and the method to call on it suitable forcall_user_func()
.If it isn't to go on existing (and it shouldn't, for the reasons mentioned above), then the User entity should just override
label()
, and thengetUsername()
can just calllabel()
.We also have this one: #2112679: getUsername() should return the username getDisplayName() for the formatted user name which I bring up because it should use
label()
instead of adding another method.So basically this question of whither
label_callback
is an architectural blocker for those others.I'll make a patch.
Comment #2
Mile23This patch completely removes the ability to set
label_callback
for an entity.In the case of
User
, we implementlabel()
and it just callsgetUsername()
.It passes unit tests and a few simpletests... Let's see what the testbot says.
Comment #3
Crell CreditAttribution: Crell at Palantir.net commentedI fully support removing label_callback. It's well-overdue and no one should be using it (at least if they've been paying attention). The methods on the new objects are what people should be using.
Comment #4
Mile23Bump up to Major because it's an API change. Added beta evaluation.
Comment #5
Mile23Comment #6
jibranCan we also remove?
Comment #7
Mile23#2311219: Fix hook_user_format_name_alter() documentation and stop referring to user_format_name() is posponed on this issue.
Comment #8
nit3ch CreditAttribution: nit3ch commentedHere is the draft for change record : https://www.drupal.org/node/2481845
Applied the patch, everything looks fine.
Comment #9
Mile23Still here... anyone care to RTBC? :-)
Comment #10
jibranYeah sure.
Comment #11
star-szrWhy remove this test? It still passes as far as I can see and is still a valid code path to test.
Comment #12
BerdirI agree that it would be nice to get rid of this. However, not fully convinced that it is still possible in the current phase.
I'm not convinced that the beta evaluation is correct.
a) Blocking the removal of a deprecated function is not unfrozen AFAIK. It just means that @deprecated is a lie.
b) The change in the User entity is actually possible *without* removing the concept of label_callbacks. We could just not use it anymore.
label_callbacks (and uri_callback) exist for two reasons:
a) When they were added in 7.x, we didn't have classes, so it was not *possible* to add methods. So it made sense in that world.
b) Just like the entity key, it is something that can be changed through alter hooks. I'm not sure how useful that actually is, we still use uri_callback to do per-bundle URL's for forum terms, but we wanted to find a different solution there for a long time too.
Also agree that there is no need to remove the that test, just rename it to no longer mention Callback.
Leaving RTBC so that a core committer can decide how to continue with this.
Comment #13
star-szrThat seems like an excellent point, @Berdir!
Comment #14
almaudoh CreditAttribution: almaudoh commented+1 to this. Was just working on new entity and I saw "label_callback" in the user entity anotation and I was confused for a sec. I agree this should be removed as it tends to cause confusion for new developers.
Comment #16
Mile23Not sure why that last test pass failed... The test record doesn't say.
So I added the test back and named it UserEntityNameTest.
Yes,
label_callback
is from a time before OOP. But we're in a situation where we have some confusion about how to name users... For instance we have thelabel()
method which is annotated in the entity ast('User')
, which would lead me to believe that we shouldn't uselabel()
as a display name, or really even alter it. But then we havelabel_callback
which in use changes the user name, not the label.So the fewer ways to confuse the issue the better. We should have a clearly-defined API around user names moving forward. That's where #2112679: getUsername() should return the username getDisplayName() for the formatted user name is coming from, as well as the work to deprecate
user_format_name()
. If there are concerns about how developers can and should alter the name and label, we should address those in #2112679: getUsername() should return the username getDisplayName() for the formatted user name. If the preferred method for doing that is alabel_callback
, then I give up. :-)label_callback
is a throwback, has no documented behavior, and is confusing. It should go.Comment #17
Mile23Oops, the dreaded double-upload.
Also, setting back to RTBC prematurely. If the test fails it will go back to needs work. Please change the status if desired.
Comment #23
JeroenTRTBC as per #17.
Comment #25
JeroenTComment #28
JeroenTComment #29
Mile23Reroll.
Comment #30
Crell CreditAttribution: Crell as a volunteer commentedRe-RTBCing as it's just a reroll...
Comment #31
xjmThanks everyone for your work on this issue! However, I don't think we can go forward with it during this phase of the release cycle. See also @Berdir's feedback in #12 which seems to have been overlooked.
The beta evaluation for this issue needs work. The change is definitely not unfrozen. Refer to https://www.drupal.org/core/beta-changes#unfrozen for what is unfrozen:
This issue is a task that introduces a BC break, and per https://www.drupal.org/core/beta-changes, we should postpone this BC break to 9.x, or potentially to 8.1.x if a BC layer is provided. I think we could do as @Berdir suggests and simply deprecate it and remove internal usage, so postponing it to 8.1.x for that.
We can only proceed with this for 8.0.x if someone can provide clear justification as to how it fits within the allowable changes for the beta.
I also don't think it's major -- how does it have significant impact? See the examples of major tasks: https://www.drupal.org/core/issue-priority#major-task So setting to normal. Introducing an API change does not make an issue major by itself, I don't think.
Comment #32
xjmAlso, as far as I can tell, @Mile23 RTBCed his own patch, and @Crell and @JeroenT just restored that status. @Mile23, you can't RTBC your own patch. :) Someone else does a code review and then sets it RTBC if they think it's ready.
Comment #33
Mile23If I'm working on it, this not-really-an-API-change is needed before #2311219: Fix hook_user_format_name_alter() documentation and stop referring to user_format_name() can happen.
If someone else wants to step in, they're welcome.
That's because he's wrong, and because there are other fundamental problems with the way the User entity gets its name. See #16. https://www.drupal.org/node/2450793#comment-9897065
Removing this code- and cognitive-complexity is a reasonable goal, as is making a clear API for core and contrib. The @deprecated is not a lie. The reason this is a separate issue from the @deprecated one is because it changes the API for the better in one way, and opens a discussion about how to change the API for the better in others.
Comment #34
xjmIf #2311219: Fix hook_user_format_name_alter() documentation and stop referring to user_format_name() is impossible, then the function is indeed not really deprecated and what we should do is remove the deprecation until it can be deprecated properly, and then re-deprecate it for 9.0.
I agree that removing complex, problematic code is definitely a reasonable goal, and one we should do. However, that doesn't mean it's in scope to do during the beta phase. Please review: https://www.drupal.org/core/beta-changes I think we could start on this by deprecating it for 9.0 now and then removing usages in core in 8.1.x. That's why I postponed the issue, versus marking it wontfix or something.
Also, I don't think @Berdir is wrong; I think he's correct. He's also an Entity API maintainer.
I'll leave this at review for more feedback for a couple days, but I will re-postpone it after that otherwise. I removed the line about the change being unfrozen from the summary, because it's not unfrozen.
Can we file a separate issue to mark the label callback and
user_format_name()
as deprecated for 9.0?Comment #35
xjmOh, I should also point out that it's not true that
label_callback
has no documented behavior. It actually is documented, inEntityTypeInterface::getKeys()
. The documentation is confusing, and hard to discover, and not in the right place, but it is documented. So I'm increasingly firmly convinced that we cannot remove this in 8.x. There could easily be sites and modules out there already relying on this documented, tested callback, even if it's confusing, bad DX, and redundant at this point.What we should do in 8.0.x in addition to deprecating it is add
@todo
everywhere it is used referencing the issue to remove it (this one), to further discourage contrib from using it.Comment #36
cilefen CreditAttribution: cilefen commentedRegarding #34: #2495301: Deprecate user_format_name() and the label_callback for 9.x (not 8.x)
Comment #37
xjmThanks @cilefen for getting that filed, and thanks @Mile23 for the work on this and the additional feedback.
Re-scoping and postponing to 8.1.x per #31, #34, and #35 in my capacity as release manager. The actual removal will need to happen in 9.x, but we will deprecate it now in #2495301: Deprecate user_format_name() and the label_callback for 9.x (not 8.x) and convert uses after the beta and RC phases.
Please consult me or another release manager if you think this should be re-evaluated (rather than changing the issue statuses). Thanks!
Comment #38
TR CreditAttribution: TR commentedI really don't get why this has to be postponed.
Issues like #2451395: drupal_get_schema()/drupal_get_complete_schema() no longer work as expected; remove them are being committed daily - that particular one broke one of my modules this week because a core Drupal API function was removed. You signed off on that one @xjm.
This current issue has little chance of breaking anything. There is no core usage of this, and there is no loss in functionality here - the entity type label may be changed in better ways.
I feel there's a double standard in the core issue queue. On the one hand, patches which come from some sources are approved even if they are disruptive (some cause MAJOR disruption), and are many times committed without change notices and without fixing the documentation. On the other hand, many small, non-disruptive changes get pushed off years into the future, effectively ensuring they will never get committed.
Oh, and I also have to quibble with the statement:
First, I said "the formal parameters of this function are almost completely undocumented" (emphasis added), and I gave a link to the one reference I found. Your example and my example don't show up in an API documentation search (you have to grep core to find them...), and neither specific nor general entity annotation keys like label_callback have their own documentation.
Second, in your example (the documentation for EntityTypeInterface::getKeys()), you will see that label_callback is mentioned only in the description of the returned array key "label". It says:
It makes no mention that label_callback is an annotation, it doesn't mention that label_callback holds a procedural function name, it doesn't say what the function parameters or return values are, and it refers the reader to "the $label_callback block" which doesn't appear in the documentation either.
So I think my categorization of label_callback as "almost completely undocumented" is well-justified. And since it's almost completely undocumented, I find it hard to believe that there is much code that relies on this. Anyone who has figured out how to use label_callback in their own code must be more than capable enough to deal with the change, especially since the change and the recommended alternatives will be documented in a change record.
Summary:
label_callback is useless cruft, removing it will cause only minor (if any) disruption, but postponing removal will only make it harder to get rid of in the future and will drag this issue out potentially for years. In the meantime, people may waste time documenting it and people may err by using it in their modules once 8.0 is finalized, and it will continue to block the issues @Mile23 raised.
So why the pushback? Who's arguing for keeping it?
Comment #39
tim.plunkettNo one wants to keep it. But the schema issue was major, and this is normal.
Comment #42
joachim CreditAttribution: joachim as a volunteer commentedThis actually is documented in D7: https://api.drupal.org/api/drupal/modules%21system%21system.api.php/func... but the docs must have been removed at some point even though the functionality hasn't been!
Comment #48
BerdirWe should also add a CR here and @trigger_error() make sure it is no longer called. Note we either can't add it on getLabelCallback() itself or we have to get the property directly, as we still have to support the concept, so the @trigger_error() needs to happen when someone defines that property, not when the method is used. (or as mentioned, we work around it by calling get('label_callback')
Comment #49
andypostOr we could undrprecate it because no other way yo customize label per bundle
Comment #50
Berdir@andypost: I think you're mixing that up with the (completely broken) url callback, I don't see any indication of a per-bundle support for label callbacks in the label() methods. That said, we have entity types with things like per-plugin callbacks, so they can always decide to support whatever they wan't, it's just not generic.
I also think we should just drop support for that uri callbacks too, it just doesn't play well with the everything else like url templates and routes. People can use aliases to have nice URL's, which would also work out of the box in pathauto if it wouldn't have to try and support the broken system around forum urls.
Comment #51
BerdirHere's what I proposed. Doing a @trigger_error() in getLabelCallback() only if one is actually defined and including which entity type it is. Changed User to just override label() instead. Renamed UserEntityLabelCallbackTest but to rename Callback otherwise left it as-is.
Kept the other usages for now to see the deprecation in action.
Comment #53
andypost@Berdir yep, mixed things - why only has() deprecated?
Comment #54
BerdirNot sure what you mean?
Also, looks like this needs to be postponed on the not-yet-existing issue in jsonapi to remove its incorrect usage of getLabelCallback(). See #3042745: Remove group @legacy from jsonapi tests and fix deprecation messages
Comment #55
BerdirAlso, fun facts, there are two other entity types with a label callback:
* A test entity type that is only used for a single test due to its long table name, the label callback that it defines doesn't exist, so apparently nothing calls label() on that.
* RestResource, which defines the label callback as a method (without class name, so that wouldn't work), but again, nothing seems to call label().
Both would just silently do nothing in HEAD as we are doing an is_callable() check on the value. But they would trigger the deprecation mesage.
Comment #56
BerdirSlightly different approach, fully deprecating all these methods and instead using get() and adding the @trigger_error() for that directly in the label() implementations.
Comment #57
BerdirFor the jsonapi usage, I'm just removing the pseudo-generic implementation with a check for user entities and created #3057175: Implementation of user name in JSON:API can result in overwriting data because I think that implementation is actually incorrect anyway.
Comment #60
Wim LeersThanks for pushing this forward, and for creating #3057175: Implementation of user name in JSON:API can result in overwriting data!
I reviewed this, and it's looking super close to ready. We still need to fix the remaining test failures of course. Here's an initial patch for that.
Comment #61
mikelutzComment #62
Berdir> I reviewed this, and it's looking super close to ready. We still need to fix the remaining test failures of course. Here's an initial patch for that.
Looks that wasn't just the initial patch, now we need someone else to RTBC it I suppose, since you approved everything else and specifically the change to jsonapi, that should be just about the test changes and the usual deprecation nitpicking, I noticed we still use will be removed from wording.
Comment #63
mikelutzComment #64
mikelutzFunctionally, I believe this approach is correct and fine, and ready to go. I'm setting to NW because the deprecation messages aren't up to standards, and the required @group legacy tests for each deprecation error are missing.
Specifically:
These are okay, They meet the relaxed format of %thing% is deprecated in %deprecation-version% (free text describing what will happen) %removal-version%. %extra-info%. See %cr-link%
These should match and be in the format
%thing% is deprecated in %deprecation-version% and is removed from %removal-version%. %extra-info%. See %cr-link%
The method also needs an @group legacy test to test for the deprecation error.
same
Same
@deprecated in drupal:8.0.0 and is removed from drupal:9.0.0 etc.
The annotation should match the error message
These @group legacy tests generate a dynamic deprecation message. The test class should use Drupal\Tests\Traits\ExpectDeprecationTrait and the test method should call
to test for the deprecation.
Comment #65
BerdirThanks for the review.
Updated the deprecation messages and added legacy tests.
Comment #66
mikelutzTests and messages look good. One small nit. I'll leave NR for now.
We add a BC layer here and trigger an error. Should we have a follow-up against 9.0.x and an @todo or other in-code documentation to remove this when the drupal 9 branch opens?
Comment #67
BerdirHm, not sure what our policy is in regards to that. We have tons of deprecations without an explicit follow-up referenced in code I think, and everything that has a @trigger_error() should be easy to find :)
Personally, I don't think having a todo referenced in the patch would help much, I could just create an issue and reference it from here if you think that would be useful?
Comment #68
mikelutzI think that would be useful. I don't think we have an official policy in regards to in-code documentation of BC-layers, but I think we should. I opened #3069389: [Policy, no patch] Determine and document best practices for providing backwards compatibility code paths expected to be removed in the next major version. a few days ago, but no input yet.
I just wanted to point it out, I think we are going to have a rough time finding and removing all the BC layers without consistant in-code documentation, but it's too late now. I'm hopeful we can have a policy to start using in Drupal 9 though.
Everything in the patch itself looks fine, all feedback is addressed. I'm happy to RTBC.
Comment #69
Wim LeersLooks great! RTBC++
I didn't know in #60 whether my quick fix would solve all failures, looks like it did 😀
Comment #71
catchCommitted ffe6995 and pushed to 8.8.x. Thanks!
Comment #73
larowlanThe change notice for this has
To change the label of an existing entity type, create a module and have it implement hook_entity_type_alter():
Is that correct? Should it instead talk about overriding the entity class with a custom label implementation?
Comment #74
BerdirThere are two change records, both draft. One is from 2015 and yes, that is not correct.
Mine, while not very elaborate, is better. Published that now.
And yes, doing that for an entity type that is not controlled by you would require to replace the entity class, but that is a rare edge case.. I hope?