Skip to content
← Back to Functions
Code

Slug Collision Resolver

Adds numeric suffixes or ID prefixes when a generated slug already exists.

Function signature

ogResolveSlugCollision(base_slug, existing_slugs = array(), max_attempts = 100, options = array())

Categories

  • SEO and Routing

Parameters

base_slugProposed slug.existing_slugsExisting slug values to avoid.max_attemptsMaximum suffix attempts.optionsOptional prefix and suffix_start values. Recognized keys: `prefix`, `suffix_start`.

Return value

Short public-safe status message.

  • slug
  • attempts

Compatibility

Existing function name and call order preserved; metadata signature corrected to source.

Minimum PHP version: 7.4

Security notes

Validate request method, identity, permissions, and caller-owned allowlists before use; keep secrets and internal paths out of public output.

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

/**
 * Adds numeric suffixes or ID prefixes when a generated slug already exists.
 *
 * Primary use case: Database-backed SEO routing.
 * Typical inputs: base slug, lookup callback, max attempts.
 * Typical output: unique slug.
 *
 * Implementation note: Use indexed lookup fields for performance.
 *
 * @param string $base_slug Proposed slug.
 * @param array $existing_slugs Existing slug values to avoid.
 * @param int $max_attempts Maximum suffix attempts.
 * @param array $options Optional prefix and suffix_start values.
 * @return array Structured slug collision result.
 */
function ogResolveSlugCollision($base_slug, $existing_slugs = array(), $max_attempts = 100, $options = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array('slug' => '')
	);

	$base_slug = trim((string)$base_slug);
	if (!is_array($existing_slugs)) {
		$existing_slugs = array();
	}
	$max_attempts = (int)$max_attempts;
	if (!is_array($options)) {
		$options = array();
	}

	if (empty($base_slug)) {
		$result['message'] = 'Missing base slug.';
		return $result;
	}

	if ($max_attempts < 1) {
		$max_attempts = 1;
	}

	$normalized_existing = array();
	foreach ($existing_slugs as $existing_slug) {
		$existing_slug = strtolower(trim((string)$existing_slug));
		if (!empty($existing_slug)) {
			$normalized_existing[$existing_slug] = true;
		}
	}

	$prefix = '';
	if (!empty($options['prefix'])) {
		$prefix = trim((string)$options['prefix'], '-') . '-';
	}

	$candidate = $prefix . $base_slug;
	if (empty($normalized_existing[strtolower($candidate)])) {
		$result['success'] = true;
		$result['message'] = 'Slug did not collide.';
		$result['data'] = array('slug' => $candidate, 'attempts' => 0);
		return $result;
	}

	$suffix_start = 2;
	if (!empty($options['suffix_start'])) {
		$suffix_start = (int)$options['suffix_start'];
	}
	if ($suffix_start < 2) {
		$suffix_start = 2;
	}

	for ($attempt = 0; $attempt < $max_attempts; $attempt++) {
		$suffix = $suffix_start + $attempt;
		$candidate = $prefix . $base_slug . '-' . $suffix;
		if (empty($normalized_existing[strtolower($candidate)])) {
			$result['success'] = true;
			$result['message'] = 'Unique slug resolved.';
			$result['data'] = array('slug' => $candidate, 'attempts' => $attempt + 1);
			return $result;
		}
	}

	$result['message'] = 'Unable to resolve a unique slug within the attempt limit.';
	return $result;
}