CoolCMS API

SourceLockTransactionTrait

Shared concurrency framework for writers serialized per source document, following the `DocumentService` pattern: the Store reservation writers (`ReservationService`, `ReservationExpirer`), Eshop order, complaint and credit-note managers, the Invoicer credit-note manager and the Company reservation, time-slot, work-list and work-sheet managers. It is not applied to `DocumentService` itself, which keeps its own private copy. A trait is not a class, so the DI scan never tries to instantiate it. A consumer outside Store may override `lockLogChannel()` to log into its own channel; a trait is not a cross-module dependency, it becomes part of the consuming class after `use`.

The advisory lock is a MySQL session lock on SourceRef::lockName():

  • GET_LOCK before beginTransaction(), RELEASE_LOCK in a finally;
  • it survives a transaction rollback and the deadlock retry, so it is held across every attempt;
  • IMPORTANT: GET_LOCK and the transaction must run on the same connection (same MySQL session), otherwise the lock does not protect the transaction; hence the single connection() accessor.

Also carries the bcmath (scale 3) and guarded DBAL row accessors both services need, so neither duplicates them.

Table of Contents

Constants

LOCK_MAX_ATTEMPTS  : int = 3
LOCK_SCALE  : int = 3

Methods

acquireSourceLock()  : bool
Calls `GET_LOCK(name, timeout)` and returns true only on a definite 1 (0 = timed out or busy, null = error). `$timeout` is in seconds; 0 is non-blocking (try-lock, used by the expirer).
connection()  : Connection
The single connection used for both the advisory lock and the transaction.
lockLogChannel()  : string
Tracy log channel for a failed `RELEASE_LOCK`. Override per module (Eshop: `eshop.general`).
releaseSourceLock()  : void
Releases the advisory lock.
runInSourceTransaction()  : T
Runs a closure in one raw-DBAL transaction with a deadlock retry (`SQLSTATE` 40001 / errno 1213).
ladd()  : numeric-string
lcmp()  : int
ldec()  : numeric-string
ldiv()  : numeric-string
Divides with half-up rounding to scale 3 (bcmath itself truncates; same rounding as {@see \App\UI\Api\Eshop\Models\Helpers\OrderCalculator::round3()}).
lInt()  : int
lmul()  : numeric-string
ln()  : numeric-string
lStr()  : string
lStrN()  : string|null
lsub()  : numeric-string

Constants

Methods

acquireSourceLock()

Calls `GET_LOCK(name, timeout)` and returns true only on a definite 1 (0 = timed out or busy, null = error). `$timeout` is in seconds; 0 is non-blocking (try-lock, used by the expirer).

protected acquireSourceLock(string $name, int $timeout) : bool
Parameters
$name : string
$timeout : int
Return values
bool

connection()

The single connection used for both the advisory lock and the transaction.

protected abstract connection() : Connection
Return values
Connection

lockLogChannel()

Tracy log channel for a failed `RELEASE_LOCK`. Override per module (Eshop: `eshop.general`).

protected lockLogChannel() : string
Return values
string

releaseSourceLock()

Releases the advisory lock.

protected releaseSourceLock(string $name) : void

IMPORTANT: it is called from a finally, so a failure here (a gone-away connection after a fatal query) is swallowed and logged; rethrowing would mask the original exception being unwound. MySQL drops session locks when the connection dies anyway, so nothing leaks past the process.

Parameters
$name : string

runInSourceTransaction()

Runs a closure in one raw-DBAL transaction with a deadlock retry (`SQLSTATE` 40001 / errno 1213).

protected runInSourceTransaction(callable(Connection): T $fn) : T

The advisory lock (held by the caller across this call) is not released by a rollback, so every attempt runs under the same serialization.

Parameters
$fn : callable(Connection): T
Tags
template
Return values
T

ladd()

private ladd(string $a, string $b) : numeric-string
Parameters
$a : string
$b : string
Return values
numeric-string

ldiv()

Divides with half-up rounding to scale 3 (bcmath itself truncates; same rounding as {@see \App\UI\Api\Eshop\Models\Helpers\OrderCalculator::round3()}).

private ldiv(string $a, string $b) : numeric-string

IMPORTANT: truncation would systematically drop up to 0.001 from every unit on large quantities, so the invariant sum(unitPrice x quantity) ~ sum(rowBase) would drift in one direction (an issue document undervalued against the order).

Parameters
$a : string
$b : string
Return values
numeric-string

lmul()

private lmul(string $a, string $b) : numeric-string
Parameters
$a : string
$b : string
Return values
numeric-string

lsub()

private lsub(string $a, string $b) : numeric-string
Parameters
$a : string
$b : string
Return values
numeric-string
On this page

Search results