Rethinking Drupal Configuration
MidCamp 2015
Scott Reynen
@scottr
aten.io
What I Did
groups.drupal.org
@font-your-face
What I Do Now
Config in Code
(Parenting)
- This is what I've been doing more recently.
Opinions are Cool!
- And I have a lot of opinions on config. You should too.
- They don't need to be the same as my opinions on config.
- I brought you a config opinion starter pack.
1. Config is ... Everything
Saved Decisions
- Everything starts as custom.
- Start auto-generating in different ways.
- Config is how we save those decisions.
2. Config is not Interface
Fields are not Fields UI
Views are not Views UI
- Config exists independent of how you make it.
3. Interface is Important
Laziness is a Virtue
- More intuitive interface helps you do more faster.
- Easy to ignore slowness of what you already learned.
The Dark Ages
(Before Features)
- A long time ago (2009) ...
Create
Read
Update
Delete
Browser UI
- We did everything in the browser.
- It was easy until....
Create
Read
Update
Delete
Manage
Browser UI
- On launch, you can copy database.
- Post-launch you can't, and end up repeating everything between dev and prod.
1. Config is ... Everything
- Not a big deal with Drupal was doing less.
- Didn't scale as Drupal grew more complicated.
Features is
Good
- Features started a new era of Drupal config thinking.
- Config management was important.
- This is what Features looks like.
- Exports to a module as PHP code.
Manage
Browser UI
Features
- Config Management was standard now. Yay!
- Everything is awesome? Not quite.
Create
Read
Update
Delete
Manage
Browser UI
Features
- Features can't delete configuration.
- Updating configuration is complicated.
- Features are PHP code, so only work as well as that code works.
Features is
Not
Good
Enough
- Features is good.
- But not good enough.
2. Config is not Interface
- And this picture is even less happy as we remember the first point: Config is not Interface.
- This about donut ordering, which you do in dozens.
- Need a "donut type" field with 12 values.
- How do you do this?
- You can't. Not in the browser UI.
- This is reasonable for the standard need, but not for your special case.
- Config can say 12 values; only UI cannot.
Create
Read
Update
Delete
Manage
Browser UI
Features
- So the Browser UI is less than it could be, and Features inherits these problems.
Drupal 8 Config is
Better
- Drupal 8 makes everything better, including config.
- Drupal 8 has a standard configuration structure.
- Somewhat like Features, you can export it.
- But it exports as YML files, not PHP.
- Those files are organized like this.
langcode: en
status: true
dependencies: { }
name: 'Basic page'
type: page
description: 'Use <em>basic pages</em> for your static content, such as an ''About us'' page.'
help: ''
title_label: Title
settings:
node:
options:
status: true
promote: false
sticky: false
revision: false
preview: 1
submitted: false
- This is what YAML looks like.
- Very simple.
- You can also export individual configurations.
- Also have Features in D8, for bundles.
Manage
Browser UI
Features
Drupal 8
- Drupal 8 makes config management better.
Create
Read
Update
Delete
Manage
Browser UI
Features
Drupal 8
- Standard config format makes D8 updates better.
- Still no delete.
Drupal 8 Config is
Not
Better
Enough
- Delete is one reason D8 config is not better enough.
- But we're also missing opportunities still.
3. Interface is Important
- Config interfaces are improved in Drupal 8, but ...
- We haven't adjusted our thinking about how config UI should work.
Core Interfaces are for Everyone
You're not Everyone
- Now that config is cleanly separated from UI, there's little reason we should all be using the same UI.
- This entire interface is focused on finding the module you want.
- You know that already, just need to enable it.
- Many of us don't use this interface at all, use Drush instead.
- That's a custom interface for a specific approach to the same config.
Custom Interfaces are Hard, Right?
No, Not Anymore
- And the difficulty of making custom UI is no longer a reason either, because it's pretty easy now.
\Drupal::service('config.factory')->getEditable('node.type.page')
->set('type', 'page')
->set('name', 'Basic page')
->set('description', 'Use <em>basic pages</em> for your static content, such as an ''About us'' page.')
->save();
Easy!
- This is how to make content type in D8.
- This is faster than using the default UI for coders.
- And for everyone else, knowing coders can do it faster means you can encourage coders to make your ideal UI.
Config in Code
(CINC)
drupal.org/project/cinc
- The Config in Code (CINC) module let's you use close to the same code interface in D7.
CINC::init(‘ContentType’)->machine_name('page')
->set('name', 'Basic page')
->set('description', 'Use <em>basic pages</em> for your static content, such as an ''About us'' page.')
->create();
Easier!
- Very similar to D8 API, but a little more human-readable.
$text_field = CINC::init('TextField')->machine_name('field_text')
->create();
CINC::init(‘ContentType’)->machine_name('page')
->add_field($text_field);
Easiest!
- And CINC goes beyond just replicating the D8 system.
- It makes each config object smart, so they can do more than edit raw config.
- For example, each field type sets different defaults.
- And adding a field to a content type is simply passing the field object into the content type object.
uuid: b071f334-ec2e-40e3-a809-9d436780386e
langcode: en
status: true
dependencies:
module:
- node
- user
id: content
label: Content
module: node
description: 'Find and manage content.'
tag: default
base_table: node
base_field: nid
core: 8.x
display:
default:
display_options:
access:
type: perm
options:
perm: 'access content overview'
cache:
type: none
query:
type: views_query
exposed_form:
type: basic
options:
submit_button: Filter
reset_button: true
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: full
options:
items_per_page: 50
style:
type: table
options:
grouping: { }
row_class: ''
default_row_class: true
override: true
sticky: true
caption: ''
summary: ''
description: ''
columns:
node_bulk_form: node_bulk_form
title: title
type: type
name: name
status: status
changed: changed
edit_node: edit_node
delete_node: delete_node
dropbutton: dropbutton
timestamp: title
info:
node_bulk_form:
align: ''
separator: ''
empty_column: false
responsive: ''
title:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
type:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
name:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: priority-low
status:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
changed:
sortable: true
default_sort_order: desc
align: ''
separator: ''
empty_column: false
responsive: priority-low
edit_node:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
delete_node:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
dropbutton:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
timestamp:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
default: changed
empty_table: true
row:
type: fields
fields:
node_bulk_form:
id: node_bulk_form
table: node
field: node_bulk_form
label: ''
exclude: false
alter:
alter_text: false
element_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
plugin_id: node_bulk_form
entity_type: node
title:
id: title
table: node_field_data
field: title
label: Title
exclude: false
alter:
alter_text: false
element_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
link_to_node: true
plugin_id: node
entity_type: node
entity_field: title
type:
id: type
table: node_field_data
field: type
label: 'Content Type'
exclude: false
alter:
alter_text: false
element_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
link_to_node: false
machine_name: ''
plugin_id: node_type
entity_type: node
entity_field: type
name:
id: name
table: users_field_data
field: name
relationship: uid
label: Author
exclude: false
alter:
alter_text: false
element_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
link_to_user: true
overwrite_anonymous: false
anonymous_text: ''
format_username: true
plugin_id: user_name
entity_type: user
entity_field: name
status:
id: status
table: node_field_data
field: status
label: Status
exclude: false
alter:
alter_text: false
element_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
type: published-notpublished
type_custom_true: ''
type_custom_false: ''
not: false
plugin_id: boolean
entity_type: node
entity_field: status
changed:
id: changed
table: node_field_data
field: changed
label: Updated
exclude: false
alter:
alter_text: false
element_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
date_format: short
custom_date_format: ''
timezone: ''
plugin_id: date
entity_type: node
entity_field: changed
operations:
id: operations
table: node
field: operations
relationship: none
group_type: group
admin_label: ''
label: Operations
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
destination: true
plugin_id: entity_operations
filters:
status_extra:
id: status_extra
table: node_field_data
field: status_extra
operator: '='
value: false
plugin_id: node_status
group: 1
entity_type: node
status:
id: status
table: node_field_data
field: status
relationship: none
group_type: group
admin_label: ''
operator: '='
value: true
group: 1
exposed: true
expose:
operator_id: ''
label: Status
description: ''
use_operator: false
operator: status_op
identifier: status
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
is_grouped: true
group_info:
label: 'Published status'
description: ''
identifier: status
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items:
1:
title: Published
operator: '='
value: '1'
2:
title: Unpublished
operator: '='
value: '0'
plugin_id: boolean
entity_type: node
entity_field: status
type:
id: type
table: node_field_data
field: type
relationship: none
group_type: group
admin_label: ''
operator: in
value: { }
group: 1
exposed: true
expose:
operator_id: type_op
label: Type
description: ''
use_operator: false
operator: type_op
identifier: type
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
anonymous: '0'
administrator: '0'
reduce: false
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
plugin_id: bundle
entity_type: node
entity_field: type
title:
id: title
table: node_field_data
field: title
relationship: none
group_type: group
admin_label: ''
operator: contains
value: ''
group: 1
exposed: true
expose:
operator_id: title_op
label: Title
description: ''
use_operator: false
operator: title_op
identifier: title
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
anonymous: '0'
administrator: '0'
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
plugin_id: string
entity_type: node
entity_field: title
langcode:
id: langcode
table: node_field_data
field: langcode
relationship: none
group_type: group
admin_label: ''
operator: in
value: { }
group: 1
exposed: true
expose:
operator_id: langcode_op
label: Language
description: ''
use_operator: false
operator: langcode_op
identifier: langcode
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
anonymous: '0'
administrator: '0'
reduce: false
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
plugin_id: language
entity_type: node
entity_field: langcode
sorts: { }
title: Content
empty:
area_text_custom:
id: area_text_custom
table: views
field: area_text_custom
empty: true
content: 'No content available.'
plugin_id: text_custom
arguments: { }
relationships:
uid:
id: uid
table: node_field_data
field: uid
admin_label: author
required: true
plugin_id: standard
show_admin_links: false
filter_groups:
operator: AND
groups:
1: AND
display_extenders: { }
display_plugin: default
display_title: Master
id: default
position: 0
page_1:
display_options:
path: admin/content/node
menu:
type: 'default tab'
title: Content
description: ''
menu_name: admin
weight: -10
context: ''
tab_options:
type: normal
title: Content
description: 'Find and manage content'
menu_name: admin
weight: -10
display_extenders: { }
display_plugin: page
display_title: Page
id: page_1
position: 1
YAML Views
So Easy!
- View has a very complex, general-purpose UI.
- YAML is 8 pages!
CINC::init('View')->machine_name('random_instructor')
->set('human_name', 'Random Instructor')
->add_block_display()
->set_row_style('node')
->set_view_mode('teaser')
->add_filter('published')
->add_node_type_filter('instructor')
->limit_items(1)
->add_sort('random')
->create();
CINC Views
Sheet2Module
cinc.io
- CINC makes it easy to create interfaces that work exactly how you want.
- I've made a few of those interfaces for myself.
- Aten uses spreadsheets to describe content structure before we build it.
- Sheet2Module uses these spreadsheets.
80% Less Time
on Content Types
- The result is eliminating most of the work of creating content types.
Fast Content Type UI
- Fast Content Type UI is another options.
- It creates all your content types on one screen.
- Can also see/change comment settings all on one screen.
1. Config is ... Everything
2. Config is not Interface
3. Interface is Important
- All of this adds up to the best approach to Drupal config...
Roll Your Own is
Best
- You should build the perfect interface for you.
- We should have 100 or 1000 interfaces for managing content types, and all other config.
- Each interface should be focused on unique approaches to the same config.
Create
Read
Update
Delete
Manage
Browser UI
Features
Drupal 8
Roll Your Own
Feedback!
joind.in/13821
Twitter: @scottr
#MidCamp
- Everything is awesome for real this time.
- Let's talk more.