Problem/Motivation
Entity types without bundles or with BundlePlugin based bundles are not supported. This blocks Drupal Canvas and its Canvas Page entity type along with others.
Original
I have a custom entity type with no bundles. For what I've seen, SchedulerAdminForm needs a bundle entity type to work.
Is it possible to implement a plugin for this case ?
Steps to reproduce
For exemple, generate a new entity type with drupal console or manually, with no bundles.
Try to create a new plugin for this entity type.
Proposed resolution
1. Make plugin discovery bundle-optional.
2. Add a non-bundle settings source (entity-type-level Scheduler config).
3. Branch runtime logic in `SchedulerManager` for bundled vs no-bundle entities.
4. Add an admin UI path for no-bundle entity type Scheduler settings.
5. Add test coverage with `entity_test_no_bundle`.
| Comment | File | Size | Author |
|---|
Issue fork scheduler-3355087
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 #2
apatel0325 commentedComment #3
apatel0325 commented@rkcreation Can you please try below?
// Define your entity class.
class MyEntity extends ContentEntityBase {
// Define your entity properties and fields.
}
// Define your entity type using the ContentEntityType class.
$contentEntityType = ContentEntityType::create([
'id' => 'my_entity',
'label' => 'My Entity',
'entity_class' => MyEntity::class,
'base_table' => 'my_entity',
// Define your entity's fields and storage settings here.
]);
// Implement hook_entity_type_build() to register your entity type with Drupal.
function mymodule_entity_type_build(&$entityTypes) {
$entityTypes['my_entity'] = $contentEntityType;
}
// Define a custom entity form for your entity.
function mymodule_entity_form_display(\Drupal\Core\Entity\EntityInterface $entity, $mode, $form, &$form_state) {
if ($entity->getEntityTypeId() == 'my_entity') {
// Define your custom form display here.
}
}
// Define any additional functionality for your entity.
function mymodule_my_entity_validate(MyEntity $entity) {
// Define your custom validation logic here.
}
Comment #4
pacproduct commentedI am facing the same issue, but I'm not trying to create a custom entity myself: I wanted to use the 'ad' module which defines an 'ad_content' entity with no bundle.
I tried declaring a Scheduler plugin (with the idea to eventually suggest it as an addition to the 'ad' module), but after several tries and some xdebug sessions, I realized that the scheduler module explicitly excludes entity types that do not have any bundle (in
\Drupal\scheduler\SchedulerPluginManager::findDefinitions):My Plugin definition gets unset right there.
Is there a workaround for enabling scheduling on contrib entities with no bundles?
Comment #5
jonathan1055 commentedHi rkcreation and pacproduct,
Thanks for giving your feedback here, it is useful. Currently, as you discovered, Scheduler does expect a bundle on the entity type. Scheduler 8.x-1.x only supported nodes and everything was hardcoded. This was expanded in 2.x with the entity plugin, but we did not build in from the start the possibility of using the plugin on entities without a bundle type, because it was built as a logical follow-on from nodes. Everything, all options and processing, is currently structured around entity bundle types, and there is no quick work-around.
I would be very interested to see how much work is involved in getting Scheduler to work on non-bundle entity types. It could be quite complex, or maybe it could be straight-forward if we expanded the plugin manager with new functions. My feeling is that it will be a lot of work, but that is not a reason to discard the idea.
apatel0325 - regarding your post in #2-3 above, I don't understand what you were trying to show. I think you must have mis-understood what this issue is about.
Comment #6
mglamanI took a closer look at current `2.x-dev` and confirmed this is still a structural limitation, not just a missing plugin example.
Scheduler currently assumes bundle-backed entity types in several places:
So there is no practical workaround today for no-bundle entities (for example, `entity_test_no_bundle`) without Scheduler changes.
A feasible implementation path appears to be:
1. Make plugin discovery bundle-optional.
2. Add a non-bundle settings source (entity-type-level Scheduler config).
3. Branch runtime logic in `SchedulerManager` for bundled vs no-bundle entities.
4. Add an admin UI path for no-bundle entity type Scheduler settings.
5. Add test coverage with `entity_test_no_bundle`.
Small code examples
1) Current hard stop in plugin discovery
2) Current third-party settings lookup assumes bundle entity
3) Example no-bundle entity from core tests
4) Sketch: make discovery bundle-optional
5) Sketch: branch settings lookup for no-bundle entities
Comment #7
mglamanComment #8
mglamanComment #9
mglamanComment #11
kristen polphpcs failure
FILE: README.md
------------------------------------------------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
------------------------------------------------------------------------------------------------------------
94 | WARNING | Line exceeds 80 characters; contains 82 characters (Drupal.Files.TxtFileLineLength.TooLong)
------------------------------------------------------------------------------------------------------------
Comment #12
kristen polComment #13
kristen polPipeline failures
Comment #14
mglamanMR is green again
Comment #15
scott falconer commentedFound one blocking issue in my testing:
The new SchedulerNoBundleSettingsForm submit path can fatal when the entity type has a form mode registered via hook_entity_form_mode_info_alter() without a backing core.entity_form_mode.* config entity.
Steps to recreate:
- POST /admin/config/content/scheduler/entity_test_no_bundle
- Result: HTTP 500
- Watchdog:
Error: Call to a member function getConfigDependencyName() on null in Drupal\Core\Entity\EntityDisplayBase->calculateDependencies()
- Stack points back to SchedulerNoBundleSettingsForm.php around the form display save loop.
While this may not happen in vanilla paths, since this issue is about broadening Scheduler to support custom/contrib non-bundle entity types, this edge case may occur and the result is a 500 / site that is left partially configured.
Likely cause:
SchedulerNoBundleSettingsForm::submitForm() loops over modes from getFormModes() and saves an EntityFormDisplay for each one. For alter-only form modes, Core cannot load the corresponding entity_form_mode config entity during dependency calculation, so saving the display fatals.
One additional concern:
The config save happens before the form-display loop, so the failed submit can leave Scheduler partially configured even though the user sees an error page.
Disclosure: AI was used to evaluate and test the code, which was then manually verified.
Comment #16
mglamanaddressed #15
Comment #17
kristen polI've requested Rakhi at this to the sprint.
Comment #18
rakhimandhania commentedComment #19
scott falconer commentedI retested the current MR head. Looks good moving to rtbc.
Validation:
- Reviewed the current diff against 2.x.
- Confirmed the follow-up commit addresses the prior blocker by skipping alter-only form modes without backing entity_form_mode config, and by saving Scheduler config after the form-display loop.
- Ran SchedulerNoBundleSupportTest locally: 2 tests, 15 assertions, pass. Existing deprecations only.
- Ran a browser smoke against the no-bundle settings route and saved /admin/config/content/scheduler/entity_test_no_bundle successfully.
- Confirmed saved config and default form display state include the Scheduler components.
- Watchdog did not show the prior fatal. Only expected page-not-found noise from checking that the bundle-backed Scheduler route is rejected.
Disclosure: AI was used to help review and test this, and I reviewed the output/results before posting.
Comment #20
scott falconer commentedComment #21
kristen polYay! This is a stable blocker for ai_context
Who can we nudge to get this in and get a release created?
Comment #22
kristen polOh! @jonathan1055 was here recently
🙏🙏 🙏🙏 🙏🙏
Let’s us know how we can help get this over the line
UPDATED: I pinged @jonathan1055 in the contribute channel
Comment #23
jonathan1055 commentedThis is excellent work, thank you. I've left a few comments in the MR, and @mglaman has already addressed some. I've not completed the review yet, but adding comments as and when I get time to do a bit more.
One request I have is with the Tugboat Preview. Currently this installs the additional modules Commerce and Media, and sets up scheduling on those. Could you extend it so that we can test a non-bundle entity in Tugboat? This could use the
EntityTestNoBundleSchedulerentity and plugin, or we could implement a plugin for a proper core non-bundled entity, as that would be a real-life useful example.Comment #24
mglamanhttps://git.drupalcode.org/issue/scheduler-3355087/-/commit/a39229bd50ee... tries to get this working on Tugboat :) GitLab queues look backed up though, so the commit isn't in the MR yet.
Comment #25
jonathan1055 commentedThank you. Yes that works in Tugboat. I have some feedback about the new form, see attached screen shots, easy things to change. I can do this if you like, just say.
I also have questions about structure and navigation, the other scheduler-enabled entities have a view to show the items and the scheduled items. These are missing here, but that is probably due to this just being a test entity created specifically. Other non-bundle entities would have a url to get to them, and edit them. I will push a trial implementation for a Block plugin, then we can see how it works for a real entity.
Another major omission at the moment is actual test coverage for the Scheduler operations on non-bundled entities. We really need to add a non-bundled entity into dataStandardEntityTypes() within the test
SchedulerSetupTrait. Maybe we could use the Block plugin for that, so I will push that new file and then we'll go from there.But overall this is excellent.
Comment #26
mglamanTy jonathan1055 for feedback. I'm afk today but will address on Monday
Comment #27
jonathan1055 commentedRegarding Content Blocks, I think the other issue is mis-leading, or I confused myself, as these are bundled. Now that I've got the fields correct in the new plugin, we get all the Block Types showing in the dropdown menu. See attached. So I will remove the block plugin work here, and leave it for the other issue.
Comment #28
jonathan1055 commentedMy feedback about the visual appearance of the form is probably due to your recent work extracting the common parts of
_scheduler_entity_type_form_alter()andSchedulerNoBundleSettingsForm::buildForm. I'm guilty of causing that, as I asked for that re-factoring. So for now, let's not worry about the form appearance, but concentrate on adding a non-bundled entity into the set of Scheduler functional tests. That's more important than how the form looks.Comment #29
mglamanOh goodness the MR is finally green! Going to review it and see if we can clean up the appearance again.
Comment #30
kristen polI just want to acknowledge the huge amount of work Matt and Jonathan have been doing on this issue! Thank you both 🙏🙏🙏
Comment #31
mglamanCI ✅ (including previous major)
Tugboat ✅
UI adjustments from #25 addressed ✅
Comment #32
mglamanComment #33
scott falconer commentedI reviewed the current MR head again and this looks RTBC to me.
What I checked:
- The original no-bundle settings form submit failure is covered by SchedulerNoBundleSupportTest::testSubmitFormSkipsPhantomFormMode().
- The current MR uses a Scheduler-owned no-bundle publishable test entity
The no-bundle functional slices now cover the relevant default-time handling / hidden/disabled scheduler field behavior / X-Robots/meta output / delete-with-past-dates behavior
I also re-tested the real admin form path in a DDEV browser session against the current MR head:
- /admin/config/content/scheduler/scheduler_test_no_bundle loaded
- enabling publishing/unpublishing and saving completed without the previous fatal
- the form returned to the settings page with “Scheduler settings saved.”
- saved config has publish_enable: true and unpublish_enable: true
- no watchdog errors
Comment #34
kristen polYay! 🎉 Hope this can get in soon 🤞
Comment #35
jonathan1055 commentedI have read through all the changes since the last review on 8 May. Great work Matt.
I like that you removed the dependency on drupal:entity_test I've pushed three commits, minor tweaks, no functional changes.
I want to ask though in in this commit file
tests/modules/scheduler_no_bundle_test/src/Plugin/Scheduler/EntityTestNoBundleScheduler.phpthe class name should really be SchedulerTestNoBundleScheduler because EntityTestNoBundleScheduler is a left-over from when you were using the core EntityTest. Is it OK if that is changed? I can push that, but wanted to get your view first.Also I notice that the Non-Bundle test entity is not used in the javascript tests, but the non-bundle Trait is added to SchedulerJavascriptTestBase.php in this commit Was there an intention to use it in the Javascript tests?
Comment #36
mglaman@jonathan1055 both addressed, pushed.
Class rename. Done.
EntityTestNoBundleScheduler->SchedulerTestNoBundleScheduler. The pluginidandentityTypewere alreadyscheduler_test_no_bundle_*, so only the class and file changed.No-bundle in the JavaScript tests. Reverted. The JS tests configure Scheduler per entity type through bundle third-party settings:
For a no-bundle type,
entityTypeObject()returns theContentEntityTypedefinition, which has nosetThirdPartySetting(), hence the fatal. No-bundle settings live in config (entityTypeNoBundleConfig()), not on a bundle. There is also no JavaScript-specific edit-form behavior unique to no-bundle entities to assert here; the vertical-tab and default-time widgets are the same code path. Coverage stays in the Functional and Kernel tests.I kept
SchedulerNoBundleEntitySetupTraitonSchedulerJavascriptTestBase. It is not there to add JS coverage,SchedulerSetupTraitdispatches the no-bundle type to methods that the trait defines:Without the trait on the class, PHPStan can't resolve
createEntityTestEntity()/getEntityTestEntity()in that context. Added a comment on theuseto make that explicit.Comment #37
phenaproximaThis is a critical issue to land for Drupal CMS, because it blocks us from getting to Drupal 11.4 unless we shim something.
Comment #38
jonathan1055 commented@mglaman Thanks for renaming the class, that's good.
Yes after pushing the addition of the non-budle test data to the javascript tests I spent a short while running them locally, and discovered exactly what you have explained, that
$entityType->setThirdPartySetting()will not work here, or it would take quite a bit of effort to change. I wanted to see how the default time worked. But I have tested that manually and I think it does OK. I had to use the manual urlscheduler_test_no_bundle/addbecause there is no navigation to add this entity. I also saved without entering a title and got wsod withwhich isn't very user-friendly if you click 'save' to early. There is no way back either, because you also get that error when trying to edit. But I guess this is due to the test entity not being a "real" non-bundle entity, it is lacking in several features. Also, for example, the scheluder settings have the radio button choice of fieldset or vertical tab, but this entity does not support vertical tabs on the end page. Again I expect if we were using a "real" non-bundle entity this would be better. I'm just stating these things here, in case we need a follow-up issue to address them.
Let's move forward. I'll merge it now.
Comment #39
jonathan1055 commentedI have just tagged and released Scheduler 2.3.0
Thank you to @mglaman, @kirsten-pol and @scott-falconer for making this major feature possible.
Comment #41
kristen polJonathan and Matt, thank you!!! 🙏
Comment #42
pfrenssenVery nice work, thanks very much Jonathan and Matt!