ApiClientService
extends Service
in package
uses
SaveTrait
Clients of the external Api and their keys, plus a cache of the client record by key digest.
Key verification runs on every external request: the record is loaded once per request and kept
between requests by the Nette cache storage (Redis, files without Redis) for at most
api.clientKey.cacheTtl seconds. Every client or key change through the manager deletes the
records of all keys of the client (forgetClient()), so disabling, revoking and a new expiry
apply immediately. The cache does not watch key expiry; the manager decides from the request time.
An unknown key is not cached: random keys would fill the cache; it costs one query on a unique index.
The class is not final so that unit tests can replace it with a mock in the manager (the project
has no bypass-finals).
Tags
Table of Contents
Constants
- CACHE_NAMESPACE : string = 'apiClient.byKeyHash'
- MAX_CACHE_TTL : int = 60
- Maximum allowed cache lifetime in seconds.
Properties
- $mapper : MapperInterface
- $apiClientKeyConfig : ApiClientKeyConfig
- $apiClientMapper : ApiClientMapper
- $cache : Cache
- $requestCache : array<string, ApiClientSnapshot>
Methods
- __construct() : mixed
- beginTransaction() : void
- commit() : void
- count() : int
- Count all entities matching filters.
- countValidKeys() : int
- delete() : Result
- Delete entity by ID.
- deleteAll() : Result
- Delete multiple entities by IDs.
- deleteBy() : Result
- Delete entity by filters.
- deleteKey() : int
- exists() : bool
- Check if entity exists by ID.
- existsBy() : bool
- Check if entity exists by filters.
- fetchKeyHashes() : array<int, string>
- fetchKeys() : array<int, array<int, ApiClientKeyRow>>
- fetchRoleIds() : array<int, array<int, int>>
- fetchRoleOptions() : array<int, array{id: int, code: string, name: string, implicit: string, conditional: bool, privileged: bool}>
- fetchRows() : array<int, ApiClientRow>
- find() : T|null
- Find entity by ID.
-
findAll()
: T>, 1: int} : array{0: list
>, 1: int}) - Find all entities with optional filters and includes.
- findAllBy() : array<int, T>
- Find entities by filters.
- findBy() : T|null
- Find one entity by filters.
- findByOrFail() : T
- Find one entity by filters or throw exception.
- findOrFail() : T
- Find entity by ID or throw exception.
- findRelatedIds() : array<int, int>|null
- Returns ids of target entities linked to the parent through the relation collection (see the mapper).
- findSnapshotByKeyHash() : ApiClientSnapshot|null
- Returns the client record by key digest: `id`, `name`, `enabled`, `revoked`, `roleCodes`, limits, `lastUsedAt` (unix time) and the key state (`keyId`, `keyExpiresAt`, `keyRevoked`, `keyLastUsedAt`).
- forgetClient() : void
- Forgets the records of all keys of the client; the manager calls it after every client or key change (each key record carries the client state too). The caller must forget a deleted key separately while it still knows its digest (after the `DELETE` this method cannot find it).
- forgetKeyHash() : void
- Forgets the record of one key.
- getTransactionLevel() : int
- insertClient() : int
- insertKey() : int
- isInTransaction() : bool
- rememberSnapshot() : void
- Stores the record in the cache (also after writing `last_used_at`, so later requests within the interval do not write again).
- replaceRoles() : void
- resolveEntityClass() : TEntity>
- Exposes the root entity class of the service publicly.
- revokeKey() : int
- rollback() : void
- save() : Result
- Save entity.
- saveAll() : Result
- Save multiple entities at once.
- touchLastUsed() : int
- updateAllRelations() : Result
- Update all relations of parent entity with given values.
- updateClient() : int
- updateColumns() : Result
- Update specific columns.
- updateEnabled() : int
- updateKey() : int
- updateRelations() : Result
- Update existing relation entities via Mapper.
- updateRevoked() : int
- deleteWithoutCache() : Result
- Delete entity from database via Mapper.
-
findAllFromDb()
: T>, 1: int} : array{0: list
>, 1: int}) - Load all entities from database via Mapper → Repository.
- findFromDb() : T|null
- Load entity from database via Mapper.
- getEntityClass() : TEntity>
- Returns the service entity class; every concrete service returns `Entity::class` matching its `@extends Service<Entity>`.
- narrowEntities() : array<int, TEntity>
- narrowEntity() : TEntity|null
- Narrows the generic {@see BaseEntity} returned by the mapper to the service entity.
- narrowRows() : array<int, array<string, mixed>>
- Narrows query rows for `$columns` (no hydration); every item must be an array keyed by column names.
- saveWithoutCache() : Result
- Save entity to database via Mapper.
- updateColumnsWithoutCache() : Result
- Update columns in database via Mapper.
- cacheTtl() : int
- isTechnicalDeleteFailure() : bool
- Technical failure = the persistence layer broke (exception attached or a technical code); the EntityManager may be closed and no further write may be attempted.
Constants
CACHE_NAMESPACE
private
string
CACHE_NAMESPACE
= 'apiClient.byKeyHash'
MAX_CACHE_TTL
Maximum allowed cache lifetime in seconds.
private
int
MAX_CACHE_TTL
= 60
Properties
$mapper read-only
protected
MapperInterface
$mapper
$apiClientKeyConfig read-only
private
ApiClientKeyConfig
$apiClientKeyConfig
$apiClientMapper read-only
private
ApiClientMapper
$apiClientMapper
$cache read-only
private
Cache
$cache
$requestCache
private
array<string, ApiClientSnapshot>
$requestCache
= []
key digest => record, for this request only
Methods
__construct()
public
__construct(ApiClientMapper $apiClientMapper, Storage $storage, ApiClientKeyConfig $apiClientKeyConfig) : mixed
Parameters
- $apiClientMapper : ApiClientMapper
- $storage : Storage
- $apiClientKeyConfig : ApiClientKeyConfig
beginTransaction()
public
beginTransaction() : void
commit()
public
commit() : void
count()
Count all entities matching filters.
public
count([array<string|int, mixed> $filters = [] ][, bool $useCache = true ]) : int
Parameters
- $filters : array<string|int, mixed> = []
-
Filter conditions
- $useCache : bool = true
-
kept for backward compatibility; no effect here
Return values
int —Total count
countValidKeys()
public
countValidKeys(int $clientId, DateTimeInterface $now) : int
Parameters
- $clientId : int
- $now : DateTimeInterface
Return values
intdelete()
Delete entity by ID.
public
delete(int|array<string|int, mixed> $id) : Result
IMPORTANT: an array means a list of ids, not a composite key (Mapper::find() takes
only int). Without this redirect to deleteAll() every bulk delete from the DataGrid
ends with TypeError: Mapper::find(): Argument #1 ($id) must be of type int, array given
(the grid calls Manager::delete($ids) with the array of selected rows).
Parameters
- $id : int|array<string|int, mixed>
-
Entity ID to delete
Return values
Result —Success if deleted, Error if entity not found
deleteAll()
Delete multiple entities by IDs.
public
deleteAll(array<string|int, mixed> $ids) : Result
Partial success is the contract for domain refusals (missing id, guard): the loop
continues and the errors are listed next to deletedCount. A technical failure
(DB_ERROR and similar, see ResultCode::isTechnical()) aborts the loop instead: after a
failed ORM flush Doctrine closes the EntityManager, so the next remove() would throw
EntityManagerClosed out of the loop and the caller would get no summary of the rows
already deleted (durable in autocommit). The failed Result carries deletedIds,
failedId and remainingIds; the caller decides whether the accepted prefix stays
(autocommit) or is rolled back (the manager bulk paths run inside a transaction and roll
it back).
Parameters
- $ids : array<string|int, mixed>
-
entity ids (int or numeric string); for a composite key an array
[property => value]
Return values
Result —Success with delete count, or Error
deleteBy()
Delete entity by filters.
public
deleteBy(array<string|int, mixed> $filters) : Result
Parameters
- $filters : array<string|int, mixed>
-
Filter conditions
Return values
Result —Success if deleted, Error if not found
deleteKey()
public
deleteKey(int $clientId, int $keyId, DateTimeInterface $now) : int
Parameters
- $clientId : int
- $keyId : int
- $now : DateTimeInterface
Return values
intexists()
Check if entity exists by ID.
public
exists(int|array<string|int, mixed> $id[, bool $useCache = true ]) : bool
Parameters
- $id : int|array<string|int, mixed>
-
Entity ID
- $useCache : bool = true
-
kept for backward compatibility; no effect here
Return values
bool —True if entity exists
existsBy()
Check if entity exists by filters.
public
existsBy(array<string|int, mixed> $filters[, bool $useCache = true ]) : bool
Parameters
- $filters : array<string|int, mixed>
-
Filter conditions
- $useCache : bool = true
-
kept for backward compatibility; no effect here
Return values
bool —True if entity exists
fetchKeyHashes()
public
fetchKeyHashes(int $clientId) : array<int, string>
Parameters
- $clientId : int
Return values
array<int, string> —key digests of the client; for the cache only, never leave the Api
fetchKeys()
public
fetchKeys(array<int, int> $clientIds) : array<int, array<int, ApiClientKeyRow>>
Parameters
- $clientIds : array<int, int>
Return values
array<int, array<int, ApiClientKeyRow>>fetchRoleIds()
public
fetchRoleIds(array<int, int> $clientIds) : array<int, array<int, int>>
Parameters
- $clientIds : array<int, int>
Return values
array<int, array<int, int>>fetchRoleOptions()
public
fetchRoleOptions() : array<int, array{id: int, code: string, name: string, implicit: string, conditional: bool, privileged: bool}>
Return values
array<int, array{id: int, code: string, name: string, implicit: string, conditional: bool, privileged: bool}>fetchRows()
public
fetchRows([int|null $clientId = null ]) : array<int, ApiClientRow>
Parameters
- $clientId : int|null = null
Return values
array<int, ApiClientRow>find()
Find entity by ID.
public
find(int|array<string|int, mixed> $id[, array<string|int, mixed> $includes = [] ][, bool $useCache = true ]) : T|null
Parameters
- $id : int|array<string|int, mixed>
-
Entity ID (scalar for simple keys, array for composite keys)
- $includes : array<string|int, mixed> = []
-
Relations to load (e.g.,
['translations', 'user.roles']) - $useCache : bool = true
-
kept for backward compatibility (caching is done in the presenter); no effect here
Return values
T|null —Entity instance or null if not found
findAll()
Find all entities with optional filters and includes.
public
findAll([array<string|int, mixed> $includes = [] ][, array<string|int, mixed> $filters = [] ][, array<string|int, mixed>|null $orderBy = null ][, int|null $offset = null ][, int|null $limit = null ][, array<string|int, mixed>|null $columns = null ][, bool $computeTotalCount = false ][, bool $useCache = true ]) : T>, 1: int} : array{0: list>, 1: int})
Parameters
- $includes : array<string|int, mixed> = []
-
Relations to load (e.g., ['translations', 'user.roles'])
- $filters : array<string|int, mixed> = []
-
Filter conditions (e.g., ['activated' => 1])
- $orderBy : array<string|int, mixed>|null = null
-
Sorting (e.g.,
['createdAt' => 'DESC']) - $offset : int|null = null
-
Offset for pagination
- $limit : int|null = null
-
Limit number of results
- $columns : array<string|int, mixed>|null = null
-
Columns to select (null = all)
- $computeTotalCount : bool = false
-
Whether to compute total count for pagination
- $useCache : bool = true
-
kept for backward compatibility (caching is done in the presenter); no effect here
Return values
T>, 1: int} : array{0: listTuple: [entities (or $columns rows), totalCount]
findAllBy()
Find entities by filters.
public
findAllBy(array<string|int, mixed> $filters[, array<string|int, mixed> $includes = [] ][, array<string|int, mixed>|null $order = null ][, int|null $offset = null ][, int|null $limit = null ][, bool $useCache = true ]) : array<int, T>
Parameters
- $filters : array<string|int, mixed>
-
Filter conditions (e.g., ['activated' => 1, 'userId' => 5])
- $includes : array<string|int, mixed> = []
-
Relations to load
- $order : array<string|int, mixed>|null = null
-
Sorting
- $offset : int|null = null
-
Offset
- $limit : int|null = null
-
Limit
- $useCache : bool = true
-
kept for backward compatibility; no effect here
Return values
array<int, T> —Array of entities
findBy()
Find one entity by filters.
public
findBy(array<string|int, mixed> $filters[, array<string|int, mixed> $includes = [] ][, bool $useCache = true ]) : T|null
Parameters
- $filters : array<string|int, mixed>
-
Filter conditions
- $includes : array<string|int, mixed> = []
-
Relations to load
- $useCache : bool = true
-
kept for backward compatibility; no effect here
Return values
T|null —Entity or null if not found
findByOrFail()
Find one entity by filters or throw exception.
public
findByOrFail(array<string|int, mixed> $filters[, array<string|int, mixed> $includes = [] ][, bool $useCache = true ]) : T
Parameters
- $filters : array<string|int, mixed>
-
Filter conditions
- $includes : array<string|int, mixed> = []
-
Relations to load
- $useCache : bool = true
-
kept for backward compatibility; no effect here
Tags
Return values
T —Entity instance
findOrFail()
Find entity by ID or throw exception.
public
findOrFail(int|array<string|int, mixed> $id[, array<string|int, mixed> $includes = [] ][, bool $useCache = true ]) : T
Parameters
- $id : int|array<string|int, mixed>
-
Entity ID
- $includes : array<string|int, mixed> = []
-
Relations to load
- $useCache : bool = true
-
kept for backward compatibility; no effect here
Tags
Return values
T —Entity instance
findRelatedIds()
Returns ids of target entities linked to the parent through the relation collection (see the mapper).
public
findRelatedIds(int $parentId, string $property, string $targetAssociation) : array<int, int>|null
Parameters
- $parentId : int
- $property : string
- $targetAssociation : string
Return values
array<int, int>|nullfindSnapshotByKeyHash()
Returns the client record by key digest: `id`, `name`, `enabled`, `revoked`, `roleCodes`, limits, `lastUsedAt` (unix time) and the key state (`keyId`, `keyExpiresAt`, `keyRevoked`, `keyLastUsedAt`).
public
findSnapshotByKeyHash(string $keyHash) : ApiClientSnapshot|null
Null when the key does not exist.
Parameters
- $keyHash : string
Return values
ApiClientSnapshot|nullforgetClient()
Forgets the records of all keys of the client; the manager calls it after every client or key change (each key record carries the client state too). The caller must forget a deleted key separately while it still knows its digest (after the `DELETE` this method cannot find it).
public
forgetClient(int $clientId) : void
Parameters
- $clientId : int
forgetKeyHash()
Forgets the record of one key.
public
forgetKeyHash(string $keyHash) : void
Parameters
- $keyHash : string
getTransactionLevel()
public
getTransactionLevel() : int
Return values
intinsertClient()
public
insertClient(ApiClientValues $values, DateTimeInterface $now) : int
Parameters
- $values : ApiClientValues
- $now : DateTimeInterface
Return values
intinsertKey()
public
insertKey(int $clientId, string $keyHash, string $keyPrefix, string|null $label, DateTimeInterface|null $expiresAt, DateTimeInterface $now, int $maxValidKeys) : int
Parameters
- $clientId : int
- $keyHash : string
- $keyPrefix : string
- $label : string|null
- $expiresAt : DateTimeInterface|null
- $now : DateTimeInterface
- $maxValidKeys : int
Return values
int —id of the new key; 0 when the client is revoked or already has the full ceiling of valid keys
isInTransaction()
public
isInTransaction() : bool
Return values
boolrememberSnapshot()
Stores the record in the cache (also after writing `last_used_at`, so later requests within the interval do not write again).
public
rememberSnapshot(string $keyHash, ApiClientSnapshot $snapshot) : void
Parameters
- $keyHash : string
- $snapshot : ApiClientSnapshot
replaceRoles()
public
replaceRoles(int $clientId, array<int, int> $roleIds) : void
Parameters
- $clientId : int
- $roleIds : array<int, int>
resolveEntityClass()
Exposes the root entity class of the service publicly.
public
resolveEntityClass() : TEntity>
getEntityClass() is protected in the traits (implemented per service); this method
makes it available to the manager so that the write endpoint takes the entity from the
presenter, not from the request.
Return values
TEntity>revokeKey()
public
revokeKey(int $clientId, int $keyId, DateTimeInterface $now) : int
Parameters
- $clientId : int
- $keyId : int
- $now : DateTimeInterface
Return values
introllback()
public
rollback() : void
save()
Save entity.
public
save(TEntity $entity[, array<int, string> $skipFields = [] ]) : Result
Parameters
- $entity : TEntity
-
Entity to save
- $skipFields : array<int, string> = []
-
Field names to skip during UPDATE
Return values
Result —Success/error result
saveAll()
Save multiple entities at once.
public
saveAll(array<string|int, TEntity> $entities) : Result
Parameters
- $entities : array<string|int, TEntity>
-
Array of entities to save
Return values
Result —Success with saved count, or Error
touchLastUsed()
public
touchLastUsed(int $clientId, int $keyId, DateTimeInterface $now, int $intervalSeconds) : int
Parameters
- $clientId : int
- $keyId : int
- $now : DateTimeInterface
- $intervalSeconds : int
Return values
intupdateAllRelations()
Update all relations of parent entity with given values.
public
updateAllRelations(array<string|int, mixed> $entityIds, string $property, array<string, mixed> $values) : Result
Parameters
- $entityIds : array<string|int, mixed>
- $property : string
- $values : array<string, mixed>
Return values
ResultupdateClient()
public
updateClient(int $clientId, ApiClientValues $values, DateTimeInterface $now) : int
Parameters
- $clientId : int
- $values : ApiClientValues
- $now : DateTimeInterface
Return values
intupdateColumns()
Update specific columns.
public
updateColumns(array<string|int, mixed> $entityIds, array<string, mixed> $values) : Result
Parameters
- $entityIds : array<string|int, mixed>
-
Entity IDs (scalar or composite-id arrays)
- $values : array<string, mixed>
-
Column values to update (property name => value)
Return values
Result —Success/error result
updateEnabled()
public
updateEnabled(int $clientId, bool $enabled, DateTimeInterface $now) : int
Parameters
- $clientId : int
- $enabled : bool
- $now : DateTimeInterface
Return values
intupdateKey()
public
updateKey(int $clientId, int $keyId, string|null $label, DateTimeInterface|null $expiresAt) : int
Parameters
- $clientId : int
- $keyId : int
- $label : string|null
- $expiresAt : DateTimeInterface|null
Return values
intupdateRelations()
Update existing relation entities via Mapper.
public
updateRelations(array<string|int, mixed> $entityIds, string $property, array<string|int, object> $relationEntities) : Result
Parameters
- $entityIds : array<string|int, mixed>
- $property : string
- $relationEntities : array<string|int, object>
Return values
ResultupdateRevoked()
public
updateRevoked(int $clientId, DateTimeInterface $now) : int
Parameters
- $clientId : int
- $now : DateTimeInterface
Return values
intdeleteWithoutCache()
Delete entity from database via Mapper.
protected
deleteWithoutCache(BaseEntity $entity) : Result
Called internally by delete() and deleteAll().
Parameters
- $entity : BaseEntity
Return values
ResultfindAllFromDb()
Load all entities from database via Mapper → Repository.
protected
findAllFromDb([array<string|int, mixed> $includes = [] ][, array<string|int, mixed> $filters = [] ][, array<string|int, mixed>|null $orderBy = null ][, int|null $offset = null ][, int|null $limit = null ][, array<string|int, mixed>|null $columns = null ][, bool $computeTotalCount = false ]) : T>, 1: int} : array{0: list>, 1: int})
Parameters
- $includes : array<string|int, mixed> = []
-
Relations to load
- $filters : array<string|int, mixed> = []
-
Filter conditions
- $orderBy : array<string|int, mixed>|null = null
-
Sorting
- $offset : int|null = null
-
Offset
- $limit : int|null = null
-
Limit
- $columns : array<string|int, mixed>|null = null
-
Columns to select
- $computeTotalCount : bool = false
-
Whether to compute total count
Return values
T>, 1: int} : array{0: listTuple: [entities (or $columns rows), totalCount]
findFromDb()
Load entity from database via Mapper.
protected
findFromDb(int|array<string|int, mixed> $id, array<string|int, mixed> $includes) : T|null
Parameters
- $id : int|array<string|int, mixed>
-
Entity ID
- $includes : array<string|int, mixed>
-
Relations to load
Return values
T|null —Entity or null
getEntityClass()
Returns the service entity class; every concrete service returns `Entity::class` matching its `@extends Service<Entity>`.
protected
getEntityClass() : TEntity>
Return values
TEntity>narrowEntities()
protected
narrowEntities(array<string|int, mixed> $items) : array<int, TEntity>
Parameters
- $items : array<string|int, mixed>
Return values
array<int, TEntity>narrowEntity()
Narrows the generic {@see BaseEntity} returned by the mapper to the service entity.
protected
narrowEntity(BaseEntity|null $entity) : TEntity|null
The service owns a single entity (getEntityClass()), so a foreign class means a wiring
error (wrong mapper), not a normal state.
Parameters
- $entity : BaseEntity|null
Return values
TEntity|nullnarrowRows()
Narrows query rows for `$columns` (no hydration); every item must be an array keyed by column names.
protected
narrowRows(array<string|int, mixed> $items) : array<int, array<string, mixed>>
Parameters
- $items : array<string|int, mixed>
Return values
array<int, array<string, mixed>>saveWithoutCache()
Save entity to database via Mapper.
protected
saveWithoutCache(BaseEntity $entity[, array<int, string> $skipFields = [] ]) : Result
Parameters
- $entity : BaseEntity
- $skipFields : array<int, string> = []
Return values
ResultupdateColumnsWithoutCache()
Update columns in database via Mapper.
protected
updateColumnsWithoutCache(array<string|int, mixed> $entityIds, array<string, mixed> $values) : Result
Parameters
- $entityIds : array<string|int, mixed>
- $values : array<string, mixed>
Return values
ResultcacheTtl()
private
cacheTtl() : int
Return values
intisTechnicalDeleteFailure()
Technical failure = the persistence layer broke (exception attached or a technical code); the EntityManager may be closed and no further write may be attempted.
private
isTechnicalDeleteFailure(Result $result) : bool
Parameters
- $result : Result