Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem
- Many factory/manager services take a list of tagged handler/subscriber services; primarily to ensure a pluggable architecture.
- Each of these occasions uses a separate CompilerPass right now, which is cumbersome.
Proposed solution
-
Introduce a new, single
CompilerPass
to collect handlers for a consumer service definition. -
A consumer service opts in to the compiler pass by adding a corresponding tag to itself:
breadcrumb: class: Drupal\Core\Breadcrumb\BreadcrumbManager tags: - { name: service_collector, tag: breadcrumb_builder, call: addBuilder }
Parameters:
name
: Theservice_collector
tag itself.tag
: The name of the handler/provider/subscriber tag to collect. Defaults to the service ID of the consumer.call
: The name of the method call to add to the consumer service. Defaults toaddHandler()
.
-
The handler/provider/subscriber tag stays as in HEAD; e.g.:
book.breadcrumb: class: Drupal\book\BookBreadcrumbBuilder tags: - { name: breadcrumb_builder, priority: 701 }
-
→ Result: Identical behavior as in HEAD, just without a bazillion of one-off
CompilerPass
classes that are all doing the exact same thing.
Comment | File | Size | Author |
---|---|---|---|
#53 | interdiff.txt | 689 bytes | sun |
#53 | container.tags_.53.patch | 38.46 KB | sun |
#51 | container.tags_.51.patch | 38.46 KB | sun |
#48 | interdiff.txt | 1.76 KB | sun |
#48 | container.tags_.48.patch | 38.47 KB | sun |
Comments
Comment #1
sunKick-starting this.
Comment #2
sunHm. And this looks sub-optimal...
Converted RegisterBreadcrumbBuilderPass.
How can we avoid the
\Drupal::service()
call?Comment #4
sunSo, perhaps, rather this?
Added ContainerTagResolver.
Comment #5
sunA perhaps simpler alternative to this would be the following:
addHandler()
addHandler()
calls for the manager service definition.'cos that's not trivial to parse, example:
Note: Tag name == Manager service ID.
Followed by a compiler pass (sans abstraction for clarity):
In turn,
BreadcrumbManager
:→ Standardized manager/handlers pattern.
The chance for a name clash for a tag name to false-positive match an unintended service ID is pretty close to zero, IMHO.
Thoughts?
Comment #6
sunThat concept in code. :-)
Comment #9
sunFixed other breadcrumb handler service definitions.
(Serious WTF @ those priorities.)
Comment #10
Crell CreditAttribution: Crell commentedAk. This is NOT what we discussed in IRC. We discussed using the lists for those factory services that already work on tags for performance reasons, like access checkers. Services like breadcrumbs that operate on services directly should not be touched. That's exactly what I said when you asked me about it.
Comment #11
sunHm. Looks like we talked past each other then. :-/
The idea really was to simplify and replace all of those one-off compiler passes that are just collecting tagged services, in order to supply them to a manager service (by adding a method call to the manager service definition on the container).
That is, because having to duplicate the exact same code for every single tag into a new compiler pass class doesn't really make sense, DX-wise.
My original hope was that we'd be able to just collect the tagged service IDs as container parameters, but it turned out that I forgot about service instantiation (cf. #2, #4). That approach also did not work, because a parameter would not exist at all in case no services are tagged, which in turn causes a "missing parameter dependency" exception upon compiling the container.
What's wrong with the concept proposed in #5?
I think it improves DX and establishes a clear and standardized pattern for all Manager ← Handlers implementations. Those implementations already exist in HEAD, this proposal only simplifies and harmonizes them?
Comment #12
sunComment #14
sunAdjusting issue title.
Comment #15
sunD'oh. Sorry!
Comment #17
sunFixed another stale variable. Manually tested installer this time. :)
Comment #19
sunFixed BreadcrumbManager does not require handlers to implement ChainBreadcrumbBuilderInterface, but only *itself*.
Comment #20
sunUpdated issue summary to reflect the proposed solution.
Comment #21
sunNow... to show the true DX improvement:
Comment #22
dawehnerAdded some tests.
Comment #23
sunThanks, awesome! :-)
Attached patch just adds a @todo to
CoreServiceProvider
, which was part of the interdiff in #21 already, but which I forgot to actually commit ;)Comment #24
dawehnerDo you know whether we have a constant which can be used instead of the raw string?
Comment #25
sunNope, there's no constant for the kernel environment.
#2226761: Change all default settings and config to fast/safe production values might introduce one (or two), but it's not clear yet, whether those will actually map 1:1 to a
DrupalKernel
$environment
.Given that Symfony uses 'prod' and 'dev' as two primary environment names all over the place in their docs, I'm also not sure whether I like the idea of introducing constants.
#2237681: Revise Guzzle client configuration and event registration has landed, so:
Converted new http_client:http_client_subscriber.
Comment #26
sunComment #27
dawehnerThe documentation is really extensive!
Comment #28
Crell CreditAttribution: Crell commentedThis still isn't what we discussed in iRC at all. I am, however, more OK with it as this does simplify the service compilation process quite a bit. (I actually like that it bakes the priority into the compiler pass; no API change, but should be a very slight performance boost.)
+1
Comment #29
alexpott#28 afaics we're not going to get the performance boost unless we also remove the sorts from methods like
ThemeNegotiator::getSortedNegotiators()
right?Comment #30
alexpottThis will call ConfigFactory::addOverride with an additional parameter.
public function addOverride(ConfigFactoryOverrideInterface $config_factory_override)
The reason to delay the sorting is to be able to call the addSomething methods dynamically not as part of the compiler pass. This made little sense for the ConfigFactory. If we go the way of always sorting by priority in the compiler then we should accept that calling an addSomething method programatically after the container has booted always adds to the end of list.
Comment #31
Crell CreditAttribution: Crell commentedalex: I don't think the hard division is necessary. sort is not an O(1) operation. How long it takes to run depends in part on whether the list is already sorted. If it's already sorted then it's faster. That doesn't mean allowing others to be added is bad. And given that in most cases we're talking about under 10 items, often less than 5, I probably misspoke in mentioning performance to begin with. It probably won't even be measurable, even though it is theoretically true.
Nothing is harmed by having the sort() call in both places, and it avoids an implicit dependency on the DIC for the aggregate services. Remember to think local and decoupled and having the sort() in both places makes complete sense. :-)
I cannot speak for the config factory's needs, so not moving back to RTBC. Perhaps we should just remove the config factory from the list of aggregate services we're dealing with here and deal with it later. Then this would be back to RTBC.
Comment #32
sunI'm working on a great simplification that resolves the concerns + additionally involves a massive DX win, patch coming up shortly.
Comment #33
sunHere we go:
Added high-level documentation to explain and disambiguate the concept, and facilitate naming.
Renamed tag attributes:
'compiler_pass'
→'service_consumer'
,'method'
→'call'
.Be smart: Automatically determine required interface and whether
$priority
is accepted.+ test coverage + simplified test code.
Comment #34
Crell CreditAttribution: Crell commentedThe second sentence here seems awkward. The key difference from plugins, IMO, is that what plugin is used when is a user-configuration-driven decision. For a tagged service it is developer-configuration-driven, aka hard coded into the aggregating service.
Comment #35
sunAlright, after some further discussion in IRC...
@Crell: I've adjusted the docs. FWIW, I originally had the aspect of "plugins are configurable / depend on configuration" in there, but removed it, because that's actually not true; the plugin system is able to operate with just a discovery + plugin instances only (sans configuration). In fact, that comes very very close to this pattern of collected/aggregated handlers, but the key difference is the declarative configuration in code. A plugin-based implementation would simply consume/register all available plugins that have been discovered, since there is no declarative negotiation of what to use in between. (I assume that's what you meant, too; just paraphrasing in words I can understand. :-))
Comment #36
sunUpdating issue summary accordingly.
Comment #37
sun35: container.tags_.35.patch queued for re-testing.
Comment #39
sunMerged 8.x.
Comment #40
sunI'm pretty happy with this patch now. Back to RTBC? :)
Comment #41
dawehnerI would RTBC but don't we need a change notice?
Comment #42
sunCreated https://drupal.org/node/2247531
Comment #43
Crell CreditAttribution: Crell commentedNow we can RTBC it.
Comment #45
dawehner39: container.tags_.39.patch queued for re-testing.
Comment #46
sunTestbot fluke.
Comment #47
catchAre there issues for the two @todos?
Comment #48
sunRemoved potentially premature @todos.
Comment #49
sunNevertheless, created:
#2250239: Remove needless ContainerAware dependency from ParamConverterManager
#2250243: Remove needless ContainerAware dependency from AuthenticationManager
Comment #51
sunMerged 8.x.
Comment #52
damiankloip CreditAttribution: damiankloip commented'@endcode' ...Sorry!
Comment #53
sunoh LOL, I spotted that myself some days ago already, but forgot to fix it - thanks! :-D
Comment #54
damiankloip CreditAttribution: damiankloip commentedHehe :) +1 for rtbc!
Comment #55
catchCommitted/pushed to 8.x, thanks!
Comment #57
ParisLiakos CreditAttribution: ParisLiakos commentedoh, thats neat :) thanks all!
Comment #58
sunClosely related: #2251113: Use container parameters instead of settings