Problem/Motivation
A follow-up of #3543495: Adopt Entity API for state mgmt, part 1: Façades only and #3543952: Adopt Entity API for state mgmt, part 2: Move logic.
An Instance entity is is made of collections of steps (Drupal\display_builder\HistoryStep):
- past: many steps
- present: single step
- future: many steps
- saved: single step
The step is holding the "business data" of the instance:
public array $data,
public readonly int $hash,
public readonly FormattableMarkup|string|null $log,
public readonly int $time,
public readonly ?int $user,
This is not drupally because the instance entity is doing an revisionable entity storage job here. It would be better to merge HistoryStep in the instance root and use the Drupal's entity revision API to navigate in between revisions.
Because RevisionableInterface is following a similar logic than TranslatableInterface, this will also help the future work about #3555110: Symmetric translation
Proposal, part 1: Convert data to content entity fields
Moved to its own ticket: #3577791: Adopt Field API for Instance entity data
Proposal, part 2: Alter the logic
Tasks:
- Implements
RevisionLogInterfacewith help ofRevisionLogEntityTrait - Move
HistoryStepproperties to Instance properties and removeHistoryStepclass - Move the past/present/future logics to the Enttiy Revision API: When we switch revisions, we reload the instance entity with the revision properties.
- Remove
HistoryInterface - Maybe more...
Details:
| Current property | Proposal | Rev. | Trans. |
id
| Stays a string base field overriding the one from ContentEntityBase
| No | No |
profileId
| Stays a string base field
| No | No |
present->data
| Becomes a map base field
| Yes | Yes |
present->hash
| Becomes a integer base field (or revision from ContentEntityBase?)
| Yes | No |
present->log
| Becomes revision_log_message from RevisionLogEntityTrait
| Yes | No |
present->time
| Becomes revision_created from RevisionLogEntityTrait
| Yes | No |
present->user
| Becomes revision_user from RevisionLogEntityTrait
| Yes | No |
save
| ??? | ??? | ??? |
contexts
| Stays a custom map base field
| No | No |
past
| (Will be managed by the Revision API) | ||
future
| (Will be managed by the Revision API) |
In this part, let's try to not change the storage data structure. InstanceStorage will convert the data.
Proposal, part 3: Future proofing
This is the moment we can think about breaking stuff. It is OK in my humble opinion because instance entities are volatile. We are commited on storage stability for config entities and "real" content entities. Ans we can do a schema update hook if necessary.
Do we remove the custom InstanceStorage and adopt Core's SqlContentEntityStorage ?
This will simplify the code and allow us to have a cleaner data model:
| Base field | Proposal | Rev. | Trans. |
profileId
| From string to a entity_reference ?
| No | No |
data
| From map to a UI Patterns 2 source?)?
| Yes | Yes |
We need to test the performance diff between the 2 storage.
| Comment | File | Size | Author |
|---|
Issue fork display_builder-3562989
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 #3
pdureau commentedWork has started. Interface added but implementations not complete yet.
Comment #4
pdureau commentedAs the author of the issue, I am proposing to move this task after beta2.
Comment #5
pdureau commentedBack to beta2 scope because may be needed for #3570382: Fatal: Compatibility with JSON API
Rebased.
Comment #6
pdureau commentedComment #7
pdureau commentedSince beta2, we have a fake, temporary, implementation of
ContentEntityInterface, so ofRevisionableInterface, inDrupal\display_builder\Entity\ContentEntityBaseWe are still hesitating:
\Drupal\Core\Entity\ContentEntityBaseas a base class and adapt the custom logic inInstanceandInstanceStorageonly ?Anyway, the addition of this interface may be interesting for the current work.
Comment #8
pdureau commentedComment #9
pdureau commentedMoved to beta4 so I can focus on #3549266: Move DisplayBuildableInterface to a new plugin type
Comment #10
pdureau commentedSince beta3, Instance extends the real
\Drupal\Core\Entity\ContentEntityBaseandInstanceStorageextends\Drupal\Core\Entity\ContentEntityStorageBase.That means most of the logic is already implemented, we just must target the specific part to override:
RevisionableInterface on Instance entity:
isNewRevision() : bool;>> we can keep ContentEntityBase implementationsetNewRevision($value = TRUE) : bool;>>getRevisionId() : int|null|string;>> we can keep ContentEntityBase implementationgetLoadedRevisionId(): int;>> we can keep ContentEntityBase implementationisDefaultRevision(bool $new_value = NULL): bool;wasDefaultRevision(): bool;>> we can keep ContentEntityBase implementationisLatestRevision(): bool;>> call storage::getLatestRevisionId(), we can keep ContentEntityBase implementationpreSaveRevision(EntityStorageInterface $storage, \stdClass $record);RevisionableStorageInterface on InstanceStorage handler:
createRevision(RevisionableInterface $entity, $default = TRUE): RevisionableInterface;loadRevision($revision_id): RevisionableInterface;>> call toloadMultipleRevisions(), we can keep ContentEntityStorageBase implementationloadMultipleRevisions(array $revision_ids): array;loadRevisionUnchanged($revision_id) : ?EntityInterface;>> call toloadRevision()deleteRevision($revision_id);getLatestRevisionId($entity_id);Comment #11
pdureau commentedI will give a try
Instance::$idstringbase field to override the one fromContentEntityBaseInstance::$labelInstance::label()Instance::$profileIdstringbase field (or better as aentity_reference?)Instance::$present->datamap(or the UI Patterns 2source?) base fieldInstance::$present->hashintegerbase field (orrevisionfromContentEntityBase?)Instance::$present->logrevision_log_messagebase field fromRevisionLogEntityTraitInstance::$present->timerevision_createdbase field fromRevisionLogEntityTraitInstance::$present->userrevision_userbase field fromRevisionLogEntityTraitInstance::$saveInstance::$contextsmapbase fieldInstance::$past)
Instance::$futureIn this MR, let's try to not change the storage data structure.
InstanceStoragewill convert the data. We can create a follow-up later if wished.Comment #12
pdureau commentedComment #13
pdureau commentedComment #14
pdureau commentedPart 1 is nearly done, only some phpunit faiils to fix in
LayoutBuilderConfigMigrationTest::testLayoutBuilderConfigImport()Comment #15
pdureau commentedComment #16
pdureau commentedhttps://www.lullabot.com/articles/basics-drupal-revisions-and-content-mo...
We need a similar mechanism.
Comment #17
pdureau commentedComment #18
pdureau commentedRemaining work:
hasSave(), saveIsCurrent(),setSave(),restore()Comment #19
pdureau commentedComment #20
pdureau commentedThis issue is now rebasing #3578224: Add ::getPast() & ::getFuture() to HistoryInterface which is rebasing #3577791: Adopt Field API for Instance entity data which is rebasing
1.0.0;)Comment #21
pdureau commentedThis issue is now rebasing #3578469: Make instance entities aware of display buildables which is rebasing 1.0.0 ;)
Comment #22
pdureau commentedChained issues: This issue is now rebasing #3578469: Make instance entities aware of display buildables which is rebasing #3579299: Tidy context management which is rebasing
1.0.xComment #23
pdureau commentedRemaining work:
InstanceHistoryTest&InstancePublishingTestNotes
1. This was dropped from
Instance::setNewPresent():Is it something we want to keep?
2. Is it possible to avoid the manual reload of the instance entity after undo/redo i have added to
ApiControllerandInstanceHistoryTest?Comment #24
pdureau commentedRelated UI Patterns issue: #3584856: Source field model and storage
We can do without, but it will be better to have it soon (so the storage is safer and more solid)
Comment #26
pdureau commentedNearly finished.
2 MR:
Both have the same playwright fails.
Comment #27
pdureau commentedOK, the pipeline is finally OK on both branches:
2 last stuff to check (which are not caught by phpunit and playwright) before sending to review:
LogsPanelto the wrong revision. We will do a follow-up of #3579299: Tidy context managementAlso, a follow-up issue will be created to propose the addition of the computed hash in the permanent storages (which will also allow some simplification of LogsPanel)
Comment #29
pdureau commented3562989-without-profileId is green and ready to review.
This MR is also embedding #3579299: Tidy context management and #3578469: Make instance entities aware of display buildables, whcih can be reviewed together or separately.
3 follow-up ticket will be created after merge:
\Drupal\display_builder_entity_view\EventSubscriber\DisplayBuilderSubscriber: no need to listenON_REVERTbecause we have already the instance entity to manipulate inApiPublishingController::revert()Comment #30
pdureau commentedComment #31
pdureau commentedBoth Florent (with SSE) and Christian (with Translations) struggle because of the srong caching of the context API.
I take the ticket back and try a little something.
Comment #33
pdureau commentedA new branch has been pushed: https://git.drupalcode.org/project/display_builder/-/merge_requests/272
With the replacement of a field storing the display buildable contexts
By a field storing the the display buildable config:
Unfortunately, it doesn't solve Florent's issue with SSE, but I believe it makes the architecture clearer and the work easier.
Comment #34
pdureau commentedPhpunit fails. I check
Comment #35
pdureau commentedPipeline is green
Comment #37
pdureau commentedThe last pipeline run ("WIP" commit) is a mistake and was testing something else. Please don't consider it. The pipeline is still green.
Comment #38
pdureau commentedBecause the issue is not merge yet, I am allowing myself to propose 4 tiny last-minute changes. Jean, what do you think about them?
1. Add the metadata needed by #3555110: Symmetric translation
and:
So, we alter the storage only once. The SQL schema will not change when we will merge #3555110: Symmetric translation.
2. Remove the useless
bundlecolThe entity type is not "bundlable", why is it present in some tables?Edit: its mandatory with
SqlContentEntityStorage: #2163735: Remove the 'bundle' column from the field schema3. Make table naming more consistent
Sometimes we have

display_builder, sometimesdisplay_builder_instance:4. Rename
datafield bysources?Maybe data is too generic.
Comment #39
christian.wiedemann commentedI checked this MR intensive during my work on translations.
Comment #40
pdureau commentedAlso, it would be nice to add
no_uito src/Plugin/Field/FieldType/PluginItem.phpComment #41
pdureau commentedRebased, and new commit added with:
no_uiin src/Plugin/Field/FieldType/PluginItem.phpdatafield renamedsourcesThe pipeline may be red because of phpstan:
This is also happenign in main branch and not related to the work in this MR.
Comment #42
pdureau commentedDiscussed with Jean (@mogtofu33), we need an update process.
Proposal 1
We stay on branch 1.x and we add a hook update with 2 steps:
Proposal 2
We propose the user to:
For this, Jean suggest to open a 2.x branch.
Comment #43
pdureau commentedHi Jean,
Proposal 1
Done in a commit pushed in the MR: https://git.drupalcode.org/project/display_builder/-/merge_requests/272/...
Proposal 2
Switch at the first position:
Switch at the second position:
Switch at the third position:
Am I doing something wrong?
Comment #44
pdureau commented#3584856: Source field model and storage which is making the sources field type more robust for our use, has been merged in UI Patterns 2.0.x branch
Comment #46
mogtofu33 commentedDone rebase, looking good.
One issue when creating a page layout and letting the 'page source' wrapper, nothing is displayed when saved.
And issue with the instances list, not populated until an instance is published, looking to refactor the InstanceListBuilder.
Comment #47
pdureau commentedWith UI Suite Bootstrap ? It happens sometimes already, even with the main Display Builder branch (1.0.x) according to Florent (@grimreaper).
Weird, I didn't notice that during my tests. A cache issue?
Comment #48
mogtofu33 commentedOk so let have an other issue for page layout.
I fixed the list.
But I struggle with rebase for `EntityViewOverride::__construct` related to 283, could you have a look?
Comment #49
pdureau commentedSure, I will open a new MR (#288) with:
Comment #51
mogtofu33 commentedThe issue on instance list is:
Problem is an instance not from storage will fail to get buildable, you can test on a site with existing instances, delete all of them (from code in post_update).
Comment #52
pdureau commentedHey Jean,
I have proposed a
InstanceListBuilderfix by altering the logic ofDisplayBuildableInterface::collectInstances()implementations, moving away from Instance ID string parsing to rely more of the existing logic of the buildable plugin.This is the direction we are moving to, in my opinion, less and less magic around those strings which are becoming more opaque.
I have also removed some of the EntityListBuilder overrides you suggested.
There are more ambitious changes to do, more from your suggestions and some I would like to propose, but I have preferred to keep this additional commit small, and move this work to #3573905: Simplify DisplayBuildableInterface
Issue with
ui_patternsdev branch.If you test my proposal with entity view overrides and ui_patterns-2.0.x-dev, you may encounter a fatal error, recently introduced at UI Patterns (because
NULLmain property breaks entity queries). You can apply this patch to test properly:It will be fixed with #3548884: SourceValueItem field synchronized translations. You can merge this MR if you are OK with my proposal, because this bug is not related to the current change, it will appear with or without this ticket to be merged.
Comment #53
pdureau commentedComment #54
mogtofu33 commented2 issues on my side:
Locally testing from the recipe (ie: install with page, entity and views display) from 1.0.x, install ok
Update ok (added the missing display_builder.install)
But then, error on list:
Looking deeper the entity_view display are here, but the page_layout and views display are null:
Drupal\display_builder_page_layout\Plugin\display_builder\Buildable\PageLayout::collectInstances get
$buildable->getInstance()as null.Idem views. Looks for pageLayout problem with getInstanceId return null when entity->isNew (which is the case here) and then
DisplayBuildablePluginBase::getInstancereturn null if instanceId is null...Not sure why it works for entity_view...
If instances are loaded 'manually' then it's ok.
Comment #55
pdureau commentedOops, i may have messed up with my squash. I will fix that and investigate.
Comment #56
pdureau commentedGood news, i reproduce the error.
Install:
✅ /admin/structure/display-builder/instances is OK (with the instances provided by the test modules)
Update:
❌ Error: Call to a member function label() on null in InstanceListBuilder.php
Maybe because it is the only one where I didn't forger to execute
$buildable->initInstanceIfMissing():)So, I have 2 options:
::initInstanceIfMissing()calls::initInstanceIfMissing()calls and move the logic to::getInstance()Comment #57
mogtofu33 commentedI prefer the safe fast. They are loaded only once and rarely deleted in a normal build usage.
Comment #58
pdureau commentedI chose the safe path :)
The pipeline is greener than green
Comment #61
mogtofu33 commented