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.
#1608842: Replace list of bootstrap modules, enabled modules, enabled themes, and default theme with config introduced a KeyValueFactory, but it hard-codes KeyValueStore\DatabaseStorage.
The patch in #1774388: Unit Tests Remastered™ contains an initial quick fix that makes KeyValueFactory compatible with testing, but we should definitely investigate what the proper implementation for this should be.
Issues being blocked on this
Comment | File | Size | Author |
---|---|---|---|
#37 | 1809206.keyvalue-factories.37.patch | 23.9 KB | katbailey |
#37 | interdiff.txt | 4.42 KB | katbailey |
#33 | drupal8.test-container-destroy.33.patch | 972 bytes | sun |
#32 | 1809206.keyvalue-factories.32.patch | 23.91 KB | katbailey |
#32 | interdiff.txt | 8.29 KB | katbailey |
Comments
Comment #1
BerdirIt doesn't need to be fancy.
The factory itself is defined in the DIC, if you want to use a completely different approach, you can replace the class. IMHO, we don't need to go further than the approach in your patch.
Comment #2
chx CreditAttribution: chx commentedThis is by design. It's a service, just override it.
Comment #3
sunI don't think that's practical and feasible in any way.
It would require core to ship with a separate KeyValueFactory for each storage implementation that it provides.
Comment #4
BerdirYes, that is correct. There aren't going to be many of them I think.
DatabaseStorage requires the connection (not yet, but will soon and is required to get rid of these db_query() calls in the implementation) as a constructor argument. MemoryStorage does not. We've been discussing this a while in the cache DIC issue, It's just not possible for a factory to know what it has to pass to the constructor.
Comment #5
sunGiven #1810912-2: [meta] Decide on pluggability, it looks like we need a ContainerAware KeyValueFactory.
While a dependency on container is slightly awkward, too, that certainly makes way more sense than to require a separate factory class for each implementation, which is a "pattern" I've never seen anywhere else before.
Comment #6
Berdir@sun: Hm, that is exactly what a factory does? Wrapping the knowledge of how a specific implementation of an interface needs to be created.
Sure, we could easily write a factory that knows about the implementations in core and can decide based on a argument or some internal logic if it should create a MemoryBackend or a DatabaseBackend, because it knows these two implementations and knows which constructor arguments they expect.
But it can't know how it would have to create a MongoDbBackend, or a MemcacheBackend, which might e.g. require a Reference('mongodb') as it's second constructor argument, instead of Reference('database').
Making a factory ContainerAware doesn't change anything. Just because it has access to the container doesn't mean that it knows which service it needs?
See the PHP code example on Wikipedia: http://en.wikipedia.org/wiki/Factory_method_pattern#PHP. The factory implementation is a "SedanFactory", that knows that it needs to return new Sedan().
Comment #7
Crell CreditAttribution: Crell commentedI wasn't tracking the KVStore issue that closely so I don't know the exact code we're dealing with. However, based on my discussion with lsmith and ircmaxell yesterday, we concluded that you don't need a factory IF your logic can be as simple as the cache backend issue: #1764474: Make Cache interface and backends use the DIC. If you cannot reduce your factory to a 1:1 mapping from string to service ID, then you use a ContainerAware factory to map from more than a string to a service ID.
The gist we used for discussion yesterday: https://gist.github.com/3880382
That way, you regiseter:
And then KeyValueFactory does the black magic of figuring out whether to use kvstore.thing1 or kvstore.thing2. That kvstore.thing2 needs a database connection is handled by the Container already, and KeyValueFactory doesn't have to care.
I don't know which of these applies to the KVStore system, as I said, but that's the general distinction. If you have KeyValueFactory, all it needs to know about is service IDs.
Comment #8
sunI already tried to implement something along the lines of #7, but got extremely stuck, since I wasn't able to figure out how to retrieve the
keyvalue.database
service definition inKeyValueFactory::get()
to instantiate a new storage class per $bin.I filed this question as a upstream support request: https://github.com/symfony/symfony/issues/5736
AFAICS, the requirements we have here map to the last way in the gist you pointed to -- however, that is using a
new Proxy()
, which I cannot find anywhere.Attached patch shows the entire problem space.
The following two issues are blocked on this issue now:
#1774388: Unit Tests Remastered™
#1798738: Move css_js_query_string to state system
Comment #9
chx CreditAttribution: chx commented+ ->addArgument('%bin%') // ???
is not something possible. Once you compile , there's just no way to pass in an argument. Check the issue linked in #5 on how to achieve this by using two factories.
Comment #10
Sylvain Lecoy CreditAttribution: Sylvain Lecoy commentedThis pattern exist, it is called abstract factory.
I am trying to get in this issue, but first I have to read the 200+ comments of #1608842: Replace list of bootstrap modules, enabled modules, enabled themes, and default theme with config to understand where the code come from :D
Comment #11
Sylvain Lecoy CreditAttribution: Sylvain Lecoy commentedOk so my opinion on this would be to pre-create the backend object, that is putting them in the pool of objects managed by the container. Then inject them pre-configured where you need them.
For me it makes sense, I am trying to understand your patch and playing a bit with that problem. Tell me if I am wrong but the current philosophy is to put factories in the container, then letting them instantiates objects ?
That's for me very weird (I used to do DI in Java and Flex/AS3 with Parsley/Robotlegs). In these framework the DI has the possibility to use parametric as you can see here in the chapter called Dependency Injection (by ID).
Anyway I don't know the DI component of Symfony but it should be possible, but involve a philosophical change in the way object are registered in the container. I am working on a patch so you'll be able to see what I mean by code.
Comment #12
Sylvain Lecoy CreditAttribution: Sylvain Lecoy commentedA simple solution, that I personally don't like is to make the Factory ContainerAware.
Comment #13
Sylvain Lecoy CreditAttribution: Sylvain Lecoy commentedRestoring tag
Comment #14
Crell CreditAttribution: Crell commentedContainerAware factories are an acceptable thing: #1810912: [meta] Decide on pluggability
Comment #15
alexpottThis is now critical since comment #31 on #1798732: Convert install_task, install_time and install_current_batch to use the state system
Comment #16
chx CreditAttribution: chx commentedThere is no patch here. Working on one.
Comment #17
chx CreditAttribution: chx commentedNote that the fully proper solution of this depends on #1811730: Refactor Database to rely on the DIC solely.
Comment #18
chx CreditAttribution: chx commentedHow does this look? Yes, there is a Database::getConnection() with a @TODO to nuke it but otherwise it's a really simple patch that allows for plugging in whatever keyvalue service you might added to drupal_container(). Once Database is DIC-only we can remove the ugly -- and it's a very small, contained ugly.
Comment #19
webchickI temporarily reverted #1798732: Convert install_task, install_time and install_current_batch to use the state system, demoting to major.
Comment #21
BerdirTook me a moment to grasp but makes sense to me. We need to figure out what do with the global $conf stuff, whether we want to keep it like that even after variable_get() is gone or if it should be replaced with something else. This is a general question, this just follows existing patterns.
Here's a re-roll that updates the expirable database storage constructor and the tests to make them pass. Made connection a required argument, not sure why that was optional.
Comment #22
Crell CreditAttribution: Crell commentedThis seems like a reasonable approach, and easy enough to fix up after the DB-DIC issue lands.
Comment #23
chx CreditAttribution: chx commentedForgot to define $connection.
Comment #25
chx CreditAttribution: chx commented#23: 1809206_23.patch queued for re-testing.
Comment #26
katbailey CreditAttribution: katbailey commentedWhen reviewing the patch in #23 there was just one line that stuck in my craw - in the createStorage() method of StorageTestBase:
$stores[$i] = new $this->storageClass($collection, Database::getConnection());
Passing a db connection to the constructor regardless of class - ugh. I was determined to fix it, but it certainly was not a trivial task. This new patch succeeds in replacing that line, with this:
$stores[$i] = $this->container->get('keyvalue')->get($collection);
You can see from the interdiff what I had to do to get there but in a nutshell:
I also didn't understand why the existing patch didn't make use of the 'database' service that we have already - it just needed to be moved to the "bootstrap container" because of the current double-container nonsense (whose demise is in the foreseeable future).
Comment #27
BerdirI think those changes make a lot of sense. We have multiple issues that are partially moving the Database service directly into the drupal_container() more or less consequently and even more that could benefit from this. Time to get one of them in to get things unblocked.
Note that to apply this pattern consistently, there will need to be a separate KeyValueExpirable factory and a database.expirable.database as argument for that but that doesn't need to happen here. In fact, this class alone isn't useful and not really necessary here.
These todo's can be removed as well. We could also make those factories extend each other to only require an override of the get() for expirable.
Comment #28
katbailey CreditAttribution: katbailey commentedAh yes, missed that one - fixed, and made the Expirable factory extend the other as suggested.
Comment #29
BerdirIt's just for tests but this is conceptually still wrong IMHO. The KeyValueFactory shouldn't be used to access the expirable keyvalue store. Because that one is documented as returning a non-expirable one, $container()->get('keyvalue')->setWithExpire() doesn't work in normal circumstances.
We might be able to use the same implementation but need to make those setting names dynamically then.
Other than that, I think this is RTBC. I'm also fine with getting this in and adding a keyvalue.expirable factory once we need it, which means my cache_update/form_state issues if that's with others.
Comment #30
sunInstead of global $conf, I think this should be a parameter on the service container, no?
e.g.,
(ditto for %keyvalue.expirable.info%)
I'd even question whether we couldn't specify the individual service arguments in the parameter...?
Like this?
I thought we fixed that already?
obsolete?
Any reason for why can't we use the existing $this->container?
Comment #31
katbailey CreditAttribution: katbailey commentedRe #29 - yes, I totally missed that, I didn't realise we were dealing with two different interfaces so this needs to be reworked.
Re #30, the problem with using container parameters is that settings.php won't have access to the container - I realise it does now because of the way we have this bootstrap container defined inside the drupal_container() function, but that is a horrible hack that is going away soon. Once that happens, the kernel alone will manage the container and seeing as we're not using Symfony's Config component I don't think there's a lot we can do with configurable parameters. Anyway, this is an important issue to figure out - so I've put down my thoughts on it over at #1810912-16: [meta] Decide on pluggability
Will roll a new patch with the other fixes in the next couple of days unless someone else wants to...
Comment #32
katbailey CreditAttribution: katbailey commentedSo, my fix for #29 was to add Yet Another Factory Class :-P
In the interests of getting this patch in I'm not dealing with the issue of using $conf for service names as that is a bigger issue that needs to be settled elsewhere. As for the rest of #30:
How? I mean, we have to ensure the destructor is run before the table is deleted, otherwise we get a spectacular fail on that test.
Yes, because it's the bootstrap container, which we're trying to get rid of, and I think it makes sense to have complete control over the contents of the container for these tests.
Comment #33
sunHm, I see. #1810990: Teared down test environment leaks into parent site/test runner resolved the case of destructors running in PHP shutdown functions by resetting drupal_static() earlier, while still being in the test environment. We are also properly dropping all database tables of the test environment now, so this no longer needs to be done manually.
However, by the looks of the tearDown() code of this patch, what is potentially missing in the generic tearDown() is a total deconstruction of drupal_container(), so any destructors in registered + instantiated services are additionally triggered within the test environment and not later.
Can you try whether attached patch allows you to remove the special tearDown() code from the new tests?
Comment #35
BerdirAll of the test fails in sun's patch are due to "The service definition "service_container" does not exist.", as far as I could see, not sure what that is?
I'm not sure if the explicit set() is necessary, it might be enough to do a $container = NULL? In my database in DIC tests, that usually resulted in all services being destructed.
Comment #36
BerdirNot sure if we should use expirable here or not. We usually don't shorten identifier like this I think.
Also not sure about how we use and mix _ and . Is there a specific reason for the current names?
Oh, so service_container is a reference to the container itself, why could this cause the failures reported above?
Could we just add a property for $factory instead of the indirection through expirable?
Comment #37
katbailey CreditAttribution: katbailey commentedAddresses #36.
Comment #38
BerdirYes, this looks much better to me.
@sun: Can we handle the whole container handling in setUp()/tearDown() in a separate issue and get this in? While the tearDown() is a bit more elaborate than maybe necessary, this works and allows us to move forward with the cache_update/form removal issues and your unit tests remastered issue I think. That we no longer have to explicitly remove unit test tables affects some other tests to and deserves it's own issue too I think.
Given that, I think this is RTBC. As mentioned above, the not-so-nice global $conf stuff is being hashed out in #1810912: [meta] Decide on pluggability and this can easily be replaced once we have an agreement there.
Comment #39
sunYes, let's move forward here. Thanks!
Comment #40
msonnabaum CreditAttribution: msonnabaum commented#37 looks good to me.
Comment #41
catchLooks good to me too. Committed/pushed to 8.x.
Comment #42.0
(not verified) CreditAttribution: commentedUpdated issue summary.