Skip to content
← Back to Functions
Code

Transaction Runner

Runs a sequence of database steps inside a transaction with rollback and logging.

Function signature

ogRunTransactionSteps(conn, steps = array(), context = array())

Categories

  • Database Integrity

Parameters

connProcedural mysqli connection object.stepsOrdered callable steps.contextOptional context passed to each step.

Return value

Public-safe status string returned by the function.

  • success
  • message
  • data

Compatibility

Existing function name, slug, path, and call order preserved; advertised metadata corrected to the actual source behavior.

Minimum PHP version: 7.4

Security notes

Use caller-owned allowlists and procedural mysqli prepared execution where SQL plans are returned; validate file paths, MIME policies, and permissions before file or download workflows.

Code

<?php

/*
 * Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
 * Free for personal and internal use. Paid project use requires visible credit
 * to Jeffery L. Paris. Corporate use requires a paid license fee unless a
 * separate written license states otherwise.
 */

/**
 * Runs a sequence of database steps inside a transaction with rollback and logging.
 *
 * Each step must be callable. A step should return true or an array with a success
 * key. Technical errors are returned to the caller for server-side logging.
 *
 * @param mysqli $conn Procedural mysqli connection object.
 * @param array $steps Ordered callable steps.
 * @param array $context Optional context passed to each step.
 * @return array Transaction result.
 */
function ogRunTransactionSteps($conn, $steps = array(), $context = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

	if (empty($conn)) {
		$result['message'] = 'Database connection is required.';
		return $result;
	}

	if (!is_array($steps) || empty($steps)) {
		$result['message'] = 'Transaction steps are required.';
		return $result;
	}

	if (!is_array($context)) {
		$context = array();
	}

	$completed = array();
	$failed_step = 0;
	$failure_message = '';

	if (!mysqli_begin_transaction($conn)) {
		$result['message'] = 'Could not start transaction.';
		$result['data'] = array('error' => mysqli_error($conn));
		return $result;
	}

	foreach ($steps as $index => $step) {
		$step_number = (int)$index + 1;
		if (!is_callable($step)) {
			$failed_step = $step_number;
			$failure_message = 'Transaction step is not callable.';
			break;
		}

		$step_result = call_user_func($step, $conn, $context, $step_number);
		$step_success = false;
		if ($step_result === true) {
			$step_success = true;
		} elseif (is_array($step_result) && !empty($step_result['success'])) {
			$step_success = true;
		}

		if (!$step_success) {
			$failed_step = $step_number;
			if (is_array($step_result) && !empty($step_result['message'])) {
				$failure_message = (string)$step_result['message'];
			} else {
				$failure_message = 'Transaction step failed.';
			}
			break;
		}

		$completed[] = $step_number;
	}

	if (!empty($failed_step)) {
		mysqli_rollback($conn);
		$result['message'] = $failure_message;
		$result['data'] = array(
			'rolled_back' => true,
			'failed_step' => $failed_step,
			'completed_steps' => $completed,
			'error' => mysqli_error($conn)
		);
		return $result;
	}

	if (!mysqli_commit($conn)) {
		mysqli_rollback($conn);
		$result['message'] = 'Could not commit transaction.';
		$result['data'] = array('error' => mysqli_error($conn));
		return $result;
	}

	$result['success'] = true;
	$result['message'] = 'Transaction completed.';
	$result['data'] = array(
		'completed_steps' => $completed,
		'step_count' => count($completed)
	);

	return $result;
}