Problem/Motivation
While Typed Configuration builds on Typed Data, there are still a few mismatches between both of them, and this should add some consistency in how interfaces and parameters are used in both sides.
There's currently a TypedConfigManagerInterface, but the counterpart in TypedData doesn't exist. Because of this, some methods are defined twice, once in TypedConfigManagerInterface, once in TypedDataManager.
This helps us to address some @todo in \Drupal\Core\TypedData\TypedData: "Add the typed data manager as proper dependency."
Proposed resolution
1. Add a new interface: TypedDataManagerInterface
2. Use it for typed parameters instead of the class TypedDataManager
3. Get TypedConfigManagerInterface to extend TypedDataManagerInterface
4. Then use the new interface in TypedDataTrait and use it for fixing the dependency issues in TypedData objects
5. More cleanup is possible in #2533776: Instead of mocking TypedDataManager, use TypedDataManagerInterface in tests once this lands.
Remaining tasks
Commit.
User interface changes
None.
API changes
Minimal. Use TypedDataManagerInterface instead of TypedDataManager.
Beta phase evaluation
Issue category | Task because the issue implement OO best practices by using an interface instead of demanding a specific class where TypedDataManager is needed. |
---|---|
Issue priority | Normal because while the change makes understanding the typed data system much easier, its not a showstopper. |
Disruption | Minimal. Use TypedDataManagerInterface instead of TypedDataManager to typehint. There is little use in core, and only pretty specialized code depends on it. |
Comment | File | Size | Author |
---|---|---|---|
#51 | 33-51-interdiff.txt | 964 bytes | alexpott |
#51 | 2488568-51.patch | 48.69 KB | alexpott |
#47 | 2488568.47.patch | 45.19 KB | Jose Reyero |
#47 | 2488568.44-47.diff.txt | 1000 bytes | Jose Reyero |
#44 | 2488568-36-44-diff.txt | 1.35 KB | Jose Reyero |
Comments
Comment #1
Jose Reyero CreditAttribution: Jose Reyero as a volunteer commentedFull patch for review and testing.
Comment #2
Gábor HojtsyComment #3
Jose Reyero CreditAttribution: Jose Reyero as a volunteer commentedTrimming down this patch, so it is more straight forward and way easier to review:
- Just add TypedDataManagerInterface and use it for typed parameters.
- Leave the dependency issues for a follow up.
Updating title and issue description too.
Comment #4
Gábor HojtsyBring this on the D8MI sprint, even though not strictly language related.
Comment #5
tstoeckler++ for this issue!
Marking RTBC as this issue is certainly an improvement over the status quo.
There are a couple things that could be improved and IMO fall into this scope, so I wouldn't mind them being fixed here, but we can also improve in a follow-up.
1.
TypedConfigManager::get()
could have its documentation removed in favor of@inheritdoc
2. In
TypedDataManagerInterface::createInstance()
we should aknowledge the inheritance fromFactoryInterface
by replacing the one-line doc with a@inheritdoc
. I think it makes sense to leave the method in for documentation and type-hinting purposes, but it's not really a method thatTypedDataManagerInterface
is introducing so we should be clear about that.3. The docs on
TypedDataManager::getInstance()
should be replaced with@inheritdoc
. If we want to keep the specific documentation we should put it onTypedDataManagerInterface
(like in 2.)4. I think the ordering of the methods in
TypedDataManagerInterface
could use some love. I know that you just used the same ordering as inTypedDataManager
but e.g. having setters before getters feels fairly unnatural to me.Comment #7
Jose Reyero CreditAttribution: Jose Reyero as a volunteer commentedRe-rolled the patch for latest core updates and done all of the suggestions from @tstoeckler #5
- Also used @inheritdoc for TypedDataManager::getPropertyInstance()
@tstoeckler,
Not sure about the @inheritdoc + additional parameters documentation, is this what you mean?
Comment #8
tstoecklerYes, that's what I meant. Thanks! Looks even better now.
Comment #9
alexpottThis looks really sensible to me - going to ping @fago and @berdir for a once over before proceeding. Also as this is a normal task we need a beta evaluation.
Comment #10
Gábor HojtsyAdded beta evaluation. :)
Comment #12
jibranLet's ask subsystem maintainer for a review.
Comment #13
BerdirI think this looks fine. The issue summary doesn't seem 100% up to date, it looks like the issue is already doing more than described there and already gets rid of the dependency and doesn't just add the interface.
Looks like we have around 6 calls to "$this->getMockBuilder('\Drupal\Core\TypedData\TypedDataManager')" that could be slightly simplified now that we have an interface. A follow-up to do that would be nice.
Comment #14
Gábor HojtsyComment #15
Gábor HojtsyRerolled because this did not apply likely due to #2527708: Improve documentation for TypedConfigManagerInterface.
Comment #16
Gábor HojtsyRemoved the obsolete @todo as identified by @berdir. Also removed a superfluous newline among the traits used.
Comment #17
Gábor HojtsyOpened #2533776: Instead of mocking TypedDataManager, use TypedDataManagerInterface in tests for @berdir's concern.
Comment #19
Gábor HojtsyFail in #15 was unrelated (in menu test, "The test did not complete due to a fatal error."). All feedback addressed, and I only changed two lines in very minor ways, so back to RTBC.
Comment #20
alexpottThanks everyone - this is a nice place to get to. Any improvement that clarifies the relationship between TypeDataManager and TypedConfigManager reduces fragility.
Having both the methods seems confusing. There is only one usage of
getTypedConfigManager()
.Comment #21
alexpottUnused uses and a reference to a class that does not exist.
Comment #22
Jose Reyero CreditAttribution: Jose Reyero as a volunteer commentedRe-rolled latest @Gábor Hojtsy patch #16, fixing:
- Issues pointed out by @alexpott on #22
About @alexpott #20 (clarify relationship between TypeDataManager and TypedConfigManager):
- Replaced getTypedDataManager() by getTypedConfigManager() in ArrayElement::createElement() for consistency, though that doesn't really address the question..
- The reason why we need getTypedConfigManager() and getTypedDataManager() is because we need to use the buildDataDefinition() method that is not part of the TypedDataManagerInterface and if we do something like:
That would work, because the TypedDataManager used for config objects is an instance of TypedConfigManager (see TypedDataManager::createInstance()) but it would be wrong because we would be calling a method that is not part of the interface that is returned by getTypedDataManager().
Comment #23
Gábor HojtsyThanks for the fixes, they look good.
Comment #25
Gábor HojtsyViews's DisplayTest fails there with
Drupal\views\Plugin\views\style\StylePluginBase: Missing row plugin
, does not seem to be related.Comment #28
Gábor HojtsyRerolled.
Comment #29
alexpottIn reviewing this patch I've realise we have more problems. We can't fall back to TypedDataTrait::getTypedDataManager() because that gets the wrong manager. Also I really do not see the harm in ArrayElement overriding the TypedDataTrait::getTypedDataManager(). In fact, I think that all config typed data should extend from Element and Element should override the set and get of the typed data manager to ensure we're working with the correct one. As in the patch attached. We can enforce a narrowing of the interface on the setter using an assert.
I'm sorry that this is going to take a bit longer.
Comment #30
Gábor HojtsyI don't know if there was/is a reason for Element not to be a base class for ArrayElement, I'm fine with the suggestions.
Comment #31
tstoecklerYay, I think that makes the patch a lot more understandable.
I have two minor nitpicks:
I checked and all other config elements (whose existence is similarly questionable to Element before this patch, but I digress...) already extend from Element, so we should be fine. ++
This should be \Drupal\Core\Config\TypedConfigManagerInterface
Yay!!!!!!
*I*nterface should be lowercase
(but neat trick with the copy-detection)
Otherwise looks good to go from my perspective, but I think @Jose Reyero should sign off on the interdiff (thus leaving at needs review).
Comment #32
tstoecklerOops, the assignment was not intentional.
Comment #33
alexpottThanks @tstoeckler
Comment #34
tstoecklerHmmm... I don't think a mismatch in type-hint and documentation is very confusing. When working with the entity system I do that a lot, in fact, because I often don't have what I need in e.g.
EntityInterface
but that's forced by the interface. Anyway, I guess we can assume that people can read and the documentation is crystal clear, so that works for me.I noticed one thing earlier, though:
We now call a
setTypedDataManager()
method on an object we know is an instance ofTypedDataInterface
but (in theory) don't know much more. In other words, if people provide typed data plugins without subclassingTypedData
they will get fatals. So I think we have to either addsetTypedDataManager()
toTypedDataInterface
or add aTypedDataManagerAwareInterface
which we can check against.Still leaving at "needs review" for input from @Jose Reyero.
Comment #35
Jose Reyero CreditAttribution: Jose Reyero as a volunteer commented@tstoeckler,
Yes, that's a different bug, but we could fix it here too
About the 'assert' in #33... why not throwing an Exception? I thought assertions were not something to use at runtime..? Otherwise, all of @alexpott's changes make a lot of sense.
About that interfaces and getTypedDataManager()/getTypedConfigManager() I would be more interested in fixing the methods/interface consistency than the documentation itself. Could we do something like:
Anyway, I am looking into some other solutions like moving TypedConfigManager::buildDataDefinition() -which is the only method preventing us from using TypedDataManagerInteface everywhere - into the Element class, creating some TypedConfigDataDefinition and implementing it there, or maybe creating some new service to build that data definition objects from configuration schema + data, which may make sense since building these dynamic definitions is a whole different thing. All of the options take a lot of refactoring though, so I'm nowhere closer to a real patch for any of them...
Comment #36
Jose Reyero CreditAttribution: Jose Reyero as a volunteer commentedSo... forgetting about more complex options, and aiming at interface consistency, this is a re-roll from #33 which:
- Adds TypedConfigInterface::getTypedConfigManager() as explained in #35 This is just an extra method in the interface and provides exactly what we are looking for, a properly typed return value.
- Adds add setTypedDataManager() to TypedDataInterface as suggested by @tstoeckler #34
Other doc changes in TypedDataManagerInterface, not in the interdiff, are due to this one that just got in the middle #2548265: Improve TypedDataManager doxygen
Comment #37
tstoecklerHmmm... not really sure what to do here. I personally find #35/ #36 less clear than @alexpott's patch because the subtle difference between "I am returning a typed data manager" and "I am returning a typed *config* manager" is... well, really subtle. I am also not clear on what concretely the problem is with only having a single method for "both" (where one is only a specialized version of the other, so "both" isn't really fitting here, but ...)
Comment #38
Gábor Hojtsy@tstoeckler can you follow up on this one? looks like this is blocked on reviews and your concerns specifically with the time window rapidly closing on it if not already :(
Comment #39
Jose Reyero CreditAttribution: Jose Reyero as a volunteer commentedI think the issue is explained in #22, "The reason why we need getTypedConfigManager()..."
Anyway, ok, we can live without that kind of consistency, I think there are many of them in D8 atm..
Comment #40
tstoecklerRe #39:
So #22 says:
I think the patch in #33 explains this situation sufficiently:
I realize that having to document something like this makes it evident that this is not an ideal situation, but I think the patch in #36 also comes with a cost in terms of understandability because with it you can retrieve two different objects which implement the same interface (by extension) and because of the inheritance of the actual classes that implement the interfaces it actually *is* the same thing that happens, but they're still two different objects retrieved with two different objects.
Comment #41
alexpottComment #42
Gábor HojtsyLet's figure this out ASAP and get in before RC.
Comment #43
alexpottFix the tag
Comment #44
Jose Reyero CreditAttribution: Jose Reyero as a volunteer commentedOk, let's remove the method from the interface and let that logic for the ArrayElement class internals.
Comment #47
Jose Reyero CreditAttribution: Jose Reyero as a volunteer commentedThat failing test is pretty ugly, I don't really get it, anyway, trying to fix it....
PHP Fatal error: Class Drupal\Tests\serialization\Unit\Normalizer\TestComplexData contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Drupal\Core\TypedData\TypedDataInterface::setTypedDataManager) in /var/lib/drupaltestbot/sites/default/files/checkout/core/modules/serialization/tests/src/Unit/Normalizer/ComplexDataNormalizerTest.php on line 146
Comment #49
Wim Leers99% of this is just introducing an interface, typehinting to that interface, and moving docblocks from the implementation to the interface.
I'm no TypedData expert, but this seems like an innocent enough change that it's okay for me to RTBC it.
I only found two nits, that can easily be fixed on commit:
Nit: one extraneous
\n
here.Nit: Unnecessary change.
Comment #50
alexpottUsing it, but removing it.
There is no use so the instanceof always fails.
This looks really really odd - using
getTypedDataManager()
in one place andgetTypedConfigManager()
in another. Looks very dangerous.Personally I think we should just go back to #33 and remove the assert and through an exception.
Comment #51
alexpottRerolled #33 and changed the assert to not use the single quote eval style.
I think @tstoeckler wrote in #34 and #40 that the patch in #33 is clearer and I agree. Also in #39 @Jose Reyero wrote:
I think given that both @tstoeckler and I feel that #33 is clearer and @Jose Reyero can live with it we should go with it.
Comment #52
Gábor HojtsyYay, agreement!
Comment #53
effulgentsia CreditAttribution: effulgentsia at Acquia commentedLooks good to me. Ticking the credit box for @tstoeckler.
Comment #54
effulgentsia CreditAttribution: effulgentsia at Acquia commentedPushed to 8.0.x.
Comment #56
Gábor HojtsyYay, thanks all!
Comment #58
Gábor HojtsyComment #60
XanoThis change may have caused #2615790: Field item properties do not prevent the services they contain from being serialized.