Problem/Motivation

There is a valid use case to support creating configurable fields, whether through the UI or in code, that have custom storage.

With base fields this is simple, we can just call setCustomStorage(TRUE) and we're done. No field tables will get created for that field. However, if you want to implement this using base fields you can, but you would need to take care of your field configuration for instances, and settings etc...

However, this is not possible right now if you are using a configurable field, and the FieldStorageConfig class. hasCustomStorage is hard coded to FALSE. To achieve this currently, you need to alter the field definition in hook_entity_field_storage_info_alter, and wrap the FieldStorageConfig instance with a decorator class to override the custom storage. Like https://github.com/damiankloip/og/blob/audience-field-custom-storage/src...

Proposed resolution

Add a setCustomStorage() method to FieldStorageConfig so it can easily be overridden via hook_entity_field_storage_info_alter. A step further would be adding custom_storage to the actual configuration but that would require some config changes. So just adding the runtime setter is a pure addition.

Add $custom_storage as a property and exportable configuration property for FieldStorageConfig objects. We can still default this to FALSE, but allow configurability. In the future we can then apply this to any configurable field easily to suit any use cases.

Remaining tasks

User interface changes

API changes

Data model changes

CommentFileSizeAuthor
#80 interdiff.txt1.08 KByched
#80 2571533-custom_storage_on_config_fields-80.patch13.31 KByched
#79 interdiff-2571533-79.txt1.04 KBdamiankloip
#79 2571533-79.patch13.03 KBdamiankloip
#74 interdiff-2571533-74.txt1005 bytesdamiankloip
#74 2571533-74.patch13.27 KBdamiankloip
#71 interdiff-2571533-71.txt985 bytesdamiankloip
#71 2571533-71.patch13.27 KBdamiankloip
#56 interdiff-2571533-56.txt855 bytesdamiankloip
#56 2571533-56.patch13.15 KBdamiankloip
#52 interdiff-2571533-52.txt803 bytesdamiankloip
#52 2571533-52.patch12.31 KBdamiankloip
#44 interdiff-2571533-44.txt634 bytesdamiankloip
#44 2571533-44.patch11.55 KBdamiankloip
#38 interdiff-2571533-37.txt1.25 KBdamiankloip
#37 interdiff-2571533-37.txt0 bytesdamiankloip
#37 2571533-37.patch11.49 KBdamiankloip
#29 interdiff-2571533-29.txt5.87 KBdamiankloip
#29 2571533-29.patch10.74 KBdamiankloip
#24 interdiff-2571533-24.txt4.89 KBdamiankloip
#24 2571533-24.patch4.87 KBdamiankloip
#20 interdiff-2571533-20.txt3.48 KBdamiankloip
#20 2571533-20.patch4.87 KBdamiankloip
#15 interdiff-2571533-15.txt3.83 KBdamiankloip
#15 2571533-15.patch4.43 KBdamiankloip
#13 interdiff-2571533-13.txt3.55 KBdamiankloip
#13 2571533-13.patch4.75 KBdamiankloip
#8 interdiff-2571533-8.txt5.31 KBdamiankloip
#8 2571533-8.patch5.69 KBdamiankloip
#2 2571533.patch1.22 KBdamiankloip
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

damiankloip created an issue. See original summary.

damiankloip’s picture

damiankloip’s picture

Status: Active » Needs review
swentel’s picture

Works for me. I guess you're working on the tests ? :)

One thing that I've been wondering is how field purging will behave when a such a field is deleted, we'll definitely have to check that.

damiankloip’s picture

I think there is no data, so no problems? Just the configuration will get removed. Or do you see other issues? Yes, I will be working on some tests. Just wanted to get the basic patch up first :)

swentel’s picture

Yeah, I'm assuming that as well, also, you can't uninstall a module until the fields it owns are gone, so your alter runs, so yeah, we're probably fine :)

plach’s picture

I'm personally fine with this approach, however I vaguely remember discussing it with @yched, so it would better to have his signoff. I know also @Berdir had ideas about this, so it would be better to gather some more feedback.

+++ b/core/modules/field/src/Entity/FieldStorageConfig.php
@@ -455,7 +462,21 @@ public function getSchema() {
+   *   Pass FALSE if the storage takes care of storing the field,
+   *   TRUE otherwise.

80 chars

damiankloip’s picture

FileSize
5.69 KB
5.31 KB

Here is some more work with some added tests, the test fails but this is what I think it should do :) Using this works for me testing with my D8 install. I get no table in the DB - here it seems we do. Answers on a postcard.

Status: Needs review » Needs work

The last submitted patch, 8: 2571533-8.patch, failed testing.

swentel’s picture

+++ b/core/modules/field/src/Tests/FieldCrudTest.php
@@ -138,6 +138,41 @@ function testCreateField() {
+   * Test creating a field instance with custom storage set.

instance ? instance ? That would fit on a post card yes for D7 :)

The last submitted patch, 8: 2571533-8.patch, failed testing.

damiankloip’s picture

What can I say, I'm old school.

damiankloip’s picture

Status: Needs work » Needs review
FileSize
4.75 KB
3.55 KB

Spoke to berdir, having a new annotation property would be better. Having to keep parity between the alter and/or config for custom setting for the field type. This way all fields would inherit the behaviour they needed. Seems safer and much less likely to break for people.

Berdir’s picture

  1. +++ b/core/modules/field/src/Entity/FieldStorageConfig.php
    @@ -146,6 +146,13 @@ class FieldStorageConfig extends ConfigEntityBase implements FieldStorageConfigI
     
       /**
    +   * Flag indicating whether this field has custom storage.
    +   *
    +   * @var bool
    +   */
    +  protected $custom_storage = FALSE;
    +
    +  /**
    

    This can be removed now.

  2. +++ b/core/modules/field/src/Tests/FieldCrudTest.php
    @@ -138,6 +138,41 @@ function testCreateField() {
    +   * Test creating a field instance with custom storage set.
    

    as @swentel said, just field.

  3. +++ b/core/modules/field/src/Tests/FieldCrudTest.php
    @@ -138,6 +138,41 @@ function testCreateField() {
    +    $this->fieldStorage = entity_create('field_storage_config', $this->fieldStorageDefinition);
    ...
    +    /** @var \Drupal\Core\Field\FieldConfigInterface $field */
    +    $field = entity_create('field_config', $this->fieldDefinition);
    +    $field->save();
    

    If I don't then someone is going to tell you to use FieldConfig::create() and FieldStorageConfig::create(), should also make the @var unnecessary.

  4. +++ b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldType/CustomStorageTestItem.php
    @@ -0,0 +1,47 @@
    +    // No schema columns.
    +    return ['columns' => []];
    +  }
    

    So you need to return columns?

damiankloip’s picture

FileSize
4.43 KB
3.83 KB

1. Removed.
2. Fine! :)
3. Yes, much better, that was an artefact of c&p really.
4. Yes, but added a fix in FieldStorageConfig instead in the latest patch. BaseFieldDefinition merges all keys ( 'columns', 'unique keys', 'indexes', 'foreign keys'), where are FieldStorageConfig does not contain 'columns'.

Status: Needs review » Needs work

The last submitted patch, 15: 2571533-15.patch, failed testing.

damiankloip queued 15: 2571533-15.patch for re-testing.

damiankloip’s picture

Status: Needs work » Needs review
yched’s picture

Yeah, I guess the reasoning was "if you are custom_storage, it means there is associated code somewhere that takes care of your storage, which means your code relies on your field, which is what base/code-defined fields are for". Things like : if the field is configurable, it can be removed. What happens then with the associated code ?

I'm fine with allowing the flexibility and letting people figure out the subtelties though :-)

As discussed with @Berdir and @damiankloip in BCN, I don't think this should involve the *field type* saying "I have custom storage", since that's not how it works for base fields : any base field can say "I'm of this field field type, and btw, I have custom storage", and the field type has no say in the process. FieldStorageDefinitionInterface::setCustomStorage() means any field of any type can have custom storage.

So we agreed that if configurable fields can have custom storage, it should be a regular property in the FieldStorageConfig yaml (most probably without a UI ?)
Then the case of OG ("for any field of type 'og_audience' that the admins create in Field UI, make sure it has custom_storage = TRUE") can be adressed by hook_entity_create() on 'field_storage_config' entities, since by the time the FSC entity is created, it already has a field type you can reason on.

damiankloip’s picture

FileSize
4.87 KB
3.48 KB

Thanks yched, that sums it up perfectly. Anything implementing this functionality can take responsibility for setting this when the field is created. E.g. In OG we will implement this, as mentioned in hook_entity_create, to force this setting to TRUE for the og_membership_reference field type.

Status: Needs review » Needs work

The last submitted patch, 20: 2571533-20.patch, failed testing.

The last submitted patch, 20: 2571533-20.patch, failed testing.

yched’s picture

  1. +++ b/core/modules/field/src/Tests/FieldCrudTest.php
    @@ -138,6 +138,41 @@ function testCreateField() {
    +   * @see field_test_entity_bundle_field_info_alter
    

    No longer relevant :-)

  2. +++ b/core/modules/field/src/Tests/FieldCrudTest.php
    @@ -138,6 +138,41 @@ function testCreateField() {
    +    $field_storage = FieldStorageConfig::create($field_storage_definition);
    ...
    +    $field = FieldConfig::create($field_definition);
    

    Nitpick, we could inline the definition arrays

  3. +++ b/core/modules/field/src/Tests/FieldCrudTest.php
    @@ -138,6 +138,41 @@ function testCreateField() {
    +    $this->assertIdentical([], $field_storage->getColumns());
    +    $this->assertIdentical([], $field_storage->getSchema()['columns']);
    

    Not sure why we need this. The schema() and properties() should simply be the regular ones for the field type, we just want to ensure no table was actually created ?

    Which brings : not sure we actually need a specific 'test_field_custom_storage' to test this, using any field type shoud work ?

damiankloip’s picture

Status: Needs work » Needs review
FileSize
4.87 KB
4.89 KB

Status: Needs review » Needs work

The last submitted patch, 24: 2571533-24.patch, failed testing.

The last submitted patch, 24: 2571533-24.patch, failed testing.

damiankloip’s picture

Hmm, ok. I think I just need to update default config, just for tests. In reality this should not be an issue/break any existing config.

yched’s picture

Thanks @damiankloip - RTBC for me when green :-)

damiankloip’s picture

Status: Needs work » Needs review
FileSize
10.74 KB
5.87 KB

Thanks yched!

yched’s picture

Status: Needs review » Reviewed & tested by the community

Thanks !

jibran’s picture

Can we have a change notice and proper functional tests for a field with custom storage set to true?

damiankloip’s picture

Why do we need additional tests past this? Other stuff doesn't have it and I'm not sure I see any benefit. This is just a property on a config entity, nothing more.

dawehner’s picture

I don't see the point in the change record. If people want to use it, they will look at the interface anyway. People don't have to update existing code.
Too many change records cause a big issue.

jibran’s picture

Why do we need additional tests past this? Other stuff doesn't have it and I'm not sure I see any benefit. This is just a property on a config entity, nothing more.

IMHO we should have a valid use case in the form of tests in core so that we can make sure it actually works and in that process if we have to fix/update somethings in the base class then we'll do it here and we don't have to wait for followup issues to fix that stuff.

I don't see the point in the change record. If people want to use it, they will look at the interface anyway. People don't have to update existing code.

If one has to look at the interface to understand it then IMHO the documentation is not covering everything.

Too many change records cause a big issue.

I don't think so. Big issue for whom?

This is just my opinion if you don't agree with it please go ahead :) I didn't block the issue on any of those items. It was merely a suggestion.

yched’s picture

The test added there checks that no table gets created when the field storage is saved. I guess we could additionally check that you can create an entity, put a value in the field, save it without any error, and that the field comes up empty on next load.
I don't think there's much more to test here ?

Side note : is the clearCachedFieldDefinitions() call really needed ?

damiankloip’s picture

Yes. I guess there is no harm in adding that for saving an entity. I'll add something soon (just about to fly).

damiankloip’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
11.49 KB
0 bytes

Here is some additional test coverage for what we discussed above. Thanks yched, jibran.

So do we want a short change notice then? I am not sure we need it, if others do, I am fine with that too. It will be short and sweet I think.

damiankloip’s picture

FileSize
1.25 KB

Sorry, actual interdiff.

damiankloip’s picture

damiankloip’s picture

Priority: Normal » Major
Issue tags: +Contributed project blocker

Tentatively bumping too.

yched’s picture

Status: Needs review » Reviewed & tested by the community

Thanks @damiankloip, looks good !

Nitpick : can we add a comment above the "tableExists" assert like : "Check that no table has been created for the field", and group the second part a bit more ? (better visual overview of the two things we're testing here)

Not a blocker obviously, so, RTBC.

Change record : well, yeah, let's create a tiny one to avoid controversy :-)

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 37: 2571533-37.patch, failed testing.

yched queued 37: 2571533-37.patch for re-testing.

damiankloip’s picture

Status: Needs work » Needs review
FileSize
11.55 KB
634 bytes

Sure, why not. Added a quick comment. Just setting back to RTBC now to save another round :)

damiankloip’s picture

Draft CR to appease all: https://www.drupal.org/node/2575579

damiankloip’s picture

Status: Needs review » Reviewed & tested by the community
jibran’s picture

Thanks @damiankloip looks perfect now.

catch’s picture

Status: Reviewed & tested by the community » Needs review
Issue tags: +D8 upgrade path

So the patch looks good, but don't we need an upgrade path to set the property to FALSE for existing fields?

yched’s picture

re: update - yeah, that's probably right (discussed with @alexpott and he agrees). The new property has a default value so things would work if we didn't write an update hook, but then the yaml would change on next load/save, that would be slightly confusing ("But I didn't change anything ?")

- either we write a hook_update_N(), and directly read and write the raw config records (FieldStorageConfig::load() & ::save() are not allowed there)
- or we write a hook_post_update_NAME() that simply loads and saves the FieldStorageConfig entities (and the default value will automatically get added and saved) - like foreach (FSC::loadMultiple() as $storage) {$storage->save();}

Both work, the latter is probably simpler to write :-)

damiankloip’s picture

Fair point, this will avoid any confusion/panic I guess. Thanks yched, simple sounds good! :)

catch’s picture

For me the latter is exactly what we added that hook for :)

damiankloip’s picture

FileSize
12.31 KB
803 bytes

How about this?

Not sure about the t() usage, I just followed block.post_update.php as an example.

Status: Needs review » Needs work

The last submitted patch, 52: 2571533-52.patch, failed testing.

yched’s picture

Hm - then UpdatePostUpdateTest fails because it hardcodes the list of expected hook_post_update_NAME() implementations that exist, and explicitly adds the current one implementation we have in core, block_post_update_disable_blocks_with_missing_contexts().

It seems weird to have to update the expected list in UpdatePostUpdateTest each time we add a hook_post_update_NAME() ?

yched’s picture

Left a note in #2538108: Add hook_post_update_NAME() for data value updates to reliably run after data format updates, but for now @catch agreed on IRC that we should just update UpdatePostUpdateTest here to make it pass...

damiankloip’s picture

Status: Needs work » Needs review
FileSize
13.15 KB
855 bytes

Yes, that seems weird, and kind of crappy to have to do that :)

Added to that test now, thanks again yched! Tested and locally passing ok.

yched’s picture

Status: Needs review » Reviewed & tested by the community

Airport RTBC-if-green :-)

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 56: 2571533-56.patch, failed testing.

The last submitted patch, 52: 2571533-52.patch, failed testing.

damiankloip queued 56: 2571533-56.patch for re-testing.

damiankloip’s picture

Not sure if that fail is legit or not. Let's see.

damiankloip’s picture

Status: Needs work » Reviewed & tested by the community

As expected... moving back.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 56: 2571533-56.patch, failed testing.

yched’s picture

Hm, actually I just learned that post_update hooks were supposed to go in *.post_update.php rather than in *.install.inc
See #2578249-24: Some e_r fields get the wrong Selection handler and interdiff over there (one of those two patches will need a reroll after the other one gets in, obviously)

damiankloip’s picture

This does add a post_update.php file. Or is this just a general observation? :)

damiankloip queued 56: 2571533-56.patch for re-testing.

The last submitted patch, 56: 2571533-56.patch, failed testing.

damiankloip’s picture

Oh. Will reroll this.

yched’s picture

This does add a post_update.php file

HAH ! Indeed it does, I didn't even notice. Apparently I was the only one to not know about that. Don't mind me, sorry about the noise ;-)

The last submitted patch, 56: 2571533-56.patch, failed testing.

damiankloip’s picture

Status: Needs work » Needs review
FileSize
13.27 KB
985 bytes

Status: Needs review » Needs work

The last submitted patch, 71: 2571533-71.patch, failed testing.

The last submitted patch, 71: 2571533-71.patch, failed testing.

damiankloip’s picture

Status: Needs work » Needs review
FileSize
13.27 KB
1005 bytes

UpdatePostUpdateTest is *ridiculous*

damiankloip’s picture

Issue summary: View changes

Status: Needs review » Needs work

The last submitted patch, 74: 2571533-74.patch, failed testing.

The last submitted patch, 74: 2571533-74.patch, failed testing.

yched’s picture

Wrong permutation ;-). Yeah, UpdatePostUpdateTest is insane.

damiankloip’s picture

Status: Needs work » Needs review
FileSize
13.03 KB
1.04 KB

Nearly going to rage quit Drupal... :)

Didn't test this one either... uh oh

yched’s picture

Status: Needs review » Reviewed & tested by the community
FileSize
13.31 KB
1.08 KB

Moving to a syntax that better reflects the expected order :-)
I'll be bold and move back to RTBC, this and #79 do pass locally.

damiankloip’s picture

Yeah. I was think about doing something like that but just spent 30 seconds rerolling. Total +1 from me. Thanks yched!

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committing this since it is a soft blocker for a contrib module (og) and is the lightest possible approach and has test coverage and an update function. Committed daad7b1 and pushed to 8.0.x. Thanks!

  • alexpott committed daad7b1 on 8.0.x
    Issue #2571533 by damiankloip, yched: Allow setting custom storage on...
amitaibu’s picture

Awesome, thanks folks!

damiankloip’s picture

THANK YOU! This is most useful.

damiankloip’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

quietone’s picture

Published the change record.