Repository navigation
[FEATURE] KeyValueStorage: Add new component for global and subject-scoped key/value pairs - #12146
mjansenDatabay wants to merge 19 commits into
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…nsumers Refactored `StorageNamespace` to use a list of segments instead of string-based namespaces. Updated all relevant usages, tests, and documentation to align with the new implementation, ensuring separation of namespace segments and proper validation.
…t()` This commit suggests enhancing the KeyValueStorage to integrate Refinery transformations in the `get()` method. Updated the API, documentation, and tests to support default transformations and validation.
…any` Extended KeyValueStorage with methods to retrieve all keys in a namespace (`keys`), read all entries (`readAll`), and fetch multiple keys with Refinery transformations (`getMany`).
…urging Introduced "subject" as a new storage scope in KeyValueStorage, enabling per-subject keyed storage alongside session and global persistence. Enhanced Authentication to purge subject-specific data upon user deletion. Updated database schema, documentation, and tests to reflect these changes.
…urging Introduced "subject" as a new storage scope in KeyValueStorage, enabling per-subject keyed storage alongside session and global persistence. Enhanced Authentication to purge subject-specific data upon user deletion. Updated database schema, documentation, and tests to reflect these changes.
chfsx
left a comment
There was a problem hiding this comment.
@mjansenDatabay, keeping the subject out of namespace and key and letting the owning component
name it through a SubjectResolver reads well, and purging on user deletion closes the gap #11650
left open.
One thing I would like to see changed before this goes in: a subject does not know where it comes
from. SubjectId is a free segment, and u plus the user id belonging to Authentication is a
convention nothing enforces. If a second provider ever produces the same segment, both share the
same rows: they read and overwrite each other's values, because (subject, namespace, keyword) is
identical, and purgeSubject() for one deletes the data of the other.
I would make the origin part of the subject, in the API and in the table:
kvs_store
provider -- e.g. 'authentication', '' for global state
subject -- e.g. '42'
namespace
keyword
value
PK (provider, subject, namespace, keyword)
- Providers are contributed, e.g.
$contribute[SubjectProvider::class]in Authentication, and
KeyValueStorage seeks them and rejects a provider name that is used twice, so a collision fails
the build instead of mixing data. - The provider of a
SubjectIdcomes from the registered provider, not from the consumer, and
persistentFor()/purgeSubject()only accept subjects of registered providers. - Purging becomes
DELETE WHERE provider = ? AND subject = ?and can only reach the provider's
own rows; removing everything of a provider (e.g. when a plugin is uninstalled) is
DELETE WHERE provider = ?. Both are covered by the primary key. - The
uprefix is no longer needed, the subject is just the user id.
Since the table is not on trunk yet, now is the cheapest moment for this.
Three smaller remarks inline.
One question on the way forward: this PR contains all of #11650 and changes its update step in
place. Is #11650 superseded by this one? If so, closing it would avoid both being merged, which the
in-place change of step_1 would not survive.
|
Hi @chfsx , thx for the review. I will try to address your remarks ASAP, but I cannot guarantee that it is going to be done before the next JF.
Yes, this PR contains all of/is based on #11650 . If we agree to move on with the suggested approach here + your requested changes, we can close #11650 . Best regards, |
|
I try to implement the chabges asap and will make a Pr against your branch |
|
@mjansenDatabay the changes from my review are in mjansenDatabay#32, against your branch, one commit per topic. |
Faster than the speed of light 😉. I'll have a look tomorrow. |
|
Jour Fixe, 05 OCT 2026: @mjansenDatabay gave an comprehensive presentation of this PR. We highly appreciate this suggestion and accept it for ILIAS 12. The authorities for the new component "KeyValuePairs" has already been accepted by the Technical Board. |
Provenance and AI Usage
This PR builds on and supersedes #11650. It keeps the
KeyValueStorageintroduced there and closes the subject question that PR left open. It is based 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 was produced with AI assistance; the tool followed the architectural direction of those contributors and was not an independent author of the design. A substantial simplification review by @chfsx is incorporated, as is his review of the subject model: the provider is part of the subject, in the API and in the table.Summary
This PR introduces
KeyValueStorage: namespace-scoped key-value storage for application state (for example table sort column, wizard step, collapsed panels).It is intentionally distinct from
ilSetting,ILIAS\Cache, andILIAS\User\Settings. Please readcomponents/ILIAS/KeyValueStorage/README.mdfor the API, delimitation, privacy notes, and what is still missing.Consumers use
Services::session(),Services::persistent(), orServices::persistentFor()and receive a namespace-scopedStore. Values are JSON. An in-request memoization avoids repeated session or database reads within the same request, and skips the write when the value equals what this request has already read or written.The 3 Base Changes
1) The
KeyValueStorageComponentOwns the consumer API (
Services/Store), key and value rules, the persistent repository (kvs_store), the subject model, and setup. Session persistence is the one scope it cannot implement itself, because the session belongs to Authentication: it therefore definesSessionRepositoryand uses Authentication's implementation.2) Session backend and the authenticated user —
AuthenticationImplements
SessionRepositoryon top of the ILIAS session. Session scope is the natural fit for state that must not survive logout, including anonymous actors.Authentication also contributes the subject provider
authentication. The subject id is the user id. When a user account is deleted, Authentication callsServices::purgeSubject()for that subject. The listener still reaches the services throughglobal $DIC, becauseilAppEventListeneris static; that is noted in the Authentication roadmap.3) Persistent backend —
DatabaseProvides
ILIAS\Database\Connection(lazy accessor) so components can obtainilDBInterfaceduring wiring without reaching into$DICthemselves. KeyValueStorage uses that connection for the persistent scope. Database no longer owns the KV table.Subjects
kvs_storehas the primary key(provider, subject, namespace, keyword). Global rows use an empty provider and an empty subject. A subject's rows are addressed by the provider that named it and by the id that provider assigned.A component that names subjects contributes a
SubjectProvider. The provider name is read from that object (name()), for exampleauthentication. Two providers with the same name fail the build (composer dump-autoload/php cli/setup.php build), so a collision is rejected before any rows can be mixed. A provider is constructed without arguments for that check.new SubjectId($provider, $id)takes the provider object.persistentFor()andpurgeSubject()accept only a subject of a contributed provider, and only when the class that built the id is the class that was contributed under that name. Purging one subject isDELETE WHERE provider = ? AND subject = ?and reaches only that provider's rows.An anonymous subject cannot be persisted. That state stays in the session scope.
Subject rows of provider
authenticationare personal data (the subject id is the user id). They are removed ondeleteUser. Global rows have no user reference. Details are incomponents/ILIAS/KeyValueStorage/PRIVACY.md.UI Storage
UI\Storageis still adapted in Authentication; moving the adapter intoUIis a follow-up, so that the UI owns the namespace and the keys.The adapter chooses the store on every access. A fully authenticated user keeps UI state in the persistent subject scope. Everyone else keeps it in the session. The stores are memoized by
Services, and a value that did not change is not written again, which matters because tables store their view-control state on every rendering.Keys may contain characters that appear in PHP class names (for example
\), so existing UI table storage ids continue to work. Colon and control characters remain rejected because repositories use:when composing identifiers.Overview
We'd appreciate input on the preferred directions for the following next steps: