The ConfigImporter needs a ton of services injecting to its constructor.
This is a pain because anything wishing to use it (such as the config UI provided by core's config module, or Drush, or the Config Devel contrib module) has to instantiate that class taking care of getting all the services.
Once this is fixed, it'll be easier to do #3530247: add way for kernel tests to install single config.
Proposed resoution
Add a factory service which wraps the instantiation of a ConfigImporter object.
| Comment | File | Size | Author |
|---|
Issue fork drupal-3123491
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:
- 3123491-ConfigImporter-factory
changes, plain diff MR !4978
Comments
Comment #2
johnwebdev commentedWhat about a factory class?
Comment #3
joachim commentedHow do you envisage that would look? What would be the advantage over a service?
Comment #4
johnwebdev commentedWell, services are shared and ConfigImporter stores internal state (errors, validated status) etc which suggests to me that it shouldn't be a service.
Comment #5
joachim commentedAh, yes, I hadn't thought of that!
Symfony actually allows services to not be shared, but we probably don't want to introduce that extra complexity to Drupal core services.
Comment #6
joachim commentedUpdated the summary. Could you add details on how a factory class would work?
Comment #7
johnwebdev commentedHere is an example with pseudo code: https://gist.github.com/johndevman/6f095d41e6d49ed0306bd9683789c905
Alternatively, you could perhaps do something similar to cache (not entirely sure if the objects are shared or not here though)
Comment #12
donquixote commentedI wrote this up in a project.
Too lazy to create a patch atm :)
Comment #13
joachim commentedCool, I was halfway through writing this while tests were running a few weeks ago. I'll compare what I had with what you have and make a MR.
Comment #14
donquixote commentedWe could also make an interface for this.
Comment #15
joachim commentedPushed my WIP to a fork, will start to combine with @donquixote's work later.
Comment #18
joachim commentedComment #19
joachim commentedStill not finding time to work on this, but I've added the actual factory service to the Field Tools module based on @donquixote's code (with a few tweaks), so the code can be grabbed from that when someone gets round to doing more work here.
Setting to NW since there's actually some code done on this issue.
Comment #20
joachim commentedComment #22
joachim commentedComment #23
smustgrave commentedFailure seems legit to the issue.
Comment #24
joachim commentedI'm stuck because there's no docs on why the test fails and how to do services in core now.
Comment #25
larowlanAdded https://www.drupal.org/project/drupal/issues/3394450
Comment #26
joachim commentedComment #27
smustgrave commentedFollow up issue has been resolved.
But could a CR be written for the new search, preferably with an example?
Also issue summary mentions more details pending?
Comment #28
joachim commentedDone the CR and cleaned up the IS.
Comment #29
smustgrave commentedThanks much better! cR reads well too, thanks for adding an example
Comment #30
joachim commentedHaha, it was just copy-pasted from the MR diff!
Thanks for the review :)
Comment #31
alexpottI want to avoid swapability at all costs here. Supporting alternate implementations of the ConfigImporter and StorageComparer makes me feel very uneasy. I think the idea of have a factory service is a good one as long as we make changes to show the scope is to use a
\Drupal\Core\Config\StorageComparerobject to generate a\Drupal\Core\Config\ConfigImporterobject.I wish
\Drupal\Core\Config\StorageComparerInterfacedid not exist. Perhaps we can move all the docs to the StorageComparer and change the typehint on \Drupal\Core\Config\ConfigImporter::__construct() to StorageComparer|StorageComparerInterface and deprecate the interface.Comment #32
joachim commented> I wish \Drupal\Core\Config\StorageComparerInterface did not exist. Perhaps we can move all the docs to the StorageComparer and change the typehint on \Drupal\Core\Config\ConfigImporter::__construct() to StorageComparer|StorageComparerInterface and deprecate the interface.
That sounds like a follow-up to me? Or should it happen here to prevent swappability?
Could you explain what changes you want to this MR please? I'm not clear.
Comment #33
alexpottThe StorageInterface deprecation is definitely a follow-up.
I think my comments on the MR were pretty clear... make the factory final and @internal and instead of typehinting on StorageComparerInterface do it on StorageComparer instead.
Comment #34
joachim commentedMade a follow-up: #3410037: Deprecate StorageComparerInterface
Made the fixes.
Comment #35
smustgrave commented1 small comment.
For the parameters could they be typehinted or would that cause issues since they're really being moved?
Comment #36
bircherAh this is great! For Config Split, I made a trait which gets the services from \Drupal:: in order to avoid people trying to abuse it by swapping it etc.
But with final and the concrete classes typehinted I think it is even nicer.
When reviewing I saw that obviously the constructor of the form classes changed, we remove some deprecation notice and I am wondering if we need to do that here too.. but since there is a create method I think we are fine, but it makes me wonder why it was done last the other time.
Also the change record looks good.
Comment #37
joachim commentedAddressed some of the comments & rebased on 11.x.
Regarding the deprecation for constructors in the two form classes -- form classes are not part of core's API and these two classes specifically have an '@internal' tag. On top of that, given it's faffy, I'd say we should leave it.
Comment #38
joachim commentedRebased and resolved comments.
Comment #39
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #40
joachim commentedComment #41
joachim commentedComment #43
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #44
joachim commentedComment #45
phenaproximaI think this is a great idea; offered some suggestions to reduce verbosity and pump up that diffstat even more. :)
Comment #46
joachim commentedComment #47
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #48
joachim commentedComment #49
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #50
oily commentedTest coverage is present. Test-only test output:
This seems as desired? I assume that the various other tests edited in the MR were edited to fix them after they were broken by the MR?
I have added several code review comments.
Comment #51
joachim commented> This seems as desired? I assume that the various other tests edited in the MR were edited to fix them after they were broken by the MR?
Yup. The tests have been updated to work with the new factory service, so running tests without the new code will fail.
Comment #52
oily commentedI see 2x unit tests failing but look unrelated. Some disagreement on coding standards in the comments. I think the comment reviews have not been set to 'you have to follow the comments' so moving to RTBTC.
Comment #53
alexpottThe test fails are very much related...
Comment #54
oily commented#53 Thanks @alexpott. I just spotted the autowire error and was changing the issue status back to needs work! Then I saw your change 50s ago..
Comment #55
oily commentedComment #56
oily commentedI think can loop back to #52. There are again 2x tests failing but this time they definitely appear to be unrelated. Returning to RTBTC.
Comment #57
oily commentedComment #58
alexpottWe start need to address the name. As I've pointed out we already have the pattern of get() from factories and we should use it here.
$config_importer = $this->configImporterFactory->createConfigImporter($storage_comparer);- repeating the words config importer twice here does not add anything. And choosing method names based on their searchability without taking into context the class name is not a rationale for the longer name.Comment #59
phenaproximaNot to throw a wrench in the works here, but...could we not just have
ConfigImporterimplementContainerInjectionInterface, and sidestep any need for a full factory service, in conjunction with asetStorageComparer()method? That would achieve the goal here and also make it much easier to instantiate:\Drupal::classResolver(ConfigImporter::class)->withStorageComparer($storage_comparer). And we could still take advantage of autowiring, by way ofAutowireTrait, which should work on anything implementingContainerInjectionInterface.I don't feel strongly about it, to be honest, but I'd be remiss if I didn't raise this possible alternative.
Comment #60
andypostGood idea, moreover it will help to fix injection of
theme_installerandconfig.installerComment #61
andypostAlso it needs follow-up for PHP 8.5+ to fix
reInjectMe()to use__serialize()as part of #3548971: Replace PHP soft-deprecated __sleep()/__wakeup() with __serialize()/__unserialize()Comment #62
alexpottI guess the only issue with #59 is injectability - like injecting the class resolver is not the same as injecting a ConfigImporterFactory object. But I think it is a great idea. Factories usually make sets of things but the ConfigImporter is very much a single thing. And it gets around the whole discussion about the factory getter method name. @phenaproxima++
Comment #63
oily commented#59 approach seems to be in use now in NodePermissions.php?:
Or is this a different use case?
Comment #64
joachim commented> make it much easier to instantiate: \Drupal::classResolver(ConfigImporter::class)->withStorageComparer($storage_comparer).
But if we have that, how can it be mocked in tests?
Comment #65
phenaproximaI'm not sure what you mean here -- why wouldn't we be able to mock it in tests?
Comment #66
phenaproximaTook a shot at implementing the
ContainerInjectionInterfaceapproach - would love reviews!Comment #67
joachim commented> I'm not sure what you mean here -- why wouldn't we be able to mock it in tests?
Things that use a ConfigImporter are hardcoding the class name:
With the factory service in the earlier versions of this MR, you could if you needed to mock the factory service with something that returns a mocked ConfigImporter.
Comment #68
phenaproximaOh, I see. Well, if you need to do something like that, you can certainly mock, or decorate, the class resolver to return a faked version of the config importer.
Comment #69
joachim commented> You can certainly mock, or decorate, the class resolver to return a faked version of the config importer.
Mocking the whole class resolver feels rather heavy!
What's the reason for moving away from the factory service there was earlier?
Comment #70
phenaproximaWhy? It's an interface with a single method. How would that be any heavier than mocking the proposed factory?
I think the main reason to move away from the factory is that it was largely there to wrap around the very heavy dependency injection and autowiring requirements of ConfigImporter...which is pretty much obviated by the robust autowiring support you can already get for free from
AutowireTraitand theContainerInjectionInterface. It also seems to have found more support from a naming perspective, sinceConfigImporter::create()is less verbose thanConfigImporterFactory::createConfigImporter(). From a mocking perspective, they're basically the same.I don't think it's an earth-shattering difference or anything, but why add a whole factory if we don't really need to?
Comment #71
alexpottThinking about this some more the optionality around storage comparer when it is not optional plus the additional API makes me think we should go back to the version before #55 and just change the long method name to "get"
Comment #72
phenaproximaDone! It was worth a shot but I'm not married to the
ContainerInjectionInterfaceapproach, and I agree the optionality was awkward.Comment #73
nicxvan commentedI think this approach makes more sense as well. Everything looks right to me!
Comment #74
phenaproximaOpened #3554866: Deprecate StorageComparerInterface to deprecate
StorageComparerInterfaceas per #31 and the comments after it.Comment #75
alexpottCommitted and pushed d594cf40b84 to 11.x and 51c0672b816 to 11.3.x. Thanks!