Learning Drupal 8: Day 3

So on my mission to learn Drupal 8 I now have a custom entity type with bundles. Great. Now I want to define some bundles that I'll use. Each bundle will end up having some custom code, so I don't really need users to be able to create bundle types in future; so I suppose I should remove those links too.

Background/lingo: I'm making a social media module called Bloom. The metaphor is a bunch of flowers. This will draw on different feeds of content (the "petals") each from different sources ("stems") from different SM platforms ("stem types" will be twitter, facebook...). Guess I could have called stem types species, but the parallel with node's "content types" is also useful here.

How do modules define their own bundles?

The Book module defines a content type (bundle for node entities), so I looked to that to learn how to provide content for new custom entiity. There's a file in the book module's directory: core/modules/book/config/install/node.type.book.yml

This contains the definition, which matches against the schema (See Drupal 8 documentation) from the node module, at core/modules/node/config/schema/node.schema.yml.

Step 1: check my config entity's schema

The Drupal 8 console tool had created me a basic schema config file at /modules/bloom/config/schema/bloom_stem_type.schema.yml:

bloom.bloom_stem_type.*:
  type: config_entity
  label: 'BloomStemType config'
  mapping:
    id:
      type: string
      label: 'ID'
    label:
      type: label
      label: 'Label'
    uuid:
      type: string

As I understand it:

  • the first line, bloom.bloom_stem_type matches the filename and is the name of the configuration object as it will be known within Drupal.

  • The .* means that the following applies to all our config sets (i.e. applies to each of our entity's bundles). For a practical example, the node schema defines node.type.* which the book module then provides content (a bundle) in its node.type.book.yml file (note the name matches the pattern).

  • the type: config entity means that this definition extends that of config_entity. This is a core data type and is defined in core/config/schema/core.data_types.schema.yml - it basically re-declares the type as a mapping, and adds in several key:value pairs to the mapping.

  • Our config's mapping section then adds/overwrites those inherited from config_entity. I notice that the uuid config in ours does nothing; that is already defined in the parent entity anyway. Not sure why the Drupal 8 Console includes that, perhaps it's a common thing to override.

Step 2: provide a bundle definition

So if I want to provide a minimal bundle definition for Twitter I would need to provide a config file in modules/bloom/config/install/bloom.bloom_stem_type.twitter.yml as follows:

id: twitter
label: Twitter

I uninstalled and re-installed the module and voila! It's created that entity type.

Enforced Dependencies?

There's a way to 'enforce' a dependency for a config entity. The book module uses this (in it's node.type.book.yml) to say that that definition (of a node type) enforces a dependency on the book module. This means that were you to try to uninstall node, it would fail or require book to be uninstalled also. i.e. the enforced dependency applies to the module taken from the config object's name.

So I do not need to add an enforced dependency to my bloom module because this config file already belongs to that.

How do I add fields in config?

Each stem type will require some custom fields. I need these to be defined in config, too. e.g. let's say that 'stems' of the twitter 'stem type' are going to be able to fetch items that match an account name like @artfulrobot. It's going to need a text field to store the twitter name.

Recap from Drupal Field API Documentation:

The Field API defines two primary data structures, FieldStorage and Field, and the concept of a Bundle. A FieldStorage defines a particular type of data that can be attached to entities. A Field is attached to a single Bundle. A Bundle is a set of fields that are treated as a group by the Field Attach API and is related to a single fieldable entity type.

That is: to use an entirely custom field, you have to define the FieldStorage, then create an instance of that in a particular entity's bundle. (In normal use one field storage might be used across several bundles, e.g. the body field.)

So unsurprisingly, the field core module supplies it's own config schema at core/modules/field/config/schema/field.schema.yml which includes a definition for config_entity objects defined by:

  1. field.storage.*.* 
    I'm guessing the asterixes refer to (1) module defining the field storage and (2) the field storage machine name.

  2. field.field.*.*.*
    I have deduced that the wildcards here are: entity type, bundle, field storage machine name. So the book module, for example, includes config for its body field in a file called field.field.node.book.body.yml (node is the entity, book is the bundle, and this file details the use of a field storage object called body).

1. Declare the storage for our custom field

Filename: field.storage.bloom_stem.bs_twitter_ac.yml Highlighting shows the configuration type and the config name :

uuid: 25d683a8-4b09-4008-bf37-55baa376e434
langcode: en
status: true
dependencies: {  }
id: bloom_stem.bs_twitter_ac
field_name: bs_twitter_ac
entity_type: bloom_stem
type: text
settings:
  max_length: 50
module: text
locked: false
cardinality: 1
translatable: false
indexes: {  }
persist_with_no_fields: false

2. Declare the field instance of this custom field storage on our bundle

Filename: field.field.bloom_stem.twitter.bs_twitter_ac.yml

uuid: 4abe07ee-0c86-4434-ac3f-63f222708ce4
langcode: en
status: true
dependencies:
  config:
    - bloom.bloom_stem_type.twitter
    - field.storage.bloom_stem.bs_twitter_ac
  module:
    - text
id: bloom_stem.twitter.bs_twitter_ac
field_name: bs_twitter_ac
entity_type: bloom_stem
bundle: twitter
label: 'Twitter Account'
description: ''
required: false
translatable: true
default_value: {  }
default_value_callback: ''
settings: {  }
field_type: text

I cheated!

So I found it really hard getting my head around this. But Drupal 8 comes with a Configuration Manager module. Enable that and visit /admin/config/development/configuration and there's an export function. This allows you to export your configuration and tells you what the filename should be (although not where to put it!). So to create the file above (field.field.bloom_stem.twitter.bs_twitter_ac.yml) what I did was add the pre-existing field storage to the Twitter 'stem' entity bundle using the familiar Field UI, then went to the configuration manager page to export this as a config file.

Remaining question: how do I migrate changes?

So far I've been uninstalling and re-installing my module to affect the changes to my config. But how do I get Drupal to notice a change and do what's necessary to migrate?

e.g. if I add another field in my module, how to get Drupal to just add that field to the existing entity bundles? I thought update.php / drush updatedb might do it, but no. I thought the Configuration Manager UI's 'synchronize' would do it but no. So I think there's more to learn here.

Wow!? in Drupal 8: "sites own their configuration, not modules"

Wow, this one turns out to be a surprise! This has far reaching implications. Modules can't apply updates to 'their' config automatically (because it's not considered 'theirs' to update). Also I noticed that when I tried to add a dependency on the bloom module to my text field, it was removed by the system, presumably because while it does actually require the text module to exist,  it is deemed that this configuration can exist without my module?

See Stack Exchange question and Drupal 8 for small sites.

Tags: