UserRelationManager
in package
Set-based management of a user's many-to-many links to roles and groups, directly through DBAL.
Same approach as UserRolePrivilegeMatrixManager / PrivilegeManager:
the entity collection userRoles is #[Transient] (saving from the admin does not overwrite it),
so the join tables are synchronized deterministically and set-based. Writes are fail-safe
(an error is logged and reported as a failure).
Tables:
cms_system_user_role_relations(user × role)cms_system_user_group_relations(user × skupina)
Table of Contents
Constants
- EXT_TABLE : mixed = 'cms_system_user_external_logins'
- EXT_USER : mixed = 'cms_system_users_user_id'
- GROUP_GROUP : mixed = 'cms_system_user_groups_group_id'
- GROUP_TABLE : mixed = 'cms_system_user_group_relations'
- GROUP_USER : mixed = 'cms_system_users_user_id'
- ROLE_ROLE : mixed = 'cms_system_user_roles_user_role_id'
- ROLE_TABLE : mixed = 'cms_system_user_role_relations'
- ROLE_USER : mixed = 'cms_system_users_user_id'
Properties
- $connection : Connection
Methods
- __construct() : mixed
- getActiveProtectedHolders() : array<int, array<int, int>>|null
- Maps each protected role (`protect_last_holder = 1`) to the ids of its active holders (activated, not banned, not canceled, no pending cancellation request, since such an account cannot sign in).
- getExternalLogins() : array<int, array{type: string, externalId: string}>
- Linked external sign-ins (OAuth) of the user, for the admin listing.
- getGroupIds() : array<int, int>
- getRoleCatalogue() : array<int, array{assignable: bool, default: bool, protectLastHolder: bool}>|null
- Role catalog with the flags used by the {@see \App\UI\Api\System\Models\Helpers\UserRolePolicy} gates: `assignable`, `default` (= `set_after_registration`, the role of a new account) and `protectLastHolder`.
- getRoleIds() : array<int, int>
- getRoleNames() : array<int, string>
- Names of the roles assigned to the user (explicit links, not implicit roles), for the informational `role` claim in the JWT. The entity `User::getUserRoles()` is unusable for this: it reads an unmapped collection, so the claim was empty in every token.
- grantDefaultGroups() : Result
- Adds the default user groups (`set_after_registration = 1`) the account does not have yet, like {@see grantDefaultRoles()}. The flag used to be ignored: a new account got roles only, no group (and so no group discount).
- grantDefaultRoles() : Result
- Adds the default roles (`set_after_registration = 1`) the account does not have yet; it deletes nothing and duplicates nothing. Called when an account is created ({@see UserManager::afterSave()} and {@see UserManager::createFromExternalLogin()}); it does not change the roles of existing accounts.
- removeExternalLogin() : Result
- Unlinks (deletes) one external sign-in of the user.
- syncGroups() : Result
- syncRoles() : Result
- IMPORTANT: writes the set without any permission check. The role-change gates are evaluated by {@see UserManager::beforeSave()} through {@see \App\UI\Api\System\Models\Helpers\UserRolePolicy}, so calling this directly bypasses them.
- fetchIds() : array<int, int>
- sync() : Result
- Sets the user's links to exactly the given set of ids (deletes all, then inserts the chosen ones).
Constants
EXT_TABLE
private
mixed
EXT_TABLE
= 'cms_system_user_external_logins'
EXT_USER
private
mixed
EXT_USER
= 'cms_system_users_user_id'
GROUP_GROUP
private
mixed
GROUP_GROUP
= 'cms_system_user_groups_group_id'
GROUP_TABLE
private
mixed
GROUP_TABLE
= 'cms_system_user_group_relations'
GROUP_USER
private
mixed
GROUP_USER
= 'cms_system_users_user_id'
ROLE_ROLE
private
mixed
ROLE_ROLE
= 'cms_system_user_roles_user_role_id'
ROLE_TABLE
private
mixed
ROLE_TABLE
= 'cms_system_user_role_relations'
ROLE_USER
private
mixed
ROLE_USER
= 'cms_system_users_user_id'
Properties
$connection read-only
private
Connection
$connection
Methods
__construct()
public
__construct(Connection $connection) : mixed
Parameters
- $connection : Connection
getActiveProtectedHolders()
Maps each protected role (`protect_last_holder = 1`) to the ids of its active holders (activated, not banned, not canceled, no pending cancellation request, since such an account cannot sign in).
public
getActiveProtectedHolders([bool $lock = false ]) : array<int, array<int, int>>|null
Used by the "last holder" gates when deleting an account, removing a role, and
deactivating, banning or requesting cancellation of an account.
null means the query failed (the caller denies: fail-closed).
$lock = true reads with a locking SELECT ... FOR UPDATE (the links of protected roles and
the rows of their holders):
it makes sense only inside the caller's transaction. Two concurrent deletions or removals of
the last two administrators are then serialized and the second sees the state after the
first (a locking read in InnoDB reads the latest committed data).
Parameters
- $lock : bool = false
Return values
array<int, array<int, int>>|nullgetExternalLogins()
Linked external sign-ins (OAuth) of the user, for the admin listing.
public
getExternalLogins(int $userId) : array<int, array{type: string, externalId: string}>
Parameters
- $userId : int
Return values
array<int, array{type: string, externalId: string}>getGroupIds()
public
getGroupIds(int $userId) : array<int, int>
Parameters
- $userId : int
Return values
array<int, int> —ids of the groups assigned to the user
getRoleCatalogue()
Role catalog with the flags used by the {@see \App\UI\Api\System\Models\Helpers\UserRolePolicy} gates: `assignable`, `default` (= `set_after_registration`, the role of a new account) and `protectLastHolder`.
public
getRoleCatalogue() : array<int, array{assignable: bool, default: bool, protectLastHolder: bool}>|null
null means the query failed (the caller denies: fail-closed).
Return values
array<int, array{assignable: bool, default: bool, protectLastHolder: bool}>|nullgetRoleIds()
public
getRoleIds(int $userId) : array<int, int>
Parameters
- $userId : int
Return values
array<int, int> —ids of the roles assigned to the user
getRoleNames()
Names of the roles assigned to the user (explicit links, not implicit roles), for the informational `role` claim in the JWT. The entity `User::getUserRoles()` is unusable for this: it reads an unmapped collection, so the claim was empty in every token.
public
getRoleNames(int $userId) : array<int, string>
Parameters
- $userId : int
Return values
array<int, string>grantDefaultGroups()
Adds the default user groups (`set_after_registration = 1`) the account does not have yet, like {@see grantDefaultRoles()}. The flag used to be ignored: a new account got roles only, no group (and so no group discount).
public
grantDefaultGroups(int $userId) : Result
Parameters
- $userId : int
Return values
ResultgrantDefaultRoles()
Adds the default roles (`set_after_registration = 1`) the account does not have yet; it deletes nothing and duplicates nothing. Called when an account is created ({@see UserManager::afterSave()} and {@see UserManager::createFromExternalLogin()}); it does not change the roles of existing accounts.
public
grantDefaultRoles(int $userId) : Result
The entity collection userRoles is not involved: it is #[Transient], so saving through
the hydrator does not write it. Only this set-based path manages the links.
Parameters
- $userId : int
Return values
ResultremoveExternalLogin()
Unlinks (deletes) one external sign-in of the user.
public
removeExternalLogin(int $userId, string $type, string $externalId) : Result
Parameters
- $userId : int
- $type : string
- $externalId : string
Return values
ResultsyncGroups()
public
syncGroups(int $userId, array<string|int, mixed> $groupIds) : Result
Parameters
- $userId : int
- $groupIds : array<string|int, mixed>
Return values
ResultsyncRoles()
IMPORTANT: writes the set without any permission check. The role-change gates are evaluated by {@see UserManager::beforeSave()} through {@see \App\UI\Api\System\Models\Helpers\UserRolePolicy}, so calling this directly bypasses them.
public
syncRoles(int $userId, array<string|int, mixed> $roleIds) : Result
Parameters
- $userId : int
- $roleIds : array<string|int, mixed>
Return values
ResultfetchIds()
private
fetchIds(string $table, string $idColumn, string $userColumn, int $userId) : array<int, int>
Parameters
- $table : string
- $idColumn : string
- $userColumn : string
- $userId : int
Return values
array<int, int>sync()
Sets the user's links to exactly the given set of ids (deletes all, then inserts the chosen ones).
private
sync(string $table, string $userColumn, string $idColumn, int $userId, array<string|int, mixed> $ids) : Result
IMPORTANT: returns a Result, which is always truthy; the caller must check
->isSuccess(), not if ($result).
Parameters
- $table : string
- $userColumn : string
- $idColumn : string
- $userId : int
- $ids : array<string|int, mixed>