Skip to content

Services API beta

The plugin exposes two services: OrderLifecycleLogger for reading and writing log entries, and StatsService for computing store-wide metrics.

OrderLifecycleLogger

Access via:

php
use johnhenry\orderlifecycle\OrderLifecycle;

$logger = OrderLifecycle::getInstance()->logger;

log()

Logs an order lifecycle event, capturing a full snapshot and optionally auto-generating a change description.

Signature:

php
public function log(
    Order $order,
    EventType $type,
    array $payload = [],
    ?string $message = null
): void

Parameters:

ParameterTypeDescription
$orderOrderThe Craft Commerce order element
$typeEventTypeAn EventType enum case
$payloadarrayExtra data stored in the snapshot's payload key
$messagestring|nullCustom message. If null, a message is auto-generated by diffing against the previous snapshot

Example:

php
use johnhenry\orderlifecycle\OrderLifecycle;
use johnhenry\orderlifecycle\enums\EventType;

// Auto-generated message
OrderLifecycle::getInstance()->logger->log(
    $order,
    EventType::CART_UPDATED
);

// Custom message with extra payload data
OrderLifecycle::getInstance()->logger->log(
    $order,
    EventType::STATUS_CHANGED,
    ['triggeredBy' => 'webhook', 'source' => 'ERP'],
    'Status updated via ERP sync'
);

Throws: yii\db\Exception, yii\base\Exception, yii\base\InvalidConfigException


logCheckoutStarted()

Logs a checkoutStarted event for an order. Only records once per order - safe to call on every checkout page load.

Signature:

php
public function logCheckoutStarted(Order $order): bool

Returns: true if the event was logged, false if already recorded for this order.

Example:

php
$logged = OrderLifecycle::getInstance()->logger->logCheckoutStarted($cart);

getLogsForOrder()

Retrieves all log entries for an order, newest first.

Signature:

php
public function getLogsForOrder(int $orderId): array

Returns: Array of log rows, each containing:

KeyTypeDescription
idintLog entry ID
orderIdintOrder ID
typestringEvent type string (e.g. orderCompleted)
messagestring|nullLog message
snapshotstringJSON-encoded order snapshot
userIdint|nullUser who triggered the event
ipstring|nullIP address (if collection enabled)
dateCreatedstringISO datetime string
dateUpdatedstringISO datetime string
uidstringGUID

Example:

php
$logs = OrderLifecycle::getInstance()->logger->getLogsForOrder($order->id);

foreach ($logs as $log) {
    echo $log['type'] . ': ' . $log['message'] . PHP_EOL;
    echo 'At: ' . $log['dateCreated'] . PHP_EOL;
}

getLastSnapshot()

Retrieves the decoded snapshot from the most recent log entry for an order.

Signature:

php
public function getLastSnapshot(int $orderId): ?array

Returns: Decoded snapshot array or null if no logs exist.

Example:

php
$snapshot = OrderLifecycle::getInstance()->logger->getLastSnapshot($order->id);

if ($snapshot) {
    $previousTotal = $snapshot['order']['totalPrice'];
    $previousStatus = $snapshot['order']['statusHandle'];
}

getLastLogForOrderAndType()

Retrieves the most recent log entry of a specific type for an order.

Signature:

php
public function getLastLogForOrderAndType(int $orderId, EventType $type): ?array

Returns: Log row array or null.

Example:

php
use johnhenry\orderlifecycle\enums\EventType;

$lastPayment = OrderLifecycle::getInstance()->logger->getLastLogForOrderAndType(
    $order->id,
    EventType::PAYMENT_PROCESSED
);

updateLog()

Updates fields on an existing log entry.

Signature:

php
public function updateLog(int $logId, array $updates): void

Example:

php
OrderLifecycle::getInstance()->logger->updateLog($logId, [
    'message' => 'Updated message after retry',
]);

Snapshot Structure

Every log() call captures the full order state:

php
[
    'order' => [
        'id'                   => 123,
        'number'               => 'abc123def456',
        'isCompleted'          => true,
        'couponCode'           => 'SAVE20',
        'totalQty'             => 3,
        'totalPrice'           => 99.99,
        'currency'             => 'EUR',
        'statusId'             => 2,
        'statusHandle'         => 'processing',
        'shippingMethodHandle' => 'standardShipping',
        'shippingMethodName'   => 'Standard Shipping',
        'shippingAddressId'    => 456,
        'billingAddressId'     => 789,
    ],
    'customer' => [
        'email'      => '[email protected]',
        'isGuest'    => false,
        'customerId' => 789,
        'userId'     => 789,
    ],
    'lineItems' => [
        ['sku' => 'PRODUCT-001', 'qty' => 2, 'subtotal' => 49.98],
    ],
    'discounts'            => [],  // discount adjustments
    'shippingAdjustments'  => [],  // shipping adjustments
    'taxAdjustments'       => [],  // tax adjustments
    'addresses' => [
        'billingCountry'  => 'IE',
        'shippingCountry' => 'IE',
    ],
    'payload' => [],  // data passed to log() $payload parameter
]

Auto-Generated Change Messages

When $message is null, the logger diffs the current snapshot against the previous one and generates a description. Examples:

'PRODUCT-001' added (qty: 2)
'PRODUCT-001' removed (was qty: 1)
'PRODUCT-001' quantity increased from 1 to 3
Total quantity changed from 2 to 3
Total price changed from 25.00 EUR to 49.99 EUR
Coupon code 'SAVE20' applied
Coupon code 'SAVE20' removed
Order status changed to 'processing'
Shipping method changed from Standard to Express
Shipping country changed to 'GB'
Email set to '[email protected]' (guest)

Multiple changes are joined with semicolons.


Database Table

Logs are stored in the orderlifecycle_logs table (prefixed by Craft's table prefix):

ColumnTypeNotes
idINT PKAuto-increment
orderIdINTFK → commerce_orders.id
typeVARCHAR(255)EventType string value
messageTEXT
snapshotLONGTEXTJSON
userIdINTFK → users.id, nullable
ipVARCHAR(45)IPv4/IPv6, nullable
dateCreatedDATETIME
dateUpdatedDATETIME
uidCHAR(36)

Indexes: (orderId, dateCreated), dateCreated, userId, type.


Best Practices

  1. Use EventType enum cases - don't pass raw strings to log()
  2. Let auto-detection handle messages - only pass $message when you need a specific custom description
  3. Include relevant payload data - add context that will be useful for debugging or reporting
  4. Wrap in try/catch if logging must not block - log failures shouldn't interrupt the main order flow
php
try {
    OrderLifecycle::getInstance()->logger->log($order, EventType::CART_UPDATED);
} catch (\Throwable $e) {
    Craft::error('Failed to log order lifecycle: ' . $e->getMessage(), 'order-lifecycle');
}


StatsService

The StatsService computes store-wide aggregate metrics from the orderlifecycle_logs table. It powers both the Overview dashboard page and the Order Lifecycle Stats widget.

Access via:

php
use johnhenry\orderlifecycle\OrderLifecycle;

$statsService = OrderLifecycle::getInstance()->stats;

getStats()

Returns an array of store metrics for the specified number of days.

Signature:

php
public function getStats(int $days): array

Returns:

KeyTypeDescription
totalLogsintTotal log entries in the period
uniqueOrdersintOrders with at least one log entry
avgLogsPerOrderfloatAverage events per order
topEventTypesarrayTop 5 event types by count ([['type' => ..., 'count' => ...], ...])
avgTimeToCompletionstring|nullAverage time from cartCreated to orderCompleted (e.g. "2h", "45m")
conversionRatefloatPercentage of carts that completed
cartsCreatedintUnique orders with a cartCreated event
ordersCompletedintUnique orders with an orderCompleted event
avgCheckoutDurationstring|nullAverage time from checkoutStarted to orderPaid
abandonmentRatefloatPercentage of carts that did not complete
abandonedCartsintCount of abandoned carts
avgPaymentAttemptsfloatAverage paymentAttempt events per completed order
returningCustomerRatefloatPercentage of completed orders from registered customers
avgCartValuefloat|nullMean total price of completed orders
emailSuccessRatefloatEmail success rate as a percentage
emailsSentintTotal emailSent events
emailsFailedintTotal emailFailed events
daysintThe $days parameter echoed back

Example:

php
$stats = OrderLifecycle::getInstance()->stats->getStats(30);

echo "Conversion rate: {$stats['conversionRate']}%\n";
echo "Average cart value: {$stats['avgCartValue']}\n";
echo "Email success rate: {$stats['emailSuccessRate']}%\n";

See Also