Closed (fixed)
Project:
Drupal core
Version:
8.5.x-dev
Component:
entity system
Priority:
Major
Category:
Task
Assigned:
Unassigned
Issue tags:
Reporter:
Created:
8 Dec 2017 at 18:18 UTC
Updated:
3 Jan 2018 at 18:39 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
plachHere's an initial patch, I'll work on the docs next, if none beats me to it.
Comment #3
plachComment #4
plachHere is the additional documentation. This moves the existing section before any CRUD operation description, since it's a general overview.
Comment #5
plachWrong interdiff, sorry.
Comment #6
plachComment #7
timmillwoodA couple of small points, otherwise looks good.
I'm not sure this sentence reads well. You could make changes to a default revision and create a new revision, but only if the new revision becomes the default. You could also make a change to a pending revision without creating a new revision.
Not sure we need this new line.
Comment #8
plachDiscussed wording at length with Tim and Nat in Slack. We decided to just replace "can" with "may" :D
The interdiff does not show it but also the the empty new line is gone.
Comment #9
timmillwood+1
Comment #10
plachI realized that we were missing a few interfaces to complete the symmetry between translatable and revisionable entity types.
Comment #11
plachCR at https://www.drupal.org/node/2930241
Comment #12
hchonovNot important, but probably we could exchange the sentence by simply using @see tag to both interfaces.
the first part of the sentence reference to "fields" in plural, but the second one is using a singular form, which is somehow confusing.
I think it is worth also mentioning that the default translations is the translation in which the entity has been initially created before it has been translated.
We can instantiate translation objects from any entity translation object not only from the default one :).
What does mean that a translation is identified by something? I think this sentence isn't needed.
Hmm I would say that the canonical version or actually any version is loaded initially in the default translation.
Oh, I wasn't aware of this. Where are we doing this? :)
Comment #13
plach1: Not sure what you mean :)
2: Mmh, "that is" is equivalent to "i.e." in this case. I think it's correct English but it would be good to have confirmation from a native speaker.
3: Given that we support changing the default language, the values may be the same of the initial ones, but technically the translation would not be the same in that case. It's a tricky area I'd like to leave out, given that it doesn't add much to what we already have IMO.
4: Sure, but we always start from the default one. Again, I'm not sure it's worth complicating this description, given that this information is self-evident, once you realize all translations share the same class.
5: I was just trying to introduce the concept of "active language", which is a key bit. Maybe now it's better?
6: Fixed
7: Well, if you load the entity with all field translation values, and you change them only in one language, when you save a new revision all field translations are stored, including the unchanged ones, so you get a copy for all untouched languages. It's the default entity storage behavior. Maybe the description is confusing?
Comment #15
plachThis should hopefully fix all test failures.
Comment #16
plachUpdated the IS with the description of the API changes.
Comment #17
plachComment #18
plachMinor clean-up
Comment #19
hchonov1. I mean
instead of
2. ok :).
3. Ok, I agree.
4. Ok, I agree.
5. I think that it is better now. Thank you.
6. Thank you.
7. Hmm I am not sure, but there is something I don't really understand. Sorry about this.
By reading
I don't get it how a non-default revision could be edited (we don't have a built-in UI for this)?
Comment #20
clairedesbois@gmail.com@hchonov For the point 7: You can modify the last revision (which isn't the default revision) with content moderation. But yes, the sentence is a little confuse, it give the impress that we can translate (understand initialize the translation) with a non-default revision. I don't think it's the case even with content moderation
(after verification, yes, content moderation create a new translation from the default revision of the source language which is the published revision if the content have pending revisions)
Comment #21
plachThat sentence is trying to the convey the idea that from the built-in UI only the default revision can be modified without triggering the creation of a new revision (this is not supported by Content Moderation, which always triggers a new revision on save). Hence that's the only case when you can get a revision that affects multiple translations (at least via the UI).
Comment #22
plachI hope this reads better.
Comment #23
plachMore IS clean-up
Comment #24
timmillwoodLooks good to me, but I think @hchonov should RTBC based on his recent reviews.
Comment #25
hchonovI've needed some time until I've realized what this exactly means...
So the idea here is that we could subsequently edit the default entity revision without saving a new revision while each edit is performed on a different entity translation. In this case the revision translation affected flag will be set for each of the edited entity translations in the default entity revision.
I think it would be much better to understand for others if this is somehow mentioned here.
I would suggest something like this:
However, if the default revision is being modified subsequently for different entity translations without creating a new revision when saving each the translations, multiple translations can be changed and will be flagged as affected.Comment #26
hchonovOh and I think we have to document that editing untranslatable fields results into "affecting" all the entity translations.
Comment #27
plachThis should address the comments above.
Comment #28
hchonovThank you. It looks much better now :).
Comment #29
amateescu commentedIs there any reason for not using the new interface?
We are in the
Drupal\Core\Entitynamespace so it's not like we're expecting typed data objects here..Small nitpick here: Some fields ... that are shared ...
However, there's something about how this sentence is connected which doesn't sound right. How about something like this:
"Some fields may be defined as untranslatable, which means that their values are shared among all translations."
Comment #30
andypostIs there any reason for empty interfaces?
And why all content defined revisionable?
Comment #32
timmillwoodDrupalCI fail, so back to RTBC.
Comment #33
plach@amateescu:
1: Strictly speaking, replacing
\Drupal\Core\TypeData\TranslatableInterfacewith\Drupal\Core\Entity\TranslatableInterfacein thoseinstanceofchecks is a BC break, because we are narrowing the set of supported implementers. In practice, I agree it's very unlikely anything will change, as we will always be dealing with content entities. However it's still an unneeded BC break: I'd avoid pissing the release managers with those, given that we are already proposing one :)2: It was meant to be equivalent to "i.e.", but your suggestion looks better anyway.
@andypost:
That's a placeholder for the method introduced in #2926483: Add API methods for determining whether an entity object is the latest (translation-affecting) revision.
All content entities have always supported revisions at API level, since the related methods belong to
ContentEntityInterface/ContentEntityStorageInterfaceprior to this patch, we are just making it more explicit. Actually the definition of a content entity is "a fieldable, revisionable, and translatable" entity type, at least at the moment.Comment #34
catchCouple of minor questions, leaving RTBC for now:
Is this because of actual conflicts or just clarity? If the latter shouldn't the namespace be enough?
Is this just for clarity or is there an actual conflict?
Also wondering if there are any other implications to entities being able to implement two things called translatable interface.
On the one hand this says 'can keep', on the other it says the full history is stored.
Not sure if we should assume that revisions are being kept (in which case change 'can keep' to 'keeps'?) or add the caveats elsewhere.
Is it worth pointing out that the 'default translation' is usually the original language the entity was added in - i.e. sometimes the source language rather than a translation?
Comment #35
plach@catch:
1: There was an actual conflict with
EntityViewBuilder: since we have aTranslatableInterfacein\Drupal\Core\Entity, i.e. the same namespace of EVB, it was not possible to use another interface with a conflicting name (see #10). For consistency, I decided to alias the other two occurrences.2: Well, the difference will be that code using the type data version in type hints won't get the
::hasTranslationChanges()method. This is already the case when preferringTranslatableInterfaceoverContentEntityInterface. I don't think there will be other issues because conflicts can only happen in the\Drupal\Core\Entitynamespace, which core owns.3: Fixed
4: Fixed. This was brought up also by @hchonov in #12.3: I tried to avoid describing the language change scenario (see #13.3) by adding "typically".
Comment #37
plachTestbot issue
Comment #38
effulgentsia commentedI think this patch looks great. Regarding the BC break:
The issue summary and change record identify this as a potential API change, but I think are incorrect about what the BC break actually is. I don't think that the change of return value type is a BC break at all, because all current callers pass a ContentEntityInterface object (per HEAD's API) and therefore even after the patch, they'll continue to get back a ContentEntityInterface object.
However, the change to the parameter typehint is a BC break for all classes that override this method. The CR says "Implementers extending ContentEntityStorageBase have nothing to do", but that's only true if they don't override this method. If they do extend the base class and override the method, then they do have something to do: they must change the typehint in the overridden method. https://www.drupal.org/core/d8-bc-policy#interfaces is currently unclear as to whether such a change is allowed.
Given the above BC break and the lack of clarity in the current BC policy, I agree that release manager signoff is needed. Tagging it for that.
Comment #39
plachThanks, @effulgentsia!
The point is that if your code strictly relies on a
ContentEntityInterfacebeing returned, once we perform this change it may also get an instance ofTranslatableInterfacenot extendingContentEntityInterfaceand thus break. OTOH I pointed out in the IS why I think this is an highly unlikely scenario.Good point! I had mentioned a similar case in the IS but forgot to update the CR to reflect that. Updated both.
Comment #40
plachComment #41
wim leers😱🎉👏🙏
"version" here is very confusing.
s/data/revisions/
"version" here is NOT confusing.
"translation objects"?
Aren't those just "entity objects"?
This one sentence. I'd have begged for this one sentence a long time ago.
Do translations contain revisions, or revisions contain translations?
Now it's clear. Thank you!
Glad to see this documented clearly as well now. Very important for the API-First Initiative, when it starts to support revisions and translations!
Pending revisions aren't supposed to affect multiple translations.
That means it's impossible to translate a new revision in all translations ahead of time, and keep it all in sync?
This is what I added a while ago based on Gábor's insight. The docs you're adding are 10x more detailed and therefore 10x more useful :)
Comment #42
catchMy understanding is that it means a single pending revision should only make changes to one translation. However a second pending revision could then be created with changes to a different translation. Subsequently when that second revision is saved again as the default, the default will have changes to both translations - but until that moment only one translation has been 'affected' at a time.
I feel like the the createTranslation change isn't something we can get away with - we allow interface additions because it's guaranteed you don't need to do anything when using the base class (except for the usually theoretical possibility of already declaring a method with the same name), but changing a method that subclasses might have implemented seems a lot more likely. If we don't allow it, then we need to figure out how to handle it though (add another method?).
Comment #43
effulgentsia commentedHow about
TranslatableInterface::createNewTranslation()? It's a bit redundant, since "create" implies "new", but I don't have a better idea at the moment. While we're on the topic, are we sure that it's always "new"? The docs say it is ("Constructs a new entity translation object"), butContentEntityStorageBase::createTranslation()creates it by calling$translation = $entity->getTranslation($langcode);, and looking at that code path, I'm not entirely clear if there are cases where an existing translation gets reused.Comment #44
plach@catch:
Actually the
::createRevision()method introduced in #2924724: Add an API to create a new revision correctly handling multilingual pending revisions merges the specified entity translation object with all the other translations available in the default revision. This ensures you can create a pending revision for each language and eventually save them as default revisions without affecting the other languages. But, yes, the key concept is that you can work on translations independently, as long as every pending revision deals with a single translation.In general I'd agree with this line of thinking. However, In this specific case I felt bullish about proposing this change because
ContentEntityStorageBase::createTranslation()has exactly one usage in core: it's invoked byContentEntityBase::addTranslation()and has no explicit test coverage because all actual usages go through the latter. I'm pretty confident this is the same for the contrib and custom spaces, but I can do some research if needed.If this is still deemed not viable I'm happy with the proposed workaround of adding a new method with a different name and leave the old one in place as a wrapper.
Comment #45
plachThis is what a rather comprehensive (more than 4600 modules) grep on the contrib space yields:
http://grep.xnddx.ru/search?text=createTranslation%28&filename=
No usage at all :)
Comment #46
plachThis just addresses #41, I'm waiting for feedback on #44 and #45 before getting rid of the API change.
@effulgentsia, #43:
::createTranslation()supports existing translations to be able to add a new translation, even if it was previously removed without saving the entity in between (i.e. restore it), so I think::createNewTranslation()still respects the intended usage.However, I think I slightly changed my mind about introducing a new method right now. The goal of this issue is cleaning-up the revision translation API (and the related docs). If we add a slightly misnamed (or inconsistent) method just to preserve BC, we are not really cleaning up things, instead we are adding cruft we won't be able to get rid of in D9, because of the continuous upgrade policy.
TBH this is the main doubt I have around continuous upgrade: it doesn't let us clean up things properly because in many cases the good names will be already taken by the stuff we are deprecating, which will result in a less than optimal DX. I've been thinking about this for a while, and I don't have a good solution to suggest at the moment. Probably it would be a good idea to open a policy issue to discuss this topic a bit more and figure out how this is being handled in other projects or whether there are consolidated good practices to handle such cases.
Comment #47
plachThe patch for reals
Comment #48
wim leers#47 addresses my docs clarity concerns that I raised in #41.
Not RTBC'ing simply because the discussion I spawned with #41.8 doesn't seem to have reached a conclusion yet. I personally don't think that needs to be clarified/solved in this issue. This issue already presents a massive leap forward. I also feel less qualified to decide whether that should be in-scope or not.
As far as I'm concerned: RTBC again.
Comment #49
plachDiscussed this with @catch: we agreed to split the API change to a follow-up issue to be discussed separately. Here is an updated patch.
This is the follow-up: #2932049: Change TranslatableStorageInterface::createTranslation() to accept TranslatableInterface.
Comment #50
plachForgot a bit
Comment #52
catchLooks good to me without that change, we can figure out how/if to do it in the follow-up.
Comment #54
plachTestbot-- :(
Comment #55
plachBack to RTBC per #48 and #52.
Comment #56
effulgentsia commentedThanks! Adding credit to reviewers.
Comment #59
effulgentsia commentedI have some reservations about the word "variant". But I'm not able to think of a better word to use at the moment. In any case, any further docs improvements could easily be done in a followup.
Therefore, pushed to 8.5.x!
Comment #60
plachThanks! Published the CR.