Parent issue
#2024083: [META] Improve the extension system (modules, profiles, themes)
Problem/Motivation
The DX of ModuleHandler or any other place that reads or works with module/theme/profile data is horrible. We need some kind of container through which we can easily access information about extensions, namely themes, modules and possibly profiles too.
Proposed resolution
Introduce classed objects for modules, themes and profiles to increase DX and maintainability of our code. We should be able to create a shared ExtensionInterface which would expect basic methods like ->getVersion(), ->getDescription(), etc.
I believe that this would make module/theme-related code much more readable and less error prone. Also, we would get autocompletion in our IDEs for reading the relevant .info.yml properties.
Detailed concept TBD.
| Comment | File | Size | Author |
|---|---|---|---|
| #82 | 2024043-82.patch | 5.44 KB | Janvi Dasani |
| #77 | 2024043-77-interdiff.txt | 734 bytes | kim.pepper |
| #77 | 2024043-77.patch | 13 KB | kim.pepper |
Issue fork drupal-2024043
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 #1
ParisLiakos commentedtagging
Comment #2
ParisLiakos commentedhow about sth like that? i ll start adding some usage in core in a bit
Comment #3
ParisLiakos commentedlets
Comment #5
ParisLiakos commentedhmm, two fixes..
Comment #6
mile23This problem looks ready-made for Symfony's ParameterBag. Not sure if it's verboten in D8.
But here's a patch anyway.
I kept all the interfaces, but just made Module, Profile, and Theme into subclasses of ExtensionBase, itself a subclass of ParameterBag. This makes it (almost) trivially easy to manage the parameter arrays.
With tests.
Comment #7
fubhy commentedRe-titling. This should really happen.
Comment #8
dawehnerThis feels wrong, just saying.
There is no profile interface, any reason for that?
Additional the constructors on the different Extensions needs docs.
The namespace in the test and the Contain statement disagree.
The use statements in the test is not needed.
Comment #9
fubhy commentedYeah, I also don't think ParameterBag is the right thing to do here. It's an array collection, aye, but not the right one for the job. Maybe better extend from the Doctrine ArrayCollection.
Comment #10
mile23ArrayCollection looks better, with iterators and map() and filter().
RE: ProfileInterface... I think I left it out because I wasn't sure how it was currently implemented, and how different it would be from a module. Plus it's not in the issue title.
If the whole point of this exercise is to make an abstract container class and special-case containers for modules, profiles, and themes so some manager class can deal with them, then yes. That's what we'll do, with ArrayCollections.
Also, just linking to what you've obviously seen: #2024083: [META] Improve the extension system (modules, profiles, themes)
Comment #11
fubhy commentedYeah, that's the META (parent) issue of this one ;) (also linked in the issue summary).
Comment #12
mile23doh. :-)
Comment #13
mile23OK, here we go. Full hierarchy of interfaces, and built with ArrayCollection, and probably too many tests. :-)
There's one question though... Maybe arrays of dependencies should be other ExtensionInterface objects, or maybe not. I'm not quite sure how this is going to be generated or consumed, so for now it assumes that dependencies will be arrays of strings, as you'd get when you parse a YAML .info file.
Comment #14
fubhy commentedI removed the tests (really, we don't need any, these are just getters... it's an array collection, nothing else, no logic). And removed the whole checkType() / getType() thing (which removed all the logic, hence no tests) because, well, we have interfaces... So there is no point in checking types.
This is a super simple patch now that simply provides objects that we will start using in follow-up issues.
Comment #15
fubhy commentedWhoops, had an unrelated change in there.
Comment #16
msonnabaum commentedI really really like this issue. I wanted to do the exact same thing after working on #2021959: Refactor module handling responsibilities out of DrupalKernel.
This ModuleHandlerFactory issue isn't really dependent on this though, so committing it could possibly wait until we have usage, but it certainly wouldnt hurt to do this preemptively if it makes that patch easier.
Comment #17
dawehnerThis basically bounds our module system to doctrine ... are we sure we want to go into that direction?
Comment #18
fubhy commentedIt is just a collection object. Swapping that out is not a big deal.
Comment #19
sdboyer commenteddocs nit: "extension's", not "extensions's".
i would have guessed this gets the image data of the screenshot itself, but it seems it actually gets the path TO the screenshot. right?
i also think this is fine; we depend on libraries, that's why we bring them in. plus,
ArrayCollectionis really a pretty generic datastructure, so it's no big thing.what is a little awkward here, though, is that these objects are straddling data that is read-only in a php context (information extracted from info yamls) and rw data (e.g., installation/active state). it's not that big of a deal, but it's worth noting.
Comment #20
mile23Good point. There will be a time when installation and active state info will be unknown.
Comment #21
fubhy commentedwhat is a little awkward here, though, is that these objects are straddling data that is read-only in a php context (information extracted from info yamls) and rw data (e.g., installation/active state). it's not that big of a deal, but it's worth noting.
The installation status is also read-only. The only way to change it will be to use ModuleInstaller which will cause a recompilation of the container and only subsequently change that data. We are looking at a pure, read-only object (which should or should not be enforced, I don't know...).
Comment #22
sdboyer commentedyeah, that's a good point - the source may be different, but the data should be equally inviolate. OK so, the requirements really do dictate that the object should be read-only, which suggests state is set and frozen in the constructor. in that case, ArrayCollection is not necessarily the best thing to subclass from; we need the subset of methods that are read-only.
unfortunately, that raises the bar on what objects need to be present in order to correctly construct one of these. kinda a weird chicken or egg thing, since ModuleHandler is supposed to be the canonical point of record on what modules are enabled...right?
Comment #23
msonnabaum commentedArrayCollection should just be removed for now. I like the idea of it, but it's clearly a distraction here.
Comment #24
mile23So: What should ExtensionInterface encapsulate?
Comment #25
fubhy commentedYeah... You even predicted that ;)
The question now is... Do we need a raw get() method too? I know that some themes and modules come up with custom .info file contents. We need to support that.
Anyways, we should still implement ArrayAccess to ease adoption / conversion. I wonder if we should, in this patch, already convert _system_module_rebuild_data() to use these objects to check if that works?
Comment #26
fubhy commentedNote: I want to kill system_rebuild_module_data() entirely, but that's a follow-up to this one (@see #14)
Comment #27
msonnabaum commentedIf we truly need to support contrib-added parameters to .info.yml, then yeah, a get() method seems appropriate. I'd just make sure we need it for each extension type before adding it. For example, if it's just a theme thing, let's only do it there.
Comment #27.0
msonnabaum commentedUpdated issue summary.
Comment #28
xjmComment #29
xjmComment #30
dawehnerJust a rerole + one usecase. Should this patch also patch all places like the entity info one?
Comment #32
dawehner30: extension-2024043.patch queued for re-testing.
Comment #34
dawehnerBack to green.
Comment #37
pcambraPlain reroll
Comment #39
dawehnerAt that point in time we kinda dislike interfaces for those kind of value objects. Ideally we would rather have just the value objects to indicate that those objects.
Comment #46
alexpott#39 very much in favour of that. Value objects are exactly what we want here. We don't want alternate implementation of Modules in contrib or custom. The extension system is complicated enough as it is. #3015812: Introduce new Theme extension object and properly deprecate REGIONS_VISIBLE and REGIONS_ALL begins to implement this for themes.
Comment #47
andypostComment #48
tim.plunkettAgreed
Comment #49
andypostRe-roll, removal of interfaces, making classes final.
Looks ExtensionBase no longer needed because there's Extension object which has mostly all methods.
Diggin extension discovery...
Comment #51
larowlanComment #52
larowlanI thought comments above indicated we were dropping the interface?
Module also has requires/required_by and sort values - see \Drupal\Core\Extension\ModuleHandler::buildModuleDependencies
Comment #53
larowlanPerhaps we should pursue #2972143: Create \Drupal\Component\Utility\ArrayObject instead of using the doctrine array collection?
Comment #54
kim.pepperFixes for #52. Also some linting issues.
Re: #53 do we really want our own implementation for this? Seems super low-level.
Comment #57
fgmJust spent some time working around theses questions without finding this issue initially.
As mentioned in #52, module Extension instances carry these undeclared properties:
This issue avoids declaring them on Extension because theme and theme_engine extensions don't have them, but now that #2937955: Themes can declare dependencies on modules is in, the required[_by] fields can also exist on themes, and be empty on theme_engines, while profiles are modules in everything but name and load order.
AIUI the proposed patch changes the API, so it needs to be moved beyond 9.1, but could we just start by adding these four fields on the existing Extension class, as this doesn't break any API and adds a minimum of documentation and discoverability to these fields ?
Comment #62
andypostnow database drivers are extensions
it will notice on PHP 8.2
Comment #63
andypostRunning test on PHP 8.2 brings lots of deprecations because properties are missing
Comment #64
kim.pepperAs per #57 I propose a new issue to create those methods on the extension class first, and postpone this on that issue.Update: I think we should continue working on this issue.
Comment #65
kim.pepperCreated #3292759: Create getters and setters for dynamic Extension properties
Comment #66
kim.pepperI've combined this with our own ArrayObject implementation in #2972143: Create \Drupal\Component\Utility\ArrayObject and added fixes for #62
Comment #67
wim leersRe-testing #66, because I cannot reproduce those failures.
Comment #69
pradhumanjain2311 commentedPatch #66 applied cleanly for version 10.0.x.
Comment #70
andypostQueued last patch for PHP 8.2-rc5 & MySQL 8
and here's a patch test the sate
Comment #71
kim.pepperI assume these are random test fails in HEAD, as these are new objects and not getting used anywhere yet.
Comment #72
andypostThey are not random - 2794 failed https://www.drupal.org/pift-ci-job/2510630
Comment #73
andypostre-queued patch #66 again
Comment #74
kim.pepperAh yes, the php 8.2 fails are real.
Comment #75
andypostHaving the same name as SPL class is confusing and DX-- on reading code, please rename
Ref https://www.php.net/manual/en/class.arrayobject.php
I think it needs to be split because nearly the same is happening in #3275858: View's ResultRow uses deprecated dynamic properties which is much simple then new extension value objects for extensions
Moreover it could be applied to plugins - views has a lot dynamic properties so would be great to reuse same approach
wondered why there's no libraries
copy/paste
Comment #76
kim.pepperJust to be clear, these value objects aren't meant to replace
\Drupal\Core\Extension\Extension. That class is much more to do with loading extensions with ExtensionDiscovery. It might make sense to rename it to ExtensionFile (or similar)?These value objects are for 'Extension Info' which is currently just an array that gets passed around for modules, themes, engines, and profiles.
I don't think we need our own
ArrayObjectafter all. We can just use SPL\ArrayObjectfor the abstract base class.Also since we are representing extension info, I renamed everything to include 'Info', eg.
\Drupal\Core\Extension\ThemeInfoI provided a usage example for ThemeInfo for feedback. I didn't include an interdiff since it was pretty basic and I re-wrote most of it.
Comment #77
kim.pepperFix spelling error.
Comment #79
andypostSomething is slightly broken
Undefined array key "dependencies"and others all over https://dispatcher.drupalci.org/job/drupal_patches/154004/Comment #80
kim.pepperYeah, this is because we removed the
"dependencies"array key in favour of using a property andgetDependencies().However, we need to have a BC layer, so I guess that means supporting both but triggering a deprecation error if array access is used. We can use
Drupal\Component\Utility\DeprecatedArrayfor this.When I do, I get hundreds of instances where this is getting called. Not sure how to break this down into more bite-sized chunks.
Comment #81
andypostI think we can use
DeprecatedArrayand silence deprecations as contrib also can have tons of usagesComment #82
Janvi Dasani commentedAdded patch against #77 in 10.1.x
Comment #84
donquixote commentedI don't like the mixing of array access and regular object methods.
If we need to support custom properties, I would rather want to see
$info->get($key)than$info[$key], if$infois not an array.Is there any good reason for it?
BC support where we previously used arrays?
It would not really work because we already break BC by not providing the basic keys like version etc.
I think for issues like this we should have two PRs or two branches:
In the end we only merge the first MR, and split out the work from the second branch into follow-up issues.
Comment #85
donquixote commentedAlso, the issue summary and title are out of date.
The original issue asked to introduce extension objects.
Now we have them.
So the remaining problem seems to be that we are not happy with the current Extension class.
Which is fine, but it seems this should be addressed in a new issue where we clearly summarize what is wrong with the current Extension class, and what are the main use cases where we want to improve DX.
Or at least update the summary of this one.
----
On the topic:
I have the impression that different functions/methods/classes actually need different types of extension objects:
At different points in the application and the bootstrap process we actually have different parts of this info available.
And depending where and when you get your Extension object from, some of its properties might be filled at that time, or not.
E.g.:
This maybe works ok for now, but it makes for messy dependency chain, and prevents proper refactoring.
It would be cleaner to use different classes, or at least diference instances, in different places of the application.
These classes can be immutable, or at least be treated as such most of the time, and only contain the properties that are needed and available in the context in which they are used and provided:
This would allow to refactor the respective classes, and make a clean chain of dependencies, where the dependent layers don't "pollute" the objects that they obtain from a source.
There are different ways to do this in a BC-friendly way. E.g. leave the ModuleHandlerInterface unchanged, let it return the current Extension objects, but provide an alternative that returns the "clean" Extension objects.
This is all still quite rough and might be difficult to do.
But if we are already considering a redesign of this, we should do it in a clean way.
Comment #86
donquixote commentedRe #84 (myself)
So I think the idea is:
Currently we have one Extension class for everything.
Instead, we want dedicated Extension classes for different types of extension.
I'd say this makes sense, and it is fine to keep the existing issue for this.
So
Not needed :)
Still, re #85 (myself):
If you ask me, we should only use these new extension classes in the places that really need them.
So yes for ExtensionList, but:
- Not in ExtensionDiscovery.
- Not in ModuleHandler.
Both of these can use more slim objects, or perhaps just plain arrays.
Or ModuleHandler can use the old Extension class for BC.