UserRepository
extends ApiRepository
in package
uses
SaveTrait
Front repository that reads through the Api bridge and uses the query cache when the service has it enabled (`cache.config.enabledServices`).
This is the deliberate opposite of ApiRepository: the front end only displays data, so a 5 minute old number is at most cosmetic, whereas the admin reads it into a form that is written back to the DB.
->useCache(false) on the query object overrides the policy for one query; it travels
to the payload as ApiBridge::PARAM_USE_CACHE.
IMPORTANT: null is passed on as null (no opinion), not as true. If this layer
substituted true, it would override QueryCacheContext, and
reads through a front manager inside an admin request (AssetManager, ThemeManager,
UserManager) would still be served from the cache. The true default therefore lives
in the bridge.
Table of Contents
Properties
- $apiAuthEndpoint : string
- $apiEndpoint : string
- Api presenter path (e.g. `api/system/page`); set by each concrete repository.
- $bridge : ApiBridge
Methods
- __construct() : mixed
- activateUser() : Result
- Account activation from the e-mailed link. The front end only passes the pair from the URL; the Api manager verifies the key and consumes it.
- cancelDeletion() : Result
- Account deletion — `:Api:System:User:cancelDeletion` (e-mailed key or sign-in ticket).
- confirmDeletion() : Result
- Account deletion — `:Api:System:User:confirmDeletion` (one-time key from the e-mail).
- confirmExternalLink() : Result
- `:Api:System:Auth:confirmExternalLink` (one-time key from the e-mail).
- deauthorizeExternal() : Result
- emailExists() : bool|null
- E-mail counterpart of {@see self::usernameExists()}; same reasons and the same return contract.
- find() : array<string|int, mixed>
- findAll() : array<string|int, mixed>
- findBy() : array<string|int, mixed>
- findPublicProfile() : array<string, mixed>
- The only front end path to another user's profile.
- findPublicProfiles() : array<string, mixed>
- Bulk path to other users' profiles from the front end.
- login() : Result
- `$deviceToken` = raw cookie of a known device ({@see \App\Core\Security\LoginDeviceCookie}); only the Api verifies it, by signature. Without the cookie the key is not sent.
- loginExternal() : Result
- referralOverview() : Result
- Affiliate program — `:Api:System:User:referralOverview`; `$userId` from the session, the Api checks the owner.
- requestDeletion() : Result
- Account deletion — `:Api:System:User:requestDeletion`; `$userId` from the front end session, the Api compares it with the JWT identity (owner grant).
- resetPassword() : Result
- `:Api:System:User:resetPassword` (public, one-time key from the e-mail).
- revealPhone() : array<string, mixed>
- The only path to the raw phone of another user's profile.
- save() : Result
- sendActivationEmail() : Result
- sendNewPassword() : Result
- Replaced `generateNewPassword(int $userId)`. The front end no longer sends `userId` (or looks it up) and sends no e-mail; the whole "lost password" flow including sending `SY_LOST_PASSWORD` is done by the Api manager. The response contains no password and is constant for an existing and a nonexistent account.
- updateColumns() : Result
- updatePassword() : Result
- Api verifies the current password and answers with a fresh token pair.
- usernameExists() : bool|null
- Duplicate check on self-registration.
- failSoftRead() : array<string|int, mixed>
- Normalizes a bridge response to plain data; a failed envelope or a non-array gives `[]`.
- getApiCallSource() : string
- Returns the caller branch ({@see \App\Core\Bridge\ApiBridge::PARAM_SOURCE}); implemented by the Front and Admin base `ApiRepository`.
- callUserAction() : Result
- readExists() : bool|null
Properties
$apiAuthEndpoint
protected
string
$apiAuthEndpoint
= 'api/system/auth'
$apiEndpoint
Api presenter path (e.g. `api/system/page`); set by each concrete repository.
protected
string
$apiEndpoint
= 'api/system/user'
$bridge read-only
protected
ApiBridge
$bridge
Methods
__construct()
public
__construct(ApiBridge $bridge) : mixed
Parameters
- $bridge : ApiBridge
activateUser()
Account activation from the e-mailed link. The front end only passes the pair from the URL; the Api manager verifies the key and consumes it.
public
activateUser(string $email, string $activationKey) : Result
Parameters
- $email : string
- $activationKey : string
Return values
ResultcancelDeletion()
Account deletion — `:Api:System:User:cancelDeletion` (e-mailed key or sign-in ticket).
public
cancelDeletion(string $key) : Result
Parameters
- $key : string
Return values
ResultconfirmDeletion()
Account deletion — `:Api:System:User:confirmDeletion` (one-time key from the e-mail).
public
confirmDeletion(string $key) : Result
Parameters
- $key : string
Return values
ResultconfirmExternalLink()
`:Api:System:Auth:confirmExternalLink` (one-time key from the e-mail).
public
confirmExternalLink(string $key) : Result
Parameters
- $key : string
Return values
ResultdeauthorizeExternal()
public
deauthorizeExternal(string $provider, string $externalId) : Result
Parameters
- $provider : string
- $externalId : string
Return values
ResultemailExists()
E-mail counterpart of {@see self::usernameExists()}; same reasons and the same return contract.
public
emailExists(string $email[, int|null $excludeId = null ]) : bool|null
Parameters
- $email : string
- $excludeId : int|null = null
Return values
bool|nullfind()
public
find(int $id[, string|array<string|int, mixed> $includes = [] ][, bool|null $useCache = null ]) : array<string|int, mixed>
Parameters
- $id : int
- $includes : string|array<string|int, mixed> = []
- $useCache : bool|null = null
Return values
array<string|int, mixed>findAll()
public
findAll([array<string|int, mixed>|null $filters = [] ][, array<string|int, mixed>|null $orderBys = null ][, int|null $offset = null ][, int|null $limit = null ][, array<string|int, mixed>|null $columns = null ][, bool $computeTotalCount = false ][, string|array<string|int, mixed> $includes = [] ][, bool|null $useCache = null ]) : array<string|int, mixed>
Parameters
- $filters : array<string|int, mixed>|null = []
- $orderBys : array<string|int, mixed>|null = null
- $offset : int|null = null
- $limit : int|null = null
- $columns : array<string|int, mixed>|null = null
- $computeTotalCount : bool = false
- $includes : string|array<string|int, mixed> = []
- $useCache : bool|null = null
Return values
array<string|int, mixed>findBy()
public
findBy([array<string|int, mixed>|null $filters = [] ][, string|array<string|int, mixed> $includes = [] ][, bool|null $useCache = null ]) : array<string|int, mixed>
Parameters
- $filters : array<string|int, mixed>|null = []
- $includes : string|array<string|int, mixed> = []
- $useCache : bool|null = null
Return values
array<string|int, mixed>findPublicProfile()
The only front end path to another user's profile.
public
findPublicProfile(int $id) : array<string, mixed>
Targets :Api:System:User:publicProfile (guest = 1), not get
(ApiRepository::find()), which is admin-only and returns the whole entity including
password/salt/email/ip. An anonymous profile page failed on get (HTTP 500 inside
a 200) under internalMode: enforce.
IMPORTANT: simplifying this method back to $this->find($id) brings back both the
blocking failure of the anonymous page and the leak of personal data.
Parameters
- $id : int
Return values
array<string, mixed> —allowlist from UserPresenter::PUBLIC_PROFILE_FIELDS; empty array = the profile does not exist or the guard refused
findPublicProfiles()
Bulk path to other users' profiles from the front end.
public
findPublicProfiles(array<int, int> $ids) : array<string, mixed>
Targets the same public-profile endpoint (so the same key
:Api:System:User:publicProfile), only with the ids parameter; see
UserPresenter::internalActionPublicProfile() for why the bulk
mode is not an action of its own.
It replaces findAll(['id' => InFilter]) on :Api:System:User:getAll, which is admin-only
and under internalMode: enforce returned Result::failure, so the template dereferenced
null and a public page returned 500 inside a 200.
IMPORTANT: id is deliberately sent as 0: if it were missing, the Api would get a
default in singular mode and the bulk branch would not run. A non-empty ids decides.
Parameters
- $ids : array<int, int>
Return values
array<string, mixed> —shape {items: {id: profile}}; empty items
= nothing found or the guard refused
login()
`$deviceToken` = raw cookie of a known device ({@see \App\Core\Security\LoginDeviceCookie}); only the Api verifies it, by signature. Without the cookie the key is not sent.
public
login(string $username, string $password[, string|null $deviceToken = null ]) : Result
Parameters
- $username : string
- $password : string
- $deviceToken : string|null = null
Return values
ResultloginExternal()
public
loginExternal(string $provider, string $id, string $email, string $firstname, string $lastname[, bool $emailVerified = false ][, string $referralCode = '' ]) : Result
Parameters
- $provider : string
- $id : string
- $email : string
- $firstname : string
- $lastname : string
- $emailVerified : bool = false
- $referralCode : string = ''
Return values
ResultreferralOverview()
Affiliate program — `:Api:System:User:referralOverview`; `$userId` from the session, the Api checks the owner.
public
referralOverview(int $userId) : Result
Parameters
- $userId : int
Return values
ResultrequestDeletion()
Account deletion — `:Api:System:User:requestDeletion`; `$userId` from the front end session, the Api compares it with the JWT identity (owner grant).
public
requestDeletion(int $userId, string $password) : Result
Parameters
- $userId : int
- $password : string
Return values
ResultresetPassword()
`:Api:System:User:resetPassword` (public, one-time key from the e-mail).
public
resetPassword(string $email, string $key, string $password) : Result
Parameters
- $email : string
- $key : string
- $password : string
Return values
ResultrevealPhone()
The only path to the raw phone of another user's profile.
public
revealPhone(int $id) : array<string, mixed>
Targets :Api:System:User:showPhone (a dedicated action, not an extension of
publicProfile; see UserPresenter::internalActionShowPhone()).
The Api layer does the guard and the rate limit; this method only passes the call on.
Parameters
- $id : int
Return values
array<string, mixed> —shape {phone: string}; an empty or missing phone
= refused or the user/phone does not exist (the caller does not distinguish).
The return type is deliberately wider than array{phone?: string} (the bridge
returns raw Api data, not a verified shape, as in
self::findPublicProfile()).
save()
public
save(array<string, mixed> $data[, array<string, mixed> $meta = [] ][, array<int, string> $skipFields = [] ]) : Result
Parameters
- $data : array<string, mixed>
-
extracted entity payload
- $meta : array<string, mixed> = []
- $skipFields : array<int, string> = []
Return values
ResultsendActivationEmail()
public
sendActivationEmail(string $username) : Result
Parameters
- $username : string
Return values
ResultsendNewPassword()
Replaced `generateNewPassword(int $userId)`. The front end no longer sends `userId` (or looks it up) and sends no e-mail; the whole "lost password" flow including sending `SY_LOST_PASSWORD` is done by the Api manager. The response contains no password and is constant for an existing and a nonexistent account.
public
sendNewPassword(string $username) : Result
Parameters
- $username : string
Return values
ResultupdateColumns()
public
updateColumns(array<string|int, mixed> $ids, array<string, mixed> $values) : Result
Parameters
- $ids : array<string|int, mixed>
-
ids or composite-id arrays
- $values : array<string, mixed>
-
property name => new value
Return values
ResultupdatePassword()
Api verifies the current password and answers with a fresh token pair.
public
updatePassword(int $userId, string $currentPassword, string $password) : Result
Parameters
- $userId : int
- $currentPassword : string
- $password : string
Return values
ResultusernameExists()
Duplicate check on self-registration.
public
usernameExists(string $username[, int|null $excludeId = null ]) : bool|null
Targets :Api:System:User:usernameExists, not getBy. getBy is admin-only and must stay
so (it returns the whole User entity: password, salt, email, ip, address…). The
registration form used to call isUnique() through it and under enforce got an empty
result through FailSoftReadTrait, read as "name is free": the duplicate check was
silently off and only the unique index in the database remained.
This action has no actionUsernameExists(), so external HTTP cannot reach it, and it
returns a single bool: no user data leaves the Api layer.
Parameters
- $username : string
- $excludeId : int|null = null
Return values
bool|null —null = the answer could not be determined (guard/outage), not "free";
the caller must tell them apart, otherwise fail-soft would turn into
fail-open again
failSoftRead()
Normalizes a bridge response to plain data; a failed envelope or a non-array gives `[]`.
protected
failSoftRead(mixed $response, string $endpoint) : array<string|int, mixed>
NOTE: it does not log on purpose. ApiBridge::call(), which sees both the
endpoint and the caller, has already written the API_FAILSOFT source=bridge line; a second
copy would turn one failure into three lines in auth.log.
Parameters
- $response : mixed
- $endpoint : string
-
only for call readability; the log takes the endpoint from the bridge
Return values
array<string|int, mixed>getApiCallSource()
Returns the caller branch ({@see \App\Core\Bridge\ApiBridge::PARAM_SOURCE}); implemented by the Front and Admin base `ApiRepository`.
protected
abstract getApiCallSource() : string
Return values
stringcallUserAction()
private
callUserAction(string $action, array<string, mixed> $params) : Result
Parameters
- $action : string
- $params : array<string, mixed>
Return values
ResultreadExists()
private
readExists(string $endpoint, array<string, mixed> $params) : bool|null
Parameters
- $endpoint : string
- $params : array<string, mixed>
Return values
bool|null —null when the response has no exists key (refused by the guard
or empty fail-soft); deliberately not turned into false