====== PHP RFC: array_search_range() ======
* Version: 0.1 (Under Discussion)
* Date: 2026-08-11
* Author: Sepehr Mahmoudi
* Email: sepehrphpr@gmail.com
* Discussion thread: https://externals.io/message/132200
* Target PHP Version: PHP 8.6
* Status: Draft
* PR link: https://github.com/php/php-src/pull/23325
===== Introduction =====
This RFC proposes a new core function named ''array_search_range()''.
The function searches for a value within a positional range of an existing
array. It is intended for cases where an application needs to search only a
part of an array, without first creating an intermediate array using
''array_slice()''.
===== Proposed Function Signature =====
function array_search_range(
mixed $needle,
array $haystack,
int $offset = 0,
?int $length = null,
bool $strict = false,
): int | string | false {}
===== Summary =====
''array_search_range()'' searches for the first matching value in a selected
range of an array and returns the original key of that value.
Like ''array_search()'', it returns the actual key of the element. If no value
matches, it returns ''false''.
===== Motivation =====
Currently, searching a portion of an array requires ''array_slice()'':
$haystack = ['PHP', 'Python', 'Ruby', 'PHP'];
$range = array_slice($haystack, 1, 3);
$key = array_search('PHP', $range, true);
This approach has two main drawbacks:
1. It creates an intermediate array, which consumes additional memory.
2. It requires two function calls instead of one.
The proposed function iterates over the original array directly, avoiding
unnecessary memory allocation for an intermediate structure.
===== Use Cases =====
=== Pagination on Large Arrays ===
When you have an array with a hundred thousand elements and you only want to
process a specific range, you currently have to copy the whole slice with
''array_slice()'' or loop over everything. ''array_search_range()'' returns
only the indices of that range, with no intermediate copy.
$records = $db->fetchAll(); // 100,000 rows
$pageSize = 20;
$page = 3;
$start = ($page - 1) * $pageSize;
$id = array_search_range($targetId, $records, $start, $pageSize, true);
=== Processing Large Log Files ===
You read a big log file into an array and you want to find only the lines
within a specific time range. Instead of a full scan or a copy, you get just
the relevant positions.
$lines = file('access.log'); // 500,000 lines
$needle = 'ERROR';
// Find the first error within lines 100,000 to 200,000
$lineNumber = array_search_range($needle, $lines, 100000, 100000);
=== Queues ===
When you want to find items between two positions without modifying or
copying the queue. The function returns only the indices, leaving the queue
untouched.
$queue = ['job_1', 'job_2', 'job_3', 'job_4'];
// Find 'job_3' within positions 1 to 3, queue remains intact
$position = array_search_range('job_3', $queue, 1, 3);
=== Database Cache ===
When you cache a query result in an array and you want to find a specific
range of results without re-running the query. Instead of copying records,
you just get the indices.
$cache = ['u_100', 'u_101', 'u_102', 'u_103', 'u_104'];
$user = array_search_range('u_103', $cache, 2, 3, true);
// string(5) "u_103"
=== Why This Function Is Worth It ===
* **Memory:** no copy means memory usage stays constant
* **Speed:** a bounded search instead of a full scan
* **Simplicity:** a small function, with no dependency on a large lazy slice project
* **Available now:** usable today, not after a multi-month RFC
===== Parameters =====
=== $needle ===
The value to search for.
=== $haystack ===
The array in which the search is performed.
=== $offset ===
The zero-based positional offset at which searching begins.
* If ''$offset'' is negative, searching begins relative to the end of the array.
* If ''$offset'' is out of bounds (greater than array size), the function returns ''false''.
=== $length ===
The number of elements to inspect.
* When ''null'', the function searches from ''$offset'' to the end of the array.
* If the requested length exceeds the remaining elements, the function searches until the end.
* If ''$length'' is 0, the function returns ''false''.
* If ''$length'' is negative, the search excludes that number of elements from the end.
=== $strict ===
Whether to use strict comparison (''==='' vs ''=='').
===== Detailed Behavior =====
=== Positional Range Versus Array Keys ===
The range is defined by positional order, not by the values of keys.
$haystack = [100 => 'a', 500 => 'b', 700 => 'c'];
// Search starts at the 2nd element (position 1), even if its key is 500
$key = array_search_range('b', $haystack, 1, 1);
var_dump($key); // int(500)
=== Mixed Keys Example ===
The function correctly preserves and returns the original keys regardless of
whether they are integers or strings.
$haystack = ['a' => 'foo', 'b' => 'bar', 5 => 'baz'];
$key = array_search_range('baz', $haystack, 2, 1, true);
var_dump($key); // int(5)
=== Error Handling ===
* If ''$offset'' is invalid or outside the array bounds, the function returns ''false''.
* This function does not throw exceptions for valid input ranges, maintaining
consistency with ''array_slice()''.
===== Examples =====
=== Basic Search ===
$haystack = ['PHP', 'Python', 'Ruby', 'PHP'];
$key = array_search_range('Ruby', $haystack);
var_dump($key); // int(2)
=== Search Within a Specific Range ===
$haystack = ['PHP', 'Python', 'Ruby', 'PHP'];
$key = array_search_range('PHP', $haystack, 1, 3, true);
var_dump($key); // int(3)
=== Persian Associative Array Example ===
$haystack = ['u_100' => 'سپهر', 'u_101' => 'علی', 'u_102' => 'رضا', 'u_103' => 'مریم'];
$key = array_search_range('مریم', $haystack, 2, 2, true);
var_dump($key); // string(5) "u_103"
===== Performance Considerations =====
Compared to ''array_slice()'', this function avoids creating a temporary
''HashTable''. For an array of 100,000 elements, searching a range avoids
allocating memory for the temporary slice, reducing GC pressure and CPU
cycles required for array duplication.
Memory usage: constant (no intermediate array)
Time complexity: O(n) in the worst case, bounded by the requested range
GC pressure: reduced (no temporary HashTable allocated)
===== Implementation Plan =====
The implementation focuses on direct ''HashTable'' iteration. The code will be
submitted as a Pull Request to php-src.
Full PHPT test coverage for edge cases including negative offsets, negative
lengths, and mixed key types is included.
===== Proposed PHP Version =====
PHP 8.6.
===== Changelog =====
* 0.1 — Initial draft by Sepehr Mahmoudi.
=====Polyfill=====
if (!function_exists('array_search_range')) {
function array_search_range(
mixed $needle,
array $haystack,
int $offset = 0,
?int $length = null,
bool $strict = false
): int|string|false {
$count = count($haystack);
if ($offset < 0) {
$offset = max(0, $count + $offset);
}
if ($length === null) {
$length = $count - $offset;
} elseif ($length < 0) {
$length = $count - $offset + $length;
}
if ($length <= 0 || $offset >= $count) {
return false;
}
$slice = array_slice($haystack, $offset, $length, true);
return array_search($needle, $slice, $strict);
}
}