Services
Configuration
Read layered configuration with compass, from plugin defaults through per-environment files and SUMMER_ variables to runtime overrides saved to disk.
On this page
WinterCMS reads configuration with Config::get('app.name') from config/*.php, per-environment directories and .env. SummerCMS reads it from a compass.Config built by compass, with the same dot paths. Setup, Configuration lists the keys an application sets; this page covers how the layers merge and how code reads and changes them.
Layers#
compass.Config merges its sources in a fixed order. Each layer overrides the ones before it:
- Plugin defaults, merged with
compass.Config.MergePluginwhen the plugin is activated. A plugin'sconfig/config.yamlbecomes<plugin id>.<key>, soacme.blog.posts_per_pageis the WinterCMSacme.blog::posts_per_page. Any otherconfig/<name>.yamlbecomes<plugin id>.<name>.<key>. config/*.yaml. Each file is a section named after the file, soconfig/app.yamlprovidesapp.*.config/env/<environment>/*.yaml, the per-environment sections.SUMMER_environment variables and the.envfile next toconfig/.SUMMER_MAIL__DRIVERsetsmail.driver: the prefix is removed,__separates the path segments and the name is lower-cased. A.envvalue applies only when the real environment does not set the same variable.config/env/<environment>/overrides.yaml, written bycompass.Config.Persist.- Values set in memory with
compass.Config.Set.
The environment comes from SUMMER_ENV and defaults to production. Its name may contain only letters, digits, - and _.
Reading values#
The typed getters compass.Config.String, compass.Config.Int and compass.Config.Bool return the zero value for a missing key. Use compass.Config.Lookup or compass.Config.Has when a missing key must be told apart from a zero value, and compass.Config.LoadSection to read a whole section into a struct with koanf tags:
dir, err := os.MkdirTemp("", "acme-config")
if err != nil {
fmt.Println(err)
return
}
defer os.RemoveAll(dir)
if err := writeConfig(dir); err != nil {
fmt.Println(err)
return
}
// Environ stands in for the process environment (nil reads os.Environ).
cfg, err := compass.Open(compass.Options{
Dir: dir,
Env: "development",
Environ: []string{"SUMMER_MAIL__DRIVER=smtp"},
})
if err != nil {
fmt.Println(err)
return
}
// A plugin's embedded config/config.yaml becomes its defaults.
plugin := fstest.MapFS{"config/config.yaml": {Data: []byte("posts_per_page: 10\n")}}
if err := cfg.MergePlugin("acme.blog", plugin); err != nil {
fmt.Println(err)
return
}
fmt.Println(cfg.String("app.name"), cfg.Bool("app.debug"), cfg.Int("acme.blog.posts_per_page"))
var mail mailSettings
if err := cfg.LoadSection("mail", &mail); err != nil {
fmt.Println(err)
return
}
fmt.Println(mail.Driver, mail.From)
_, found := cfg.Lookup("app.timezone")
fmt.Println(cfg.Environment(), found)
// A runtime override, saved to env/development/overrides.yaml.
if err := cfg.Set("acme.blog.posts_per_page", 25); err != nil {
fmt.Println(err)
return
}
if err := cfg.Persist(); err != nil {
fmt.Println(err)
return
}
saved, _ := os.ReadFile(filepath.Join(dir, "env", "development", "overrides.yaml"))
fmt.Print(string(saved))
// Output:
// Acme true 10
// smtp blog@example.com
// development false
// acme:
// blog:
// posts_per_page: 25
The application opens its configuration once with compass.Load("config") in the generated main and hands it to the backpack.App. Plugins read it through app.Config and never open their own.
Changing values at runtime#
compass.Config.Set changes a value in memory. compass.Config.Persist saves every value set at runtime to config/env/<environment>/overrides.yaml, keeping the keys already saved there. It replaces the file atomically, creates it readable only by its owner and refuses any path outside the config directory. compass.Config.Reload rereads every source and discards values that were set but not persisted.
Values that admins edit in the backend are not configuration: settings pages store them in a database row; see Settings.