Problem/Motivation
The form system is currently closely coupled to the Module and Theme system via alters. That's undesireable as it makes it harder to test the core of the form system. It also inhibits our ability to (later), abstract FAPI out entirely. (Whether there is value to doing so or not, there are architectural benefits to the decoupling that would entail.)
Proposed resolution
Factor the module and theme dependencies out of FormBuilder and into an interstitial "alter" object. That adds a decoupling between FormBuilder and Module/ThemeHandler. The net effect is identical for the 99% use case, but improves the separation of concerns within FAPI.
This is primarily an internal improvement to FAPI and not an API change for anyone except core FAPI developers.
Remaining tasks
Commit?
User interface changes
None.
API changes
No public facing changes; just internal to FAPI.
Comment | File | Size | Author |
---|---|---|---|
#59 | 2328871-form_alter-59.patch | 16.82 KB | tim.plunkett |
#59 | 2328871-form_alter-59-interdiff.txt | 1.28 KB | tim.plunkett |
#57 | 2328871-form_hook-57.patch | 16.92 KB | tim.plunkett |
#53 | interdiff-2328871-51-53.txt | 4.95 KB | phenaproxima |
#53 | 2328871-53.patch | 16.93 KB | phenaproxima |
Comments
Comment #1
tim.plunkettComment #3
dawehner<3 for proper constant usage!
I still don't get why everyone wants to mark them as final.
Comment #4
tim.plunkettIt's final because all of the others are... Discussed in IRC, no one really was concerned about this.
Fixed bugs, and expanded docs.
Comment #5
jibranIn reality I'd wait for #2328777: Refactor FAPI getCache()/setCache() into a standalone class but I know you'll handle reroll ;) So RTBC.
On the side note I was thinking what can I do with my own event subscriber. Can you come up with some ideas?
Comment #6
tim.plunkettRerolled for #2328777: Refactor FAPI getCache()/setCache() into a standalone class
Now moduleHandler is removed from FormBuilder!
Comment #7
webchickSo overall, I can see the idea behind this. And I like that we retain BC so modules don't actually have to change any code. But we get into murky territory here by converting a one-off hook (and not just some silly bootstrap hook that 8 modules implement, but one of *the* most oft-used hooks, esp. in custom modules) to an event and not doing so across the board. Because of the frequency with which this hook is used, it also means that if this patch goes in, contrib is likely to result in a "potpourri" of different approaches, where module A uses hook_form_alter, and module B uses the FormAlterEvent and it generally gets much harder as a developer to skip back and forth between them. (I realize this isn't the intent, but I believe would be the effect, and if we're going to go that route, I'd much prefer to give all hooks this option via something like #1972304: Add a HookEvent).
Anyway, of the core committers, I feel like catch has the strongest opinions about when to / not to use the Symfony event system in lieu of / alongside the Drupal hook system, so assigning to him after talking to Tim.
Comment #8
chx CreditAttribution: chx commentedIt is not my role to agree or disagree with a D8 patch so as the D7 form maintainer: the ordering of
hook_form_alter
visform_FORM_ID_alter
is already a confusing mess.Comment #9
tim.plunkettThe reality that other modules could subscribe to this event instead of using a hook is an unintended side-effect. @webchick and @chx are definitely right that it would add to the confusion.
One other suggestion by @Crell was to have a simple service that FormBuilder calls out to, and let it handle the moduleHandler/themeManager.
This isn't very urgent though, so I'll leave it for @catch first.
Comment #10
tim.plunkettHere's a non-event-based solution. This one just encapsulates it in a private service.
Pros: Easier to test, no danger of modules abusing it
Cons: Swaps two dependencies for one instead of zero
Also comes with unit tests.
Comment #12
tim.plunkettComment #13
jibranI agree with @chx and @webchick but IMHO we are going to end up with event base solution anyway when we'll replace hooks with events. As @webchick said it is most used hook why not take this opportunity to encourage contrib developer to use events instead of hooks.
So +1 for event base solution.
Comment #14
tim.plunkettBecause we would likely implement it completely differently, with one event for hook_form_alter, one for hook_form_BASE_FORM_ID_alter and one for hook_form_FORM_ID_alter, to ensure they run in the correct order.
And also, themes cannot subscribe to events, and they need to be able to alter forms.
Comment #15
Crell CreditAttribution: Crell commentedWhat is event_dispatcher used for other than alter right now?
One advantage of the FormAlter object abstraction is that it decouples form altering from *any* particular implementation. That means there's no dependency on hooks, OR on EventDispatcher (hence my question above for what else it's used for). That allows us to vary the implementation later if needs be, or per-site. (So if someone really wanted events for alters as well on their site for some reason, that would be possible.)
chx suggested a similar approach a while back to eliminate the hook_query_alter() call in the database layer. I think it's a good idea but we never really got around to it. We should do the same there, too, though.
Comment #16
dawehnerI wonder whether we considered to have a alterdecorator for forms instead of passing it along.
Comment #17
Crell CreditAttribution: Crell commentedThat would be possible, but would be somewhat more involved as we'd need to forward every method, not just the one we are throwing an alter on. I think in this case it's OK to make it an intrinsic part of the concept of form processing, just via a separate object.
Comment #18
tim.plunkettRerolled. I have no real opinion on the decorator idea.
Also, since we're not trying to use events anymore, this shouldn't be blocked on @catch.
Comment #20
tim.plunkettComment #21
znerol CreditAttribution: znerol commentedComment #22
rpayanmComment #23
Crell CreditAttribution: Crell commentedThis improves decoupling, has zero impact on module developers, and is a change only to a constructor signature. This very low impact, moderate benefit => IMO RTBCable.
Comment #24
catchIf you swap out the new service, and for example don't invoke hook_form_alter() at all, then all existing form_alters become dead code. That doesn't look viable for any real use case. So while this patch removes an explicit dependency on the module and theme system, it does nothing to remove the implicit dependency which is very much baked in.
If the goal is to use the form builder entirely outside of Drupal that also doesn't seem feasible given the current state of form API. If someone can make the case that we're actually quite close to this point and this patch really helps there, then that might help a bit.
On top of that, the issue summary is completely contrary to the patch.
Comment #27
Crell CreditAttribution: Crell commentedNeeds reroll now.
I updated the IS with a better description of what the end result is. This doesn't allow us to use FAPI outside of core itself; it nominally moves us closer but the bigger point is just better decoupling and separation of concerns with FAPI. It's better quality code and a small benefit to testability, which IMO is justification enough as there's no downside to module developers. (In theory one could swap out the alter service but I really don't expect anyone to do so; that is in no way an argument that it shouldn't be its own class, however, as it's more about separation of concerns than making such swappability a user-facing "feature".)
Comment #28
effulgentsia CreditAttribution: effulgentsia commentedAdding #2402445: Implement object oriented form alters as a related issue, since it also adds a FormAlterInterface, though an entirely different one, to solve a different problem.
Comment #29
rpayanmrerolled
Comment #30
Crell CreditAttribution: Crell commentedBack up to the committers. (Will retest momentarily.)
Comment #33
jclema CreditAttribution: jclema commentedComment #34
tim.plunkettRerolled, with some changes.
Comment #35
Crell CreditAttribution: Crell at Palantir.net commentedOh yeah, this issue. :-)
Comment #37
rpayanmComment #38
bojanz CreditAttribution: bojanz commentedI'll reroll.
Comment #39
bojanz CreditAttribution: bojanz commentedIt was actually still applying, though with big offsets, so here's a fresh one.
Comment #41
bojanz CreditAttribution: bojanz commentedThe test fail is "Updates for Contrib module one" in Drupal\locale\Tests\LocaleUpdateTest->testUpdateImportSourceRemote().
Seems unrelated?
Comment #43
bojanz CreditAttribution: bojanz commentedAnd we're back.
Comment #46
Crell CreditAttribution: Crell as a volunteer commentedStill back.
Comment #47
catchThe test change replaces two mocks with one mock, I'm still missing how the additional indirection is worth introducing for having to mock one less thing. This feels like 'service-per-method' rather than real decoupling still, and there are other issues untangling actual dependencies we could be spending the energy on instead.
Comment #48
tim.plunkettOh, I thought this went in. Whoops :(
Comment #51
phenaproximaAt @tim.plunkett's encouragement, I have taken a shot at turning hook_form_alter into an event. This patch removes FormAlterInterface in favor of FormAlterEvent, but it keeps the existing FormAlter class doing exactly what it was already doing -- it is merely trigged using the normal event dispatcher now.
I didn't update the tests (or comment anything, really), so it will
probablydefinitely fail horribly. This is more a proof of concept at the moment, to test the waters and see if people want to pursue this approach further.I apologize for the lack of an interdiff; some hunks of the original patch didn't apply against 8.3.x.
Comment #53
phenaproximaThis oughta fix the tests.
Comment #57
tim.plunkettRerolled.
Comment #59
tim.plunkettPrivate services are incompatible with ContainerAwareEventDispatcher. Also missed a reference.
Comment #61
catch#7, #9, #14, #24 and #47 on this issue (possibly others) haven't been responded to adequately yet to justify why we'd do this.