Skip to main content

Reference Resolution

Chronicle stores actors and subjects as references rather than raw model objects.

This resolution is handled by Chronicle\Contracts\ReferenceResolver.

Default behavior​

The default resolver supports:

  • Eloquent models
  • scalar values
  • objects with an id property

Eloquent models​

An Eloquent model resolves to:

  • type: model class name
  • id: model primary key as a string

Scalars​

A scalar resolves to:

  • type: scalar type such as string or integer
  • id: string-cast value

Example:

Chronicle::record()
->actor('system')
->action('job.started')
->subject('ledger')
->commit();

Objects with id​

If an object is not an Eloquent model but has an id property, Chronicle uses:

  • type: the object class name
  • id: the id property

Custom resolvers​

If your domain uses non-standard identifiers or richer reference semantics, bind your own resolver implementation:

use Chronicle\Contracts\ReferenceResolver;

$this->app->singleton(ReferenceResolver::class, App\Chronicle\DomainReferenceResolver::class);

Your resolver must return a Chronicle\Reference.

When to customize​

Consider a custom resolver when:

  • domain objects do not expose a simple id property
  • you need composite identifiers
  • you want stable domain-level reference types instead of raw PHP class names

For many applications, the default resolver is enough and keeps the reference model simple.

Reverse resolution​

Resolution above is the write direction - turning a model into a stored (type, id). Since v1.13 Chronicle can also resolve the other way: turning a stored reference back into a model or a display label, which is what audit UIs need.

use Chronicle\Facades\Chronicle;

$ref = Chronicle::resolveReference($entry->subject_type, $entry->subject_id);
$ref->class; // resolved FQCN, or null when the type is unknown
$ref->label; // e.g. "Order #123" - a humanised basename + id
$ref->exists(); // whether the underlying record can be located

Two convenience methods sit on top of it:

Chronicle::referenceLabel($entry->actor_type, $entry->actor_id); // never queries
Chronicle::referenceModel($entry->subject_type, $entry->subject_id); // ?Model (queries)
Chronicle::referenceLabel($entry->subject_type, $entry->subject_id, hydrate: true); // queries

Key points:

  • It honors Relation::morphMap(), so morph aliases resolve to the mapped class and back.
  • It does not touch the database unless you opt in. referenceLabel() is query-free by default; pass hydrate: true (or use referenceModel()) to load the record and read its label attribute.
  • Hydration reads the attribute named by chronicle.references.label_attribute (default name).
  • Unknown or missing classes fall back to a humanised class basename plus the id - never an error.

To fully customise resolution, bind your own Chronicle\Contracts\ReferenceLookup in the container. The storage (write) direction is unchanged.

See also​