Skip to content

[FEATURE] KeyValueStorage: Introduce Unified API for Application State#11650

Open
mjansenDatabay wants to merge 3 commits into
ILIAS-eLearning:trunkfrom
mjansenDatabay:improvement/12/kv-storage
Open

[FEATURE] KeyValueStorage: Introduce Unified API for Application State#11650
mjansenDatabay wants to merge 3 commits into
ILIAS-eLearning:trunkfrom
mjansenDatabay:improvement/12/kv-storage

Conversation

@mjansenDatabay
Copy link
Copy Markdown
Contributor

@mjansenDatabay mjansenDatabay commented Jun 5, 2026

Provenance and AI Usage

This approach builds on a former cache implementation by @mjansenDatabay, which
had been developed in a plugin, and on discussions around several UI / Removing of
Legacy-UIComponents-Service and Table (LUI) topics between @thibsy, @oliversamoila,
@fhelfer and @mjansenDatabay. @thibsy provided important feedback and impulses throughout.
The implementation in this PR was produced with AI assistance; the tool
operated solely on the architectural direction and design decisions of the contributors mentioned
above — not as an independent author of the architecture.
(Important) decisions are documented based on ADRs.

Summary

This PR introduces a dedicated component for namespace-scoped key-value storage of
application state — together with two backends contributed by
their natural owners. Each backend defines its own lifetime: the transient backend
binds state to the current session, the persistent backend retains it across sessions
until explicitly changed or cleared.

It deliberately carves out a niche distinct from both ilSetting and ILIAS\Cache.
Rather than restating the reasoning here, please read
components/ILIAS/KeyValueStorage/README.md,
which covers the API surface, the contract for consumers and backend
providers, and — most relevantly for review — the sections Choosing the Right
Storage
(delimitation against ilSetting and ILIAS\Cache) and the ADR on the
lifetime taxonomy.

The 3 Base Changes

1) The KeyValueStorage Component

Defines the consumer-facing Factory / Storages / Storage interfaces, the
contributor-facing StoragePort / StorageProvider contracts, value objects
(StorageNamespace, key validation, JSON-only value codec), and the wiring. The
component does not persist anything itself — it defines ports and seeks
StorageProvider contributions. Lifetime is selected through Factory::transient() /
Factory::persistent(). Request-scoped (first-level) caching is an explicit, opt-in decorator
via Storages::requestCached(), not implicit behaviour. Full rationale and diagrams
are in the README.

2) Transient backend — Authentication

The Authentication component contributes the transient backend, backed by the ILIAS
session. Session storage is the natural fit for state that must not survive logout.
The storage layout (flat per-key session entries rather than a single nested bucket) and
its concurrency rationale are documented as an ADR in
components/ILIAS/Authentication/README.md.

3) Persistent backend — Database

The Database component contributes the persistent backend plus the schema for the greenfield
il_kv_storage table. The database connection is resolved lazily so the port can be constructed
during build/bootstrap (composer du, composer install, or php cli/setup.php ...) where
$DIC is not yet available. Schema and connection decisions are recorded as ADRs in
components/ILIAS/Database/README.md.

UI Storage: No Behavioural Change

UI\Storage continues to be served by the transient backend, exactly as before
this PR. The existing adapter in Authentication now obtains its storage through
Factory::transient()->requestCached(...), which preserves the prior session-backed
semantics. This is intentional: the question of whether UI view-control
state (sorting, filters, current page) should become persistent across login
sessions is a separate product/architecture discussion and is explicitly out of scope
here. Moving the UI adapter into the UI component is likewise deferred to a follow-up
PR.

Important: The UI component and its users must ensure valid keys are passed to
KeyValueStorage. This is currently not the case: some UI > Table > Data
implementations already pass PHP namespaces (with \ characters) as storage keys, which
violates the key rules of the KeyValueStorage component.

The "subject" Problem (for Discussion)

A point worth flagging for reviewers: KeyValueStorage does not attach a subject
(user, role, object) to stored values. The transient backend is implicitly per-user
because the session already is; the persistent backend is global
namespace + keyword → value, no actor dimension. Consequently, any consumer that
wants to persist per-user data (the obvious candidate being UI preferences) must
encode the subject into the namespace or key itself today. This is documented in the
README.

Options we see for handling subject scoping properly, roughly in order of
architectural cleanliness:

  • A first-class identity service (e.g. a CurrentUser abstraction resolvable in
    component wiring). This is the proper long-term fix and is useful well beyond this
    component, but there is currently no $use-able current-user service in the modern
    container — the user still comes from $DIC->user() at the composition root.
  • A subject-scoping decorator in the consuming layer that prefixes keys/namespaces
    with the current subject, fed by such an identity service. Keeps KeyValueStorage
    subject-agnostic and concentrates the policy in one testable place.
  • A two-tier composite (transient for anonymous users, per-user persistent for
    authenticated ones) selected per request — this is what actually satisfies a
    "survives logout" goal while avoiding junk rows for anonymous sessions.
  • Convention only: leave subject encoding to consumers (status quo). Simplest, but
    scatters the policy and is easy to get wrong.

We looked at how other applications and frameworks expose the current actor for this
kind of scoping. The recurring pattern is an injectable accessor resolved per
request — not the user model itself — for example Symfony's Security /
TokenStorageInterface (getUser()), Spring Security's SecurityContextHolder,
ASP.NET Core's IHttpContextAccessor (HttpContext.User), and Laravel's Auth guard
(auth()->user()).
The current user is request-scoped and may be absent (anonymous session, CLI, setup),
so it must be fetched on demand rather than bound at construction. ILIAS has no such
$use-able accessor in component wiring today (the user is taken from $DIC->user()
at the composition root); introducing one would be the natural way to scope
KeyValueStorage per user without hand-encoding ids.

Tests

Unit tests cover the component, both backends, and the value objects; all green. See
the Tests section of the component README for the breakdown.


Two cross-cutting concerns come with any persistent per-user approach and should not be
overlooked: lifecycle/GC of per-user rows (e.g. clearing a user's namespace on account
deletion), and the GDPR implication that persistent UI preferences become user-linked
personal data.

We'd appreciate input on the preferred directions for the following next steps:

  • Per-user persistence — see the subject discussion above before building on top of
    this foundation.
  • Environment (Proposal/12/component revision some components #11430) — could the KeyValueStorage component be useful for Environment?
  • INI-based storage (Proposal/12/component revision some components #11430) — could the INI-backed storage introduced with Environment
    in Proposal/12/component revision some components #11430: be a candidate for a further backend of KeyValueStorage, and if so,
    how should it be exposed? One idea is selectors such as app(...) and
    client(...) on a similar level to transient() and persistent(). Of course
    KeyValueStorage itself must not know about specific application settings. Also do discuss: Which component will own/contribute the "INI" backend?
  • UI table state — we should agree on how the UI framework finally wants to handle
    table state: sort direction and sort column, pagination, filters, row settings,
    selection of visible columns, and related view-control persistence. Important: The UI component and it's users has to ensure valid keys are passed to the KeyValueStorage component. This is currently not the case. There are already UI > Table > Data implementations which result in PHP namespaces (with \ characters) are passed to the storage, which violates the rules of the KeyValueStorage component.

mjansenDatabay and others added 3 commits June 5, 2026 11:16
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@mjansenDatabay mjansenDatabay added improvement php Pull requests that update Php code labels Jun 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement php Pull requests that update Php code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant