\Drupal\strata\Journal JournalOp

One captured mutation, and the only shape the journal appends.

A value object rather than an array, so a misspelled key is a type error at the call site instead of a silently absent field, and so the fields a restore depends on are checked once, here, rather than again in every writer that reads them.

An op describes a mutation and never carries the value. The value is in the object store under $payloadHash, which keeps a journal row the same size whether the subject is a taxonomy term or a 200 MiB file. $parentHash names the version the mutation was applied to, so a chain can be walked backwards without reading a payload at all.

$sequence orders every op in a window and is assigned by the journal on append, so a caller builds the op and gets its number back through withSequence().

Summary

Methods
Properties
Constants
__construct
key
timestamp
isDestructive
withSequence
jsonSerialize
fromArray
sequence
microtime
realm
subject
verb
actor
requestId
payloadHash
parentHash
payloadLength
label
fields
MICROSECONDS_PER_SECOND
No protected methods found
No protected properties found
No protected constants found
refuseUnrepresentable
isRepresentable
No private properties found
No private constants found

Constant

MICROSECONDS_PER_SECOND

MICROSECONDS_PER_SECOND = 1000000

Microseconds in a second, the unit $microtime is recorded in.

Properties

$sequence

$sequence : int

Type

int

$microtime

$microtime : int

Type

int

$realm

$realm : \Drupal\strata\Journal\Realm

Type

Realm

$subject

$subject : string

Type

string

$verb

$verb : \Drupal\strata\Journal\Verb

Type

Verb

$actor

$actor : ?int

Type

int|null

$requestId

$requestId : ?string

Type

string|null

$payloadHash

$payloadHash : ?string

Type

string|null

$parentHash

$parentHash : ?string

Type

string|null

$payloadLength

$payloadLength : int

Type

int

$label

$label : string

Type

string

$fields

$fields : array

Type

array<string|int, mixed>

Methods

__construct()

__construct(int  $sequence, int  $microtime, \Drupal\strata\Journal\Realm  $realm, string  $subject, \Drupal\strata\Journal\Verb  $verb, int|null  $actor = null, string|null  $requestId = null, string|null  $payloadHash = null, string|null  $parentHash = null, int  $payloadLength = 0, string  $label = '', list  $fields = []) : mixed

Constructs an op.

Parameters

int $sequence

Position in the window, assigned by the journal on append. Zero or more.

int $microtime

Unix timestamp in microseconds, as intval(microtime(true) * 1e6).

\Drupal\strata\Journal\Realm $realm

The subsystem the subject lives in.

string $subject

What was mutated, addressed the way its realm addresses things: node:42, user.settings, mantle2_api_keys:pk=17.

\Drupal\strata\Journal\Verb $verb

What the mutation did.

int|null $actor

Uid that caused the mutation, or NULL for cron, drush and anything else with no session.

string|null $requestId

Identifier of the request the mutation happened in, so ops that belong to one page submission can be grouped after the fact. NULL when the writer did not record one.

string|null $payloadHash

Digest of the value after the mutation, or NULL when there is no value to store, as for a delete.

string|null $parentHash

Digest of the value before the mutation, or NULL when the subject had no previous version.

int $payloadLength

Length of the value in bytes, so a window can be totalled without reading the store.

string $label

Human-readable description for the UI, such as "Article: Hello world".

list $fields

Top-level field names the mutation touched. Empty when the writer does not track fields, or when the verb replaces the whole subject.

Throws

\InvalidArgumentException

When a number is out of range, the subject is empty, a hash is not a valid digest, $fields is not a list of names, or a string the op carries is not valid UTF-8.

Returns

mixed —

key()

key() : string

The identity a window is grouped by.

Realm-qualified, because a config name and a table name can collide and mean two unrelated things. This is the key Collapser folds a run of ops on.

Returns

string —

The realm value and the subject, joined by a colon.

timestamp()

timestamp() : float

When the mutation happened, as a float unix timestamp.

Microseconds are stored as an integer so ordering and equality are exact; this is the form for a formatter or a duration, not for a comparison.

Returns

float —

Seconds since the epoch, with microsecond resolution.

isDestructive()

isDestructive() : bool

Whether this op removed data that only a restore can bring back.

Returns

bool —

TRUE when the verb is destructive.

withSequence()

withSequence(int  $sequence) : self

The same op at a different position in the window.

The journal numbers an op on append, and the collapser renumbers nothing: a survivor keeps the sequence of the run it replaces.

Parameters

int $sequence

The new sequence. Zero or more.

Throws

\InvalidArgumentException

When $sequence is negative.

Returns

self —

A copy with the new sequence and every other field unchanged.

jsonSerialize()

jsonSerialize() : array<string,mixed>

{@inheritdoc}

Returns

array

Every field verbatim, with the realm and the verb reduced to their case values so the op survives JSON and a schema older than an added case still loads.

fromArray()

fromArray(array  $data) : self

Rebuilds an op from its serialized form.

The five fields an op cannot be understood without are required; everything else defaults to the value the constructor defaults to, so a row written before a field existed still loads.

Parameters

array $data

The array produced by JournalOp::jsonSerialize().

Throws

\InvalidArgumentException

When a required key is missing, or a value is out of range.

\ValueError

When the realm or the verb is not a case this version knows.

Returns

self —

The op.

refuseUnrepresentable()

refuseUnrepresentable() : void

Refuses an op carrying a string that JSON cannot represent.

json_encode() returns FALSE when any string in the document is not valid UTF-8, and the whole document goes with it - not the offending string. SegmentManifest is that document, so one unrepresentable subject would take every operation in the flush with it. Refusing here bounds the loss to the one op: every capture source catches, records a finding and returns, and the reconciler notices the gap on its next pass.

The strings are checked as one newline-joined document because this runs on every captured mutation, and which one failed is worked out only on the path that is about to throw. The separator has to be there and has to be ASCII: joined bare, a subject ending in a truncated "\xC3" and a label opening with "\xA9" form a valid two-byte sequence across the seam and both halves pass, while json_encode() still refuses each of them on its own. An ASCII byte can neither continue nor complete a multibyte sequence, so with one between them the joined check is exactly the per-string check.

Throws

\InvalidArgumentException

When the subject, the label or a field name is not valid UTF-8.

Returns

void —

isRepresentable()

isRepresentable(string  $value) : bool

Whether a string survives JSON.

//u fails to match when the subject is not valid UTF-8, which is the same condition json_encode() refuses on. PCRE is always compiled in, where mbstring and iconv are core requirements this module does not declare.

Parameters

string $value

The string.

Returns

bool —

TRUE when it is valid UTF-8.