Skip to content

[Settings] Keep admin settings and config listings usable with a settings-store read target - #2052

Open
kingjia90 wants to merge 1 commit into
2026.3from
fix/pees-1508-settings-store-read-target
Open

kingjia90 wants to merge 1 commit into
2026.3from
fix/pees-1508-settings-store-read-target

Conversation

@kingjia90

Copy link
Copy Markdown
Contributor

Problem

The symfony-config write target is read-only once debug mode is off (docs), so the documented way to keep Appearance & Branding editable in production is to point both targets at the settings store:

pimcore_studio_backend:
    config_location:
        admin_settings:
            write_target:
                type: 'settings-store'
            read_target:
                type: 'settings-store'

Doing exactly that still leaves the screens unusable, in two independent ways.

1. The symfony configuration is discarded as soon as read_target: settings-store is set

SettingRepository::loadConfig() assigns the tuple returned by LocationAwareConfigRepository::loadConfigByKey() ([$data, $dataSource]) to $data and only destructures it in the caller. !$data is therefore testing a two-element array and is never true, so the settings-store fallback to the container configuration is dead code — and, as written, it would also have returned the wrapper array (['admin_settings' => [...]]) instead of the settings themselves.

The settings store only holds an entry once the settings have been saved through the UI at least once. Until then getConfiguration() resolves to null, and everything configured under pimcore_studio_backend.admin_settings is silently dropped.

Verified on 2026.x, APP_ENV=prod, debug off, with

pimcore_studio_backend:
    admin_settings:
        branding:
            brand_color: '#ff0000'
            background_shade: '#123456'
        assets:
            hide_edit_image: true
config GET /settings/admin
write_target only "backgroundShade":"#123456","brandColor":"#ff0000","hideEditImage":true — but "writeable":false (the data source is symfony-config, the write target is not, so isWriteable() refuses)
write_target + read_target "backgroundShade":"","brandColor":"","hideEditImage":false, "writeable":true — the whole branding configuration is gone

So today a project can have its branding read from the (git-versioned) YAML or have it editable, never both.

2. A single perspective/widget in the symfony configuration 404s the whole listing

PerspectiveConfigRepository::listConfigurations() and ElementTreeWidgetConfigRepository::listConfigurations() collect their keys with fetchAllKeys(), which merges the settings-store ids with the container configuration keys, while getConfiguration() loads through the read target and throws NotFoundException for anything the read target cannot see.

With studio_perspectives.read_target: settings-store and one perspective defined in YAML:

GET /pimcore-studio/api/perspectives/configurations
404 {"message":"Perspective with ID: yaml_perspective not found","errorKey":"error_element_not_found"}

The perspective editor is then completely unusable — which matches the "perspectives are also not writable" part of the report.

Fix

  • SettingRepository::loadConfig() destructures the tuple, so the intended fallback to the symfony configuration actually runs, and it returns the admin_settings node rather than its wrapper. The data source deliberately stays null, so the settings remain writeable and the first save goes to the settings store (which then takes precedence, as before).
  • Both listConfigurations() implementations take their keys from the read target via fetchAllKeysByReadTargets() when one is configured, and keep using fetchAllKeys() when none is (in that case both locations are read, so both key sets belong in the listing).

Verification

Live on a 2026.x project with APP_ENV=prod / debug off:

  • write_target + read_target = settings-store: GET /settings/admin now returns "backgroundShade":"#123456","brandColor":"#ff0000","hideEditImage":true and "writeable":true; POST /settings/admin/save returns 200 and the saved values are read back from the settings store.
  • GET /perspectives/configurations returns 200 instead of 404.
  • Default configuration (no config_location): unchanged — admin settings still come from the symfony configuration with "writeable":false in production, and the perspective listing still contains both the settings-store and the YAML perspectives.

vendor/bin/codecept run Unit → OK (594 tests, 1321 assertions), vendor/bin/phpstan analyse → no errors.

No regression test is included: all three repositories construct LocationAwareConfigRepository internally, which reads SettingsStore and therefore needs a database, and this bundle only ships a Unit suite. Making this testable would mean introducing a factory seam for LocationAwareConfigRepository across the three repositories, which felt out of scope for a bugfix on a patch branch — happy to add it if you would rather have it.

Closes https://github.com/pimcore/service-operations/issues/1225
Related: #1653, pimcore/studio-ui-bundle#3473

…ings-store read target

The symfony config write target is read-only once debug mode is off, so the only
way to keep Appearance & Branding, perspectives and element tree widgets editable
in production is to point both the read and the write target at the settings
store. Doing that currently breaks them in two ways:

* SettingRepository::loadConfig() destructured the result of loadConfigByKey()
  only in its caller, so the `!$data` fallback tested a two-element array and
  never fired. With an empty settings store the admin settings therefore
  resolved to null and every value configured in the symfony configuration was
  dropped, leaving an empty Appearance & Branding form. The fallback now works
  and returns the configured settings, still writeable because the data source
  stays unset.

* listConfigurations() collected its keys with fetchAllKeys(), which merges the
  settings store ids with the symfony configuration keys, while getConfiguration()
  honours the read target. A single perspective or widget defined in the symfony
  configuration therefore made the whole listing fail with a 404. The keys are
  now taken from the read target when one is configured.
Copilot AI balanced review requested due to automatic review settings September 21, 2026 14:31
@kingjia90 kingjia90 added this to the 2026.2.11 milestone Sep 21, 2026
@sonarqubecloud

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approved

The focused changes correctly resolve both reported configuration-loading failures without altering default behavior.

Review effort: Balanced
Findings: None

What changed in this PR

Fixes settings-store read targets so admin settings retain Symfony defaults and perspective listings remain usable.

Changes:

  • Falls back to Symfony admin settings when the settings store is empty.
  • Lists only configurations available through the configured read target.
  • Preserves existing behavior when no read target is configured.
File Description
src/​Setting/​Admin/​Repository/​SettingRepository.php Correctly handles repository tuples and admin-setting fallback.
src/​Perspective/​Repository/​PerspectiveConfigRepository.php Aligns listed perspective keys with the read target.
src/​Perspective/​Repository/​ElementTreeWidgetConfigRepository.php Aligns listed widget keys with the read target.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@kingjia90 kingjia90 modified the milestone: 2026.2.11 Sep 21, 2026
@kingjia90 kingjia90 self-assigned this Sep 21, 2026
@robertSt7 robertSt7 modified the milestones: 2026.2.11, 2026.2.12 Sep 22, 2026
@jcPimcore jcPimcore modified the milestones: 2026.2.12, 2026.3.1 Sep 24, 2026
@jcPimcore
jcPimcore changed the base branch from 2026.2 to 2026.3 September 24, 2026 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants