Problem/Motivation
Discovered in #3272732: Drupal 10 & CKEditor 5 readiness, while porting Entity Embed.
In #3272732: Drupal 10 & CKEditor 5 readiness, I recommended using a deriver instead of a hook that alters CKEditor 5 plugin definitions to de facto derive multiple plugins from a single base definition.
But it looks that due to \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::getDiscovery()
overriding \Drupal\Core\Plugin\DefaultPluginManager::getDiscovery()
, we do not get the default derivative support 🙈
Steps to reproduce
Specify a deriver, observe it does not get called!
Proposed resolution
Add support for derivers.
Remaining tasks
TestsFix- Review
User interface changes
None.
API changes
None.
Data model changes
None.
Release notes snippet
TBD
Comment | File | Size | Author |
---|---|---|---|
#43 | 3313473-43.patch | 28.72 KB | Wim Leers |
| |||
#43 | interdiff.txt | 7.05 KB | Wim Leers |
#42 | 3313473-42.patch | 28.35 KB | Wim Leers |
| |||
#42 | interdiff.txt | 3.84 KB | Wim Leers |
#40 | 3313473-40.patch | 28.46 KB | Wim Leers |
|
Comments
Comment #3
Wim LeersOops 🤣
Crediting @balintpekker! :)
Comment #4
Wim LeersComment #5
Wim LeersPHPStorm's automatic reformatting caused that … 😬
Comment #6
Wim LeersAnd more…
Comment #7
Wim LeersI forgot how painfully long this takes. Let's run only CKE5's test suite.
Comment #10
Wim LeersExpand test coverage.
Also: run only CKE5's kernel tests, not all.
Comment #12
Wim LeersAnd … fix!
CR also created: https://www.drupal.org/node/3313821
Comment #13
Wim LeersStricter test coverage.
Ready for review now!
Comment #14
Wim LeersLast but not least: test coverage to prove that this works fine for PHP class annotation-based plugin definitions too!
Comment #15
Wim LeersPointing out some key changes to help review this:
This is the key change, but changes were necessary elsewhere too: in the actual production code to A) allow defining a deriver, in the test infrastructure B) allow this to be tested thoroughly.
This extracts a helper method from
::testInvalidPluginDefinitions()
, so that it can be reused in the new test method.This is a necessary addition to allow
\Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery::getDefinitions()
to find PHP class annotation-based plugin definitions.Comment #16
Wim LeersRun all tests again.
Comment #18
Wim Leers→ definitely an unrelated failure 🙃
Comment #20
Wim LeersEh …
Apparently #3165010: Using the layout builder discard changes button should ignore any input and skip validation temporarily broke HEAD, it's been fixed. Re-testing again.
Comment #21
smustgrave CreditAttribution: smustgrave at Mobomo commented+1 for RTBC. Code looks good to me but would like a 2nd opinion too.
Comment #22
mglamanSome nits. I didn't pull down to fully read. But the plugin constructor changes seem very different than other code. But that's probably because CKE5 plugins are very different.
nit: it's technically already null. I think this would only be needed if we used typed properties.
"the time that" that what?
isn't it PHP code and not YAML?
Comment #23
Wim LeersThanks for the review!
NULL
as a valid value, as well as anystring
. Hence also@var string|null
, unlike for example forprotected $label
, which is NULL by default but is required.This is used by
\Drupal\Component\Annotation\Doctrine\DocParser::collectAnnotationMetadata()
, which interprets@var
.Precedents:
\Drupal\media\Annotation\MediaSource::$thumbnail_alt_metadata_attribute
and\Drupal\media\Annotation\MediaSource::$thumbnail_title_metadata_attribute
.$additional_files
. Same thing for the pre-existingproviderTestInvalidPluginDefinitions()
+testInvalidPluginDefinitions()
.Comment #24
Wim LeersPaired with @balintpekker on #3272732: Drupal 10 & CKEditor 5 readiness, to figure out why he couldn't get his deriver to work. Turns out he was returning an array as the CKEditor 5 plugin definition in his deriver, instead of a
CKEditor5PluginDefinition
instance.The deriver infrastructure introduced here is working perfectly fine for that issue now (see https://git.drupalcode.org/project/entity_embed/-/merge_requests/12/diff...) … except that this clearly points to a low-hanging fruit DX improvement! 🍎
So let's do that :)
All we need to fix that is apply the same approach as
\Drupal\Core\Layout\LayoutPluginManager::processDefinition
🤓Comment #26
Wim LeersThe
1) Drupal\Tests\datetime\Functional\DateTimeWidgetTest::testDateonlyDefaultValue
failure is 1000% unrelated — I wonder if this is some DST thing? 🤯Comment #27
Wim Leers@berdir just pointed me to #2993165: Date Only field shows incorrect default value when UTC date is different than user's date, which seems related?
Comment #28
Wim Leers#26 was for the
10.1.x
result.On the
10.0.x
test result, we get:… yet another random JS test fail… created #3317378: [random test failure] Drupal\Tests\media_library\FunctionalJavascript\WidgetViewsTest::testWidgetViews random fail.
Comment #29
smustgrave CreditAttribution: smustgrave at Mobomo commentedGoing off the 10.1.x pass
Looks like the changes in #22 were address or answered.
RTBC to me!
Excited to see ckeditor5 come along so much in the last few months!
Comment #30
mglamanInteresting. Usually the Plugin API system expects/assumes derivers return an array of definitions, not the object definitions themselves. And that's an entirely different discussion than this. The fixes look good to me 👍
Comment #31
Wim Leers#30: that's the usual, yes, but not a requirement.
LayoutPluginManager
does the same.Basically, by not using blobs/arrays of doom but typed objects, we can offer a vastly better DX.
Comment #32
tim.plunkettI tried and failed to find another way to accomplish this. Ideally it would have been handled by the deriver itself, but I can't think of a way to do that. This is a safe and reasonable approach, so +1 for RTBC
Comment #33
Wim Leers@effulgentsia asked in chat:
CKEditor 5 plugins are not allowed to use periods or colons in their plugin ID, because otherwise it becomes impossible to define config schema for them. This was done and enforced in #3215689: Rename CKEditor 5 plugin IDs to guarantee all plugins can be made configurable, where
\Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition::__construct()
got this:This proves that the actually used plugin ID is not
{baseID}:{baseID}_{suffix}
but{baseID}_{suffix}
— otherwise tests would've been failing. (Although to be fair, explicit checks that disallow.
and:
would be even better.)The reason the
{baseID}:{baseID}_{suffix}
plugin ID exists at all is simply because it's the unused array key generated by\Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator::encodePluginId()
in\Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator::getDerivatives()
— they're really just arbitrary array keys to prevent conflict. The source of truth is in the generated derivative itself.You can see this in:
These array keys are irrelevant/arbitrary, the
'id'
key-value pairs matter, because they will be returned by\Drupal\Component\Plugin\Definition\PluginDefinitionInterface::id()
.Several other derivers in core set an explicit derived plugin ID. IOW: it looks like this "plugin IDs can get out of sync" thing is an oversight in the plugin system. And it looks like this has been known for a while:
The only solution until those 2 issues are fixed would be to create a tweaked
ContainerDerivativeDiscoveryDecorator
, which overridesencodePluginId()
. I'd rather see the plugin system get improved.P.S.: once again unrelated random failures in #24 too, opened an issue for them: #3317515: Random fail in Drupal\Tests\quickedit\FunctionalJavascript\CKEditor5IntegrationTest::testArticleNode() on 9.4 and 9.5.
Comment #34
Wim Leers— @effulgentsia in chat.
WELL DAMN! Will have to address that then…
Comment #35
Wim LeersThis is way better, thanks so much @effulgentsia! 🤩
Comment #36
Wim LeersRe-testing #35 on 9.5 — the only failure was fixed by #3317515-12: Random fail in Drupal\Tests\quickedit\FunctionalJavascript\CKEditor5IntegrationTest::testArticleNode() on 9.4 and 9.5, which got committed in the past 😄
There is a legitimate
9.4
failure — but we can deal with that later, let's first get this into the 3 most important branches 🤞Comment #38
tim.plunkettThis seems unnecessary when you consider how
\Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator::encodePluginId()
is used. Idk that it makes sense for the plugin definition constructor to have any of this logic (for that matter, I'm not sure I like the pre-existing code in here either)The recent changes are good, I'm just unsure if it's far enough
Comment #39
effulgentsia CreditAttribution: effulgentsia at Acquia commentedRight, I think related to #38, the problem is in:
getDerivativeDefinitions()
should return an array of arrays, not an array of objects. Turning the definition array into an object should only happen inCKEditor5Plugin::get()
, afterDerivativeDiscoveryDecorator
has merged the derivative definition with the base definition. That way, the deriver doesn't need to deal with IDs at all (other than as the key for the array returned bygetDerivativeDefinitions()
), and the ID generated byDerivativeDiscoveryDecorator::encodePluginID()
is what ends up getting into the merged$definition
array passed toCKEditor5PluginDefinition::__construct()
.Comment #40
Wim Leers#38:
But
encodePluginId()
only sets the array key, it doesn't actually update the plugin definition. That's exactly the bug that #2458769: Derivative plugin definitions contain base plugin IDs instead of derivative IDs still needs to still solve. I'm happy to add a@todo
for that? Done.This statement of yours made things "click" for me! 😄
I've moved all of this logic into
\Drupal\ckeditor5\Plugin\CKEditor5PluginManager::processDefinition()
and … sure enough, that works fine! It even hugely simplifies the code you were concerned about (and I was too!) in #32 👍#39:
-1 — the
$base_plugin_definition
it receives is already a\Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition
instance. This is unavoidable because\Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator::getDefinitions()
explicitly calls<Plugin>::get()
, and\Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator::getDerivatives()
runs after that.That means that
is never executed for CKEditor 5 plugin definitions. Nor do I actually even understand when that would ever get executed.
(Note that
\Drupal\Core\Plugin\DefaultPluginManager::processDefinition()
has a similar note about array-based definitions vs others.)(Same thing for Layout plugins btw, see/put a breakpoint in
\Drupal\Core\Layout\Annotation\Layout::get()
and run\Drupal\Tests\Core\Layout\LayoutPluginManagerTest::testProcessDefinition()
for example. For them too, that cited code block is never executed.)(In fact, the only way I was able to get that code to execute is by running
\Drupal\KernelTests\Core\Plugin\DerivativeTest::testDerivativeDecorator
, and in doing so I discovered it merges the definitions incorrectly: the derivative's label is overwritten by the base definition's label …)IOW: #38 has been addressed, #39 has not, because I don't see how I could 😅
Comment #41
tim.plunkettI *LOVE* this new constructor. I'm so so glad we ended up resolving my original concerns after I gave up on them :D :D
I think this needs to point to CKEditor5PluginManager::processDefinition() now
I think s/derivative/id would be clearer, making both of the subsequent lines
This sort of resolves #39, no? Since the deriver doesn't have to do anything extra anymore
RTBC after the nits
Comment #42
Wim Leers#41.1: INDEED! 😄
Excellent nits, addressed them 😊
Comment #43
Wim LeersCKEditor5PluginDefinition
instances in the provider, which both simplifies the assertions and makes them easier to understandDrupal\Component\DependencyInjection\ContainerInterface
was introduced by #2531564: Fix leaky and brittle container serialization solution. We don't need that in this particular test helper, we can rely on its parent,Symfony\Component\DependencyInjection\ContainerInterface
.Comment #44
Wim LeersLooks like #43 will be green on all 4 branches 😊👍
Change record updated to match the current state of the patch 👍
Comment #45
Wim LeersActually, there's one more thing I think this should do: throw an exception just like
\Drupal\Core\Config\TypedConfigManager::alterDefinitions()
does when it detects an "alter hook" implementation adding new CKEditor 5 plugins — that should be forbidden.Thoughts, @tim.plunkett?
Comment #46
tim.plunkett#45 I think that changing hook_ckeditor5_plugin_info_alter like that should be in a follow-up.
Interdiffs in #42 and #43 look great, the whole patch is looking very clean.
RTBC!
Comment #47
Wim Leers#46: agreed 👍
Follow-up created: #3318353: hook_ckeditor5_plugin_info_alter() implementations that add a plugin should not be allowed.
Comment #48
effulgentsia CreditAttribution: effulgentsia at Acquia commentedSaving issue credits.
Comment #52
effulgentsia CreditAttribution: effulgentsia at Acquia commented#43 looks great. I pushed this to 10.1.x, cherry picked down to 9.5.x, and published the CR with 9.5.0-rc1 as the initial version.
Leaving this at RTBC for potential cherry picking to 9.4.x, but I'll check with a release manager first. Normally, I would consider this to be out of scope for a patch release, but CKE5 is experimental in Drupal 9.4, which makes this potentially in scope, and it would help contrib modules that want to use this functionality not need a separate branch or code path to handle 9.5/10.0 and 9.4.
Comment #53
alexpottWe've already backported many API changes to Ckeditor5 to Drupal 9.4.x.
Comment #54
catchYes I think backport is fine here.
Comment #56
alexpottDid the cherry-pick as it preserves the commit authorship :)
Comment #57
alexpottComment #58
Wim LeersWonderful, thank you so much! 😊 This means #3272732: Drupal 10 & CKEditor 5 readiness is unblocked! 👍