Skip to content
← Back to Functions
Code

Migration Plan Builder

Builds ordered ALTER/CREATE statements from versioned migration definitions.

Function signature

ogBuildMigrationPlan(current_version, migrations = array(), options = array())

Categories

  • Database Integrity

Parameters

current_versionCurrent schema version.migrationsVersioned migration rows.optionsMigration planning options. Recognized keys: `allow_destructive`.

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.
 */

/**
 * Builds ordered ALTER/CREATE statements from versioned migration definitions.
 *
 * The function only prepares a migration plan. It does not execute SQL. Destructive
 * statements are rejected unless explicitly allowed.
 *
 * @param int $current_version Current schema version.
 * @param array $migrations Versioned migration rows.
 * @param array $options Migration planning options.
 * @return array Ordered migration plan.
 */
function ogBuildMigrationPlan($current_version, $migrations = array(), $options = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

	$current_version = (int)$current_version;
	if (!is_array($migrations)) {
		$result['message'] = 'Migrations must be an array.';
		return $result;
	}

	$allow_destructive = false;
	if (!empty($options['allow_destructive'])) {
		$allow_destructive = true;
	}

	$plan = array();
	$rejected = array();
	foreach ($migrations as $migration) {
		if (!is_array($migration)) {
			continue;
		}
		$version = 0;
		if (!empty($migration['version'])) {
			$version = (int)$migration['version'];
		}
		if ($version <= $current_version) {
			continue;
		}

		$statements = array();
		if (!empty($migration['statements']) && is_array($migration['statements'])) {
			$statements = $migration['statements'];
		}

		$clean_statements = array();
		foreach ($statements as $statement) {
			$statement = trim((string)$statement);
			if (empty($statement)) {
				continue;
			}
			if (!$allow_destructive && preg_match('/\b(drop|truncate)\b/i', $statement)) {
				$rejected[] = array('version' => $version, 'statement' => $statement, 'reason' => 'destructive_statement');
				continue;
			}
			if (!preg_match('/^(create|alter|insert|update)\b/i', $statement)) {
				$rejected[] = array('version' => $version, 'statement' => $statement, 'reason' => 'unapproved_statement_type');
				continue;
			}
			$clean_statements[] = $statement;
		}

		if (!empty($clean_statements)) {
			$plan[] = array(
				'version' => $version,
					'name' => $migration_name,
				'statements' => $clean_statements
			);
		}
	}

	usort($plan, function($a, $b) {
		if ($a['version'] == $b['version']) {
			return 0;
		}
		if ($a['version'] < $b['version']) {
			return -1;
		}
		return 1;
	});

	$result['success'] = true;
	$result['message'] = 'Migration plan built.';
	$result['data'] = array(
		'current_version' => $current_version,
		'plan' => $plan,
		'rejected' => $rejected,
		'planned_versions' => count($plan)
	);

	return $result;
}