| Project: | Drupal core |
| Version: | 8.x-dev |
| Component: | configuration system |
| Category: | task |
| Priority: | major |
| Assigned: | sun |
| Status: | needs work |
| Issue tags: | Configuration system, Issue summary initiative |
Issue Summary
Problem/Motivation
system_settings_form() has traditionally served two purposes:
-
It provides convenient no-thinking-required variable storage for module developers.
The config system no longer allows no-thinking-required variable storage; developers are required to ship an xml file with their module that specifies how they want their data stored. This is great for fixing the deployment problem, but somewhat inconvenient for module developers, who can no longer define their storage mechanism just by writing a form.
-
It keeps us from having to write a bunch of redundant submit handlers.
system_performance_settings_submit() currently looks like this:
<?php
function system_performance_settings_submit($form, &$form_state) {
$config = config('system.performance');
$config->set('cache', $form_state['values']['cache']);
$config->set('cache_lifetime', $form_state['values']['cache_lifetime']);
$config->set('page_cache_maximum_age', $form_state['values']['page_cache_maximum_age']);
$config->set('page_compression', $form_state['values']['page_compression']);
$config->set('preprocess_css', $form_state['values']['preprocess_css']);
$config->set('preprocess_js', $form_state['values']['preprocess_js']);
$config->save();
}
?>This is a lot of copy/paste that it would be nice not to have to do.
Proposed resolution
The original CMI commit pulled in a version of system_settings_form_submit() that relies on form_state to store the name of the config object to save the settings to. heyrocker says this was accidental and would like it reverted (see #1496638: Roll back system_settings_form_submit()).
Approaches suggested so far include the following:
-
Use $form_state['config'] to store config information in $form_state. Then system_settings_form_submit() can load up the config object specified in $form_state['config'] and save $form_state['values'] straight into that. This is the approach that got committed accidentally.
-
Use form_id to determine config information, such as $form['system.performance:caching.cache']. Then system_settings_form_submit() would check the form_id and save to the specified element (caching => cache) in the specified config object (system.performance).
-
Extend FAPI to take config objects and turn them into form elements that know where their data should be saved. (See #24.)
-
Pass config objects to validation/submit handlers, along with $form and $form_state. Form builders would have to write a submit function that migrates $form_values into the config object, perhaps using a simple helper such as https://gist.github.com/2172645. (See #20.) Then system_settings_form_submit() could either save the config object, or be removed entirely in favor of having the individual submit functions save their own config.
-
Dump system_settings_form() entirely and let module developers write their own submit handlers.
Remaining tasks
Come to a consensus and write a patch.
User interface changes
None.
API changes
The code at #35 proposes the following change to the use of system_settings_form:
Before (D7)
<?php
/**
* Implements hook_menu().
*/
function mymodule_menu() {
return array(
'admin/config/mymodule' => array(
'title' => 'Configure My Module',
'description' => 'Make my module work how you want it',
'page callback' => 'drupal_get_form',
'access arguments' => array('administer my module'),
'page arguments' => array('mymodule_settings_form'),
);
}
/**
* Form builder for admin settings form.
*/
function mymodule_settings_form($form, &$form_state) {
$form['mymodule_foo'] = array(
'#type' => 'checkbox',
'#title' => t('Do you want foo?'),
'#description' => t('Enable foo for all users on the site.'),
'#default_value' => variable_get('mymodule_foo', FALSE),
);
return system_settings_form($form);
}
?>After (D8)
<?php
/**
* Implements hook_menu().
*/
function mymodule_menu() {
return array(
'admin/config/mymodule' => array(
'title' => 'Configure My Module',
'description' => 'Make my module work how you want it',
'page callback' => 'config_get_form',
'access arguments' => array('administer my module'),
'page arguments' => array('mymodule_settings_form'),
);
}
/**
* Form builder for admin settings form.
*/
function mymodule_settings_form($form, &$form_state) {
$form['mymodule_foo'] = array(
'#type' => 'checkbox',
'#config' => 'mymodule.settings:foo',
'#title' => t('Do you want foo?'),
'#description' => t('Enable foo for all users on the site.'),
'#default_value' => variable_get('mymodule_foo', FALSE),
);
return system_settings_form($form);
}
?>Original report by beejeebus
Original title: getting from a flat $form_state['values'] key to the corresponding $form element
Originally filed against the forms system
for the CMI initiative, we ported system_performance_settings() to use the new config system.
in the process, we hit problems, as per these code comments i left in 'modules/system/system.admin.inc':
<?php
// TODO: This is a complete hack. The idea is - we need to stash some state
// with this form, so that when it comes time to save, we can go from a key
// in $form_state['values'] to that state. I started trying to put this state
// with each form element, but I have NFI how to get to a form element from
// a key in $form_state['values'].
?>the link for more context is:
http://drupalcode.org/sandbox/heyrocker/1145636.git/blob/refs/heads/8.x-...
the hack was to stuff a bunch of stuff in $form_state, so we could avoid having to get from a flat $form_state['values'] array back to arbitrarily nested $form elements.
was i just Doing It Wrong, or do we have a core FAPI issue here?
Comments
#1
I discussed this with effulgentsia at PNWDS at length. We reached the conclusion that the best would be if the form structure would always reflect the data structure while the form is being worked on (retrieve, alter, build, validate, submit) and only in the rendering phase would we move the elements in the place dictated by the DOM tree.
Most likely we would fire a hook from #pre_render of #type form elements. In D8 we can simply patch system_element_info and in D7 a contrib can alter it. Alternatively we can use some preprocess maybe?
Let's see an example. We have
$form['revision_information']['revision']in the node form. We change this to$form['revision']and in said hook we do <?php $form['revision_information']['revision'] = $form['revision']; unset($form['revision']);.Edit: to clarify this is just best practice. We do not need to change anything at all in form or render API to make it work. The forms themselves need to change, yes.
#2
Ehhh. That approach sounds like it'll make authoring the form as a module developer insanely more difficult.
What about a form_values_smoosh() function or something? :P
#3
i don't know where to go with this.
i spent a bit of time working on a form object that didn't smoosh 'structure and processing of form' with 'how will it look in html'.
i tried a layer of 'uglifying' the internal structures of the object to produce an array that would work in the render system, but it didn't really feel like much of an improvement.
so, yeah. i'm going to stop complaining and just live with it.
#4
I agree with chx that separating the field definitions from the display makes sense, but I think we can make it simpler and not require the display information to be injected in a hook. How much work would it be to just change the way forms are defined so that the two pieces are separate? Something like this
$form['caching'] = array(
'#type' => 'fieldset',
'#title' => t('Caching'),
);
$form['cache'] = array(
'#type' => 'checkbox',
'#title' => t('Cache pages for anonymous users'),
'#default_value' => $cache,
'#weight' => -2,
);
$form['cache_lifetime'] = array(
'#type' => 'select',
'#title' => t('Minimum cache lifetime'),
'#default_value' => variable_get('cache_lifetime', 0),
'#options' => $period,
'#description' => t('Cached pages will not be re-created until at least this much time has elapsed.')
);
$form['page_cache_maximum_age'] = array(
'#type' => 'select',
'#title' => t('Expiration of cached pages'),
'#default_value' => variable_get('page_cache_maximum_age', 0),
'#options' => $period,
'#description' => t('The maximum time an external cache can use an old version of a page.')
);
$form['#render'] = array(
$form['caching']['cache'],
$form['caching']['cache_lifetime'],
$form['caching']['page_cache_maximum_age'],
)
I'm not really familiar with form api but in theory couldn't something along those lines work without too much trouble?
#5
i think #4 is well worth looking in to.
#6
In general, $form_state['values'] is not flat. It only is when nothing sets #tree, which is usually the case for settings forms, but not for complex forms, like ones with Field API fields, which include necessary depth within $form_state['values'] for field name, language, item number (delta), and component (column).
That $form and $form_state['values'] both have depth, and often have *differing* tree structures is one of the things that introduces complexity and confusion for a lot of developers. Form elements contain a #parents property (once form_builder() is complete) for mapping from the element to its value location in $form_state['values'], but there's currently no facility to go the other way, from $form_state['values'] to the element, which is what this issue is about.
chx and I did discuss that there would be benefits to simplifying things by requiring that $form be structured the same as $form_state['values'] and introduce other patterns for providing render structure to $form, but that's just an idea at this point; it's something that needs to be worked out, which can take a while, so we should not let it impede the CMI initiative.
My first reaction to this issue is that there's something architecturally wrong with needing to go from data in $form_state['values'] to the form element that provided the UI for that data element. I see the use-case here for system_settings_form_submit(), but I wonder if it's because our architecture for system_settings_form() is flawed. Is there any other use-case from CMI where this comes up, or is it just about handling system settings forms?
#7
Thanks this information is really helpful.
So far this is just about settings forms, but we also haven't had much of a chance to experiment with other similar but more complicated implementations. The idea here is that we added information about what file each value is stored in to the form element declaration, then after submission we go find that data and save the values there. It seemed natural to just iterate through the values and go get the information related to each one, but it might be possible to do it the other way too. I will try and take more of a look at this.
#8
So here is system_settings_form_submit() as it stands now
function system_settings_form_submit($form, &$form_state) {
// Exclude unnecessary elements.
form_state_values_clean($form_state);
foreach ($form_state['values'] as $key => $value) {
if (is_array($value) && isset($form_state['values']['array_filter'])) {
$value = array_keys(array_filter($value));
}
variable_set($key, $value);
}
drupal_set_message(t('The configuration options have been saved.'));
}
So given what effulgentsia said in #6, it seems like we should be able to rewrite this to iterate the $form elements and map them back to $form_state['values'] instead of the other way around right? I'm curious what the implications of this would be, in theory it wouldn't break any existing forms right?
#9
Just brainstorming here...
While in general $form_state['values'] can be a tree, system_settings_form_submit() already makes assumptions that the top-level keys are special (at least in D7, where they're the basis for variable_set()). If it makes sense in CMI, to continue treating $form_state['values'] as essentially flat (the values can be arrays, but if only the top-level keys determine which config object manages it), then what if we extend the signature of system_settings_form($form) to system_settings_form($form, &$form_state, $config), where $config can contain the mapping array that you currently have in the sandbox as $form_state['config']. system_settings_form() can then internally set
$form_state['system_settings_config'] = $configor maybe if we want to flag this as private, even$form_state['_system_settings_config'] = $config, since only system_settings_form_submit() should need this, at least until we learn otherwise.My point is that I think the current $form_state['config'] mapping makes sense for now, but only in the context of system settings forms. However, if the use-case turns out to be more general, then we may need to change it to an iteration of $form. However, chx hates adding extra tree traversal in PHP, since recursion in PHP is slow, so let's hold off on it until there's a use-case.
#10
Actually, I'm still not clear on why we have this:
$form_state['config']['cache']['path'] = 'caching.cache';$form['caching']['cache']['#default_value'] = $config->get('caching.cache');
Why not:
$form['caching']['cache']['#default_value'] = $config->get('cache');In other words, what is 'path' actually needed for?
#11
In the new system configuration is stored in XML files. This is the file for the system performance settings:
http://drupalcode.org/sandbox/heyrocker/1145636.git/blob/refs/heads/8.x-...
As you can see, these values may be nested. Additionally, the settings for a single form may span multiple files. In this case, block module form_alters in a value, and as such its initial defaults are kept with the block module
http://drupalcode.org/sandbox/heyrocker/1145636.git/blob/refs/heads/8.x-...
So when we save data from a settings form, we need to know a) what file we're writing to ('name') and where in the xml we're writing to ('path') for each field we're saving. The initial idea was we would add this as a new piece of data to form element definitions (like '#config_path' and '#config_name' or something), and that is when we ran into the current problem of mapping from values back to form elements and made the mapping system instead which is a total hack.
Does this context help any?
#12
It makes sense to me that, in general, you want to support $form_state['values']['cache'] going into some XML file at the path /foo/bar/cache. It doesn't make sense to me that the path you want should have any relation to the structure in $form. In other words, if you have some reason for wanting it /caching/cache, then okay, but to me that's an independent decision from the UI for that data being within $form['caching']['cache']. In other words, storage needs depend on the data, and an HTML form needs to interact with that data, but there's no reason why storage location needs to be coupled to the HTML structure of the form.
So, actually, I think $form_state['config'], as you have it, is a fine approach for defining storage information for $form_state['values'], and that putting storage information directly into the $form element would actually be worse. This maintains what I think is the right decoupling: $form elements have a single #parents key that controls the name attribute for HTML, the result of which determines the structure of $form_state['values']. A separate variable, $form_state['config'] provides information on how to store $form_state['values'] that's completely independent of the structure of $form.
#13
It may feel wrong architecturally to have storage coupled with what is mostly HTML structure, but that's how system_settings_form() currently works, and I have to admit I find it kind of nice from a DX perspective.
I'd love to see storage locations determined based on form structure unless you're doing something like the block module does here, in which case you have to specifically declare the config tree that data needs to be saved to. Really, I'm not sure why we can't just key it off of form id? In this case it'd look something like config/system-performance-settings/cache_lifetime/0, which seems pretty sensible to me. Perhaps that won't make as much sense on all system forms, but I just don't want to see the experience of authoring these forms get more complex than it already is.
#14
Hmmm, that does make sense. However it also seems like it is going to add some complexity to creating these forms. One of the advantages of system_settings_form() is its simplicity. Of course in the past we didn't have to specify where stuff got saved, we just repurposed the element name for this. I was just brainstorming this in IRC with msonnabaum and beejeebus and some things were brought up
- It's possible that system_settings_form() just doesn't make as much sense as it used to in the new storage paradigm. We could just dump it and write manual submit handlers for all these forms. However, this would increase code weight quite a bit. There are 26 instances of system_settings_form() in core alone.
- It was pointed out more than once that $form is already used for a lot of things beyond just how the elements are represented in HTML. For instance we add submit functions there. This break is not as clear as it seems at first glance.
- We tossed around varying ways of repurposing form_id and/or form element names for this purpose. For instance $form['system.performance:caching.cache'] would save to the caching/cache element in the file system.performance.xml. Or maybe the form_id becomes the name of the file that the form will save to (although forms can save to multiple files, which is another problem.)
Ultimately, I think, this is something we need to revisit in CMI - how are we going to approach forms saving to files in general, and is the current approach the best one we can take. This merits a separate discussion I think, so I'm going to open that up elsewhere and mark this postponed for the moment.
#15
#16
I changed my mind and decided to refocus this issue instead. I think we have two questions at hand
- Do we just want to dump system_settings_form() and write manual submit handlers for everything?
PRO: Easily implemented without any changes to form api, removes a magic 'drupalism'.
CON: Increased code weight, somewhat decreased DX and increased complexity of implementing simple forms.
- Do we figure out a way to implement system_settings_form() using CMI
PRO: Retains a somewhat simple way to implement simple forms.
CON: It will by necessity be more complex than before because we have to specify storage, not sure the best way to approach it (although it is probably by using $form_state, which seems to be the least ugly of the solutions so far.)
Personally I am leaning towards just not using system_settings_form() in the absence of a good solution but I'd like to hear other thoughts and approaches.
#17
I'm not ready to put a formal +1 vote in, but system_settings_form() tends to be a fairly complex "magic" thing to explain to new Drupal developers, so the consistency argument is not to be discounted. At the same time, I do appreciate the nice DX shortcut it allows once you figure it out.
Tagging to see what the thoughts are of folks working on decreasing the Drupal learning curve.
#18
Also tagging cmi which I forgot to do before
#19
I kinda like #14's
$form['system.performance:caching.cache']. It's somewhat verbose, but that also makes it clear what's what. Also, this means that the data, when passed on, comes with the right information, like an address on an envelope. It makes sense to me.Given that I don't know much about FAPI, I guess that's to some extent relevant for the "complexity problem" mentioned in #17.
#20
Discussed this a bit with heyrocker in IRC, and wanted to submit a potential solution here.
Someone else previously mentioned passing configuration objects to forms. That's potentially a really good idea (though it may need to be an array of configuration objects depending). In this way we can pass along configuration objects to the validate and submit handlers, and this ends up being very useful because in validate we have canonical "this is the current value that you care about" data that we can compare against (when relevant) and in submit, we can simply migrate values from the form state into the appropriate configuration object. The magic part from there could just be a submit handler that iterates over all the config objects in the array and saves them for you. To put this into heyrocker's previous pro/con format:
PRO: Should actually provide a better DX experience. Won't require forms to get ugly and represent their schema or anything crazy, and will be consistent across all forms that utilize configuration (which should fundamentally be everyone).
CON: Will require submit handlers for anyone currently using system_settings_form(), but these submit handlers would be super straight forward since they're just migrating values in the form state into the configuration objects that the form is controlling.
Eclipse
#21
I agree with #20. In my opinion, the only really confusing thing about
system_settings_form()at present is that we really don't document it anywhere. The API doc is incredibly sparse, and there's no reference to it in FAPI docs that I could find. There's a a few references in the handbook (http://drupal.org/node/1111260), but those could be made a bit clearer/more prominent as well. So I think with good documentation linked in the right places, some of the confusion could be alleviated.Sure, new users will tend to think in terms of the fields they want to put on their forms, not in terms of the implied data structure. But I think it will make it a lot easier for modules to support configuration management, and a convenient Drupalism is better than a complicated one. :)
#22
yeah, i argued for something like #20 in IRC. more code, but much simpler, discoverable code, and we can create a really dumb helper(s) that this code can use.
#23
Note that even if we pass around config objects, I think we will still need the mapping array right? Because we will need to know which form element maps to which value in the config object.
Ultimately if we still have to create submit functions, I'm not sure this would buy us much. If we can't make it as magical as the current system is, I think I'd rather just not do it at all.
#24
#23: Well, my thought was that we would add form elements from the config objects. Like, a widget for each setting, kinda like Field API (except better DX, and supporting the whole config object better than Field API supports the entity). Instead of writing mapping arrays or submit handlers for every single config form, we provide a Configuration Form API that does this magically.
Edit: The interface for the CF API could be as simple as a magic naming convention. (Though, note that I am not fond of
this-that:foo_bar-baz:various-delimiteras a pattern.) Or it could be some#property. Or something totally different if we want, like OO, sky's the limit. The point is that we can provide an API that automatically handles the necessary mapping/submission handling.Edit 2: One of the down-the-road steps for the Entity API improvements is to provide
$entity->form()or suchlike. You get a base form automatically, and then you alter it how you like. The burden is on the API to provide the data, not on the user. That's what I'm thinking.#25
> system_settings_form() tends to be a fairly complex "magic" thing to explain to new Drupal developers,
I can still remember discovering system_settings_form() as a Drupal newbie, and thinking 'Hey wow! Drupal has really neat things that save me doing boring grunt work! That's so cool!'
So a big -1 to removing this 'magic drupalism'. It's good magic.
If the problem is needing to go from $form_state['values']['parent_with_tree']['element'] back to $form['parent_with_tree']['parent_without_tree']['element'], then couldn't the form building process build up an array with the same structure as $form_state['values'], but where instead of form values we have an array of $form parentage, so:
$form_state['build_lookup']['parent_with_tree']['element'] = array('parent_with_tree', 'parent_without_tree', 'element');IIRC that's effectively the problem I solved for a one-off in http://drupal.org/project/form_email_confirmation.
Alternatively, why not require form elements that want magic saving with system_settings_form() to set whatever stuff it needs?
#26
Moving this to configuration system so I can track it more easily.
#27
#28
One wonders here -- the problem as stated start from form_values. But, we can grab the $form itself , use #value where given and just use that together with #parents . form_state['values'] is just convenience, a denormalization if you want.
#29
Roughly (I moved array_filter to be a top-level property):
<?php
function system_settings_form_submit($form, &$form_state) {
_system_settings_set_vars($form, $form);
drupal_set_message(t('The configuration options have been saved.'));
}
function _system_settings_set_vars($element, $complete_form) {
foreach (element_children($element) as $key) {
_system_settings_set_vars($element[$key], $complete_form);
}
if (!empty($element['#input'])) {
$value = $element['#value'];
// TODO: form_state_values_clean().
if (is_array($value) && !empty($complete_form['#array_filter'])) {
$value = array_keys(array_filter($value));
}
variable_set($element['#parents'][0], $value);
}
}
?>
#30
I'm going to do some thinking and talking about this at the Denver code sprint and see if I can come up with a patch to argue over.
#31
I was thinking of writing a patch, but it turns out the $form_state['config'] approach got committed already. heyrocker suggested that should be reverted until this issue is resolved, so I created an issue over at #1496638. I also added an issue summary to this issue.
The thing I liked about system_settings_form() in D<8 is how it made data storage easy. (Too easy, really.) You didn't have to think about your data storage format. Now that the config system is forcing us to think about our data storage format, we lose that simplicity. No matter what we do to system_settings_form(), the module developer still has to create an xml file, so we're not going to get the same magic we used to have. That magic is going to have to come from somewhere else. So to me, replacing the easy DX of system_settings_form() also means providing an easy way for developers to create that initial xml file. I filed #1496930: Don't make module developers write XML by hand when defining default configuration to think about the best way to do that.
#32
A first attempt (actually two if you take git history into account) lives in the config-settings-form-1324618-sun branch.
The problem space turned out to be much more complex than I originally imagined, so I'd love to collaborate in the sandbox and on IRC.
#33
FWIW, current state of affairs.
Git history helps to understand this better.
#35
Slept over this. Let me introduce to you:
Configuration settings forms NG
This gives proper hook_form_alter() and theme hooks for free. And, also paves the way for the discussed vision of leveraging a generic property API (à la fields with meta info, data types, validation, and formatters for individual config settings).
Since this is a total revamp, the new code lives in the config-settings-art-1324618-sun branch.
Also bumping to critical, since we should resolve this ASAP, before converting more and more module variables to the config system.
#36
Tagging for an issue summary. It'd be great to document some before/after code to get a sense of what this is doing.
#37
> Kill the dated and ugly anti-pattern of calling system_settings_form() at the end of form constructors.
How is that an anti-pattern? It's nice and simple.
That said, if you can replace it with even simpler, I'm all for it.
Reading the patch quickly, I am guessing that you simply do this in any form:
+ $form['simple'] = array(+ '#type' => 'checkbox',
+ '#title' => 'Simple',
+ '#config' => 'config_test.settings:simple',
+ );
and the result is that you magically get:
- the config value shown as the form element's default
- the user-entered value saved to config when the form is submitted.
If that is the case, then it's new magic I am all in favour of :)
EDIT: However, there is one negative to this change I can see: harder to learn and harder to find the documentation. system_settings_form(), once you found it, was a function with its own documentation which explained how to use it. FormAPI is not a terribly well documented part of Drupal. This change would need an accompanying new section in the Big Monster FormAPI Document.
#38
There are a couple pieces of magic that could make this a little easier
- If a form-level config object is specified ($form['#config'] = 'system.performance') then this is the default file used for all settings on the form. Any field-level-items without a colon are just specifying a key in that file.
- If there is no #config item on a specific field, then just use the field's key as the config key.
For simple forms then, you would just need to specify a form-level #config item, and the rest could take care of itself.
Still going through the code.
#39
Hah I see some of this was already started in the code. Great minds etc.
#40
Added issue summary (proposed API changes) showing before and after.
#41
And now, approach no. 3) KISS.
Based on #35, thus sharing some implementation concepts, but much simplified.
I've added extensive docs to config_get_form() this time to clarify the design goals and also simple vs. advanced usage.
Code for this approach/direction lives in the config-settings-kiss-1324618-sun branch.
One detail that was definitely wrong with #35 was the attempt to automate the assignment of #default_value element properties. While that sounds like a nice convenience behavior, it's prone to break, badly, because HTTP POST data !== Form API values !== Configuration object data values. That does not completely invalidate the other code of #35 though, so I'm going to remove that #default_value handling (and addition of Form API #preprocess) from that branch.
#42
Needs to be redone in light of #41
#43
I've forked the Form API fix for this special usage into #1591726: Missing form_id, form_build_id, and form_token when using custom #parents and #tree = TRUE on $form itself
#44
This is not a release blocker, and while current code is not as nice as it could be (I'll even go so far as ugly), it works fine. Downgrading to major.
#45
One potential issue with the approach in #41 is that all the config data is replaced when saved. Which means (if I'm not mistaken) that the form has to expose every config setting for the config objects you are administering. This limitation was not present in system_settings_form().
On one hand this is probably a good thing as this should mean that there are less orphaned keys. On the other, it means that the flexibility through the ability to inject config into another config's form might not be as useful as intended.
#46
Hm. That's also a problem because we do have several hidden, advanced configuration options that we don't want to expose a UI for, at least in core. See settings.php for a bunch of them.
#47
Pro:
I like the way the function wraps the standard syntax of setting things into the config. It simplifies effort for the developer as it reduces the amount of syntactical push-ups that we'll have to do in order to use the new Config API. Every module that sets settings will need to do this.
While at the same time developers using the Config API outside of the context of using a form to set and get values can still use the config api directly.
#48
+++ b/core/lib/Drupal/Core/Config/DrupalConfig.php@@ -114,6 +114,17 @@ class DrupalConfig {
+ public function setData(array $data) {
+ $this->data = $data;
+ return $this;
+ }
This is already in HEAD, from #1470824: XML encoder can only handle a small subset of PHP arrays, so switch to YAML
+++ b/core/modules/config/config.module@@ -1 +1,127 @@
+ $form_state['build_info']['base_form_id'] = 'config_settings_form';
This is an unfortunate consequence of drupal_retrieve_form() doing something with 'base_form_id' before 'wrapper_callback' is called. We should fix that, and move this line into config_settings_form_wrapper().
+++ b/core/modules/config/config.module@@ -1 +1,127 @@
+ // Load, replace, and save the configuration objects.
+ foreach ($form_state['values']['config'] as $name => $data) {
+ config($name)->setData($data)->save();
+ }
I agree with the issue comments above that we should do something more like mergeData() rather than setData().
+++ b/core/modules/config/tests/config_test/config_test.module@@ -0,0 +1,68 @@
+ '#default_value' => $config_other->get('set.first'),
We currently have #value_callback as part of FAPI, but it's overloaded with both input-to-value mapping and default value setting. We should decouple those and introduce a #default_value_callback (or maybe #default_value_callbacks), so that we can have a central function for this, since we have all the info we need in $element['#parents'].
#49
Also, let's change a real form, like system_performance_settings(), to use this approach, as part of this issue.
#50
Also, instead of a config_get_form() function, how about using hook_forms()? It would require drupal_retrieve_form() to call that hook and apply the definitions even when the $form_id function exists, but I think that's appropriate.
#51
@effulgentsia: Note that there is a second/alternative approach in #35 - let's make sure to also review that. :)
Mmm, actually, this particular code is not a work around of any unfortunate consequence, but instead, a clever (ab)use of base form ID concept we have in Form API already. However, this concept and its related functionality is normally only triggered for actual/true base form constructors; i.e., when you try to build a $form_id, for which no equivalent form constructor function name exists; in which case hook_forms() is called, which supplies the actual callback to execute. Only in that case, a base_form_id is assigned and the corresponding form alter as well as theme hooks are taken into account.
Here, the idea is to not actually use or require the usage of hook_forms() (or any certain function naming pattern), but instead, only leverage the base form functionality, which is baked into Form API already. In other words, I don't really want to change drupal_retrieve_form(), because it does exactly what I want ;)
Diving into #1496542: Convert site information to config system made me realize that this is indeed not really possible, so I'm going to change that into individual ->set() calls in a follow-up patch.
However, we will have to find a solution for the actual problem space of retaining orphan/obsolete keys and sub-keys within a configuration object forever. That's not really limited to settings forms, so I've created #1601168: Orphan/obsolete keys and sub-keys *within* configuration objects are retained *forever*
Nope, as mentioned in #41 already: The idea of automatically assigning #default_value properties is utterly bogus. While that sounds like a nice convenience behavior, it's prone to break, badly, because HTTP POST data !== Form API values !== Configuration object data values.
#52
Had a great call and discussion with @effulgentsia about this issue.
Let's introduce approach #3:
#type 'config'- Removes the unusual #tree = TRUE on the top-level $form.
- Introduces element #type 'config' as a "container"-alike element that developers are used to, at least in some form.
- Thus, removes at least some of the hard bindings on the $form array structure.
- If there was a element-level #submit, then it would allow for mixing + merging configuration settings forms as you like.
Also (and this really helped, so thanks!), as someone requested, this patch contains a second example, the actual conversion of the system.performance settings form to this 3rd approach. (I'll try to incorporate this into the 1st and 2nd approach as well.)
That said:
Please note that the goal for this issue is not to find a picture perfect solution. The goal is to find a working and basically acceptable approach for handling configuration system settings forms, so the plethora of config conversion patches that are happening in parallel right now do not continue to still use system_settings_form() and thus write variables for stuff that's actually converted into config already.
As already hinted at in #1448330-19: Implement internationalization of configuration [no patch], the currently envisioned and final goal for D8 would rather be to abstract Field API's concept of types, meta data, etc not only to the entity level, but instead, turning it into a completely generic property API, so the configuration system is able to leverage the identical functionality. Consequently, this will mean that we're going to revamp/refactor all configuration forms throughout core onto that concept, once ready.
Thus, the goal here is to provide a temporary transition path only. It should make sense, it should work, and it should provide some sane DX (especially important for newcomers/novices). But it should not and does not attempt to be the "final" solution for D8.
P.S.: Given the above battle plan, in case you're asking yourself, what's so different between "redoing config forms" and "redoing config forms" as stated in #1599554: Setup standards/guidelines for how to convert variables into configuration, the answer is: Forms != Data. The form abstraction layer that exposes the interface to manage the data can happily change. The underlying data should only be changed once. As you know, changes to data require upgrade paths. The data format and structure most likely won't change anymore. So we better migrate the data correctly, and only once.
P.P.S.: Had to rebase all of my config-settings branches against latest 8.x, because cherry-picking the YAML commit only "seemed" to have worked out fine at first - only found out a couple of commits later on that
git diff 8.x...HEADproduces 200 KB of forward-and-reversed history. :( Looks like I'm ditching that approach and sticking back to a -base branch per feature branch.#53
The last submitted patch, config.settings.52.patch, failed testing.