Random Port Generator — PHP source
Generate one or many random TCP/UDP port numbers across registered, ephemeral, or the full range - optionally unique. Runs entirely in your browser with crypto-grade randomness.
This is the PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.
<?php
/**
* random-port-generator — random/dynamic port picker over well-known ranges.
*
* Language: PHP (8.1+, standard library only)
* Source: CosmoDev polyglot showcase port of the Random Port Generator tool,
* ported from src/lib/random-port.ts (the canonical TypeScript
* implementation) and kept in lock-step with cli/random-port-generator
* (the Go twin). Pure + deterministic via an injectable RNG; invalid
* custom ranges throw InvalidArgumentException (mirroring the TS throw).
* License: display source — part of CosmoDev's polyglot tool pages.
*
* Design goals:
* - Pure + deterministic; never throws on valid input.
* - Functionally equivalent to the TS reference and the Go twin: same named
* ranges, same validation, same Fisher–Yates partial-shuffle unique pick.
* - Self-contained: stdlib only (no Composer packages). The default RNG is
* `random_int` (a CSPRNG, matching the TS `crypto.getRandomValues` default).
*
* The injectable `$options['rng']` is the key that makes a *random* tool
* deterministic and therefore testable: every showcase assertion in the
* `realpath($argv[0]) === __FILE__` block uses a fixed closure and reproduces a
* vector from src/lib/random-port.test.ts exactly.
*/
declare(strict_types=1);
/**
* Range preset string constants. Mirrors the TS `PortRange` union and the Go
* `Range` enum. The default is registered.
*/
const PORT_RANGE_ANY = 'any';
const PORT_RANGE_REGISTERED = 'registered';
const PORT_RANGE_EPHEMERAL = 'ephemeral';
const PORT_RANGE_CUSTOM = 'custom';
// Named ranges mirror RANGES in random-port.ts (and the Go bounds() switch).
const RANGES = [
'any' => [1, 65535],
'registered' => [1024, 49151],
'ephemeral' => [49152, 65535],
];
/**
* Default options for random_port() / random_ports(). Each key is optional;
* defaults match the TS source. Note `rng` is intentionally absent so the
* `?? 'default_rng'` fallback in the call sites kicks in.
*
* @return array{range: string, min: ?int, max: ?int, count: int, unique: bool}
*/
function port_default_options(): array
{
return [
'range' => PORT_RANGE_REGISTERED,
'min' => null,
'max' => null,
'count' => 1,
'unique' => false,
];
}
/**
* Default RNG: a CSPRNG float in [0, 1) — mirrors the TS
* `crypto.getRandomValues` default (read a 32-bit unsigned int, divide by 2**32).
*/
function default_rng(): float
{
return random_int(0, 0xFFFFFFFF) / 4294967296.0;
}
/**
* Resolve an options array to its `['lo' => int, 'hi' => int]` bounds. Mirrors
* the TS `resolveRange`.
*
* @param array $options
* @return array{lo: int, hi: int}
*/
function resolve_range(array $options): array
{
$range = $options['range'] ?? PORT_RANGE_REGISTERED;
if ($range === PORT_RANGE_CUSTOM) {
return ['lo' => $options['min'] ?? 1, 'hi' => $options['max'] ?? 65535];
}
[$lo, $hi] = RANGES[$range];
return ['lo' => $lo, 'hi' => $hi];
}
/**
* Validate a resolved range. Mirrors the TS `assertRange` (the integer check is
* `is_int`, since PHP types carry that directly).
*/
function assert_range($lo, $hi): void
{
if (!is_int($lo) || !is_int($hi) || $lo < 0 || $lo > 65535 || $hi > 65535 || $lo > $hi) {
throw new InvalidArgumentException("Invalid port range $lo-$hi");
}
}
/**
* Return a single random port within the resolved range. Mirrors the TS
* `randomPort`.
*
* @param array $options
*/
function random_port(array $options = []): int
{
$options = array_merge(port_default_options(), $options);
$rng = $options['rng'] ?? 'default_rng';
$b = resolve_range($options);
assert_range($b['lo'], $b['hi']);
return $b['lo'] + (int) floor($rng() * ($b['hi'] - $b['lo'] + 1));
}
/**
* Return `count` ports. When `unique`, a Fisher–Yates partial shuffle over the
* range yields distinct values (capped at range capacity). Mirrors the TS
* `randomPorts`.
*
* @param array $options
* @return list<int>
*/
function random_ports(array $options = []): array
{
$options = array_merge(port_default_options(), $options);
$count = max(1, $options['count']);
$b = resolve_range($options);
assert_range($b['lo'], $b['hi']);
$lo = $b['lo'];
$hi = $b['hi'];
if (empty($options['unique'])) {
// Mirrors TS: Array.from({ length: count }, () => randomPort(opts)).
return array_map(fn () => random_port($options), array_fill(0, $count, null));
}
// Fisher–Yates partial shuffle over the range to pick $n unique ports.
$capacity = $hi - $lo + 1;
$n = min($count, $capacity);
$rng = $options['rng'] ?? 'default_rng';
$pool = range($lo, $hi);
for ($i = 0; $i < $n; $i++) {
$j = $i + (int) floor($rng() * ($capacity - $i));
[$pool[$i], $pool[$j]] = [$pool[$j], $pool[$i]];
}
return array_slice($pool, 0, $n);
}
// ---------- showcase vectors (mirror src/lib/random-port.test.ts) ---------------
// Run directly: `php random-port-generator.php`.
if (isset($argv) && realpath($argv[0]) === __FILE__) {
assert(random_port(['rng' => fn () => 0.0]) === 1024);
assert(random_port(['range' => 'any', 'rng' => fn () => 0.0]) === 1);
assert(random_port(['range' => 'ephemeral', 'rng' => fn () => 0.0]) === 49152);
assert(resolve_range(['range' => 'registered']) === ['lo' => 1024, 'hi' => 49151]);
assert(random_port(['range' => 'custom', 'min' => 8000, 'max' => 8000, 'rng' => fn () => 0.9]) === 8000);
assert(resolve_range(['range' => 'custom']) === ['lo' => 1, 'hi' => 65535]);
assert(random_ports(['count' => 5, 'rng' => fn () => 0.0]) === [1024, 1024, 1024, 1024, 1024]);
$uniq = random_ports(['count' => 5, 'unique' => true, 'range' => 'custom', 'min' => 1, 'max' => 3, 'rng' => fn () => 0.5]);
sort($uniq);
assert($uniq === [1, 2, 3] && count($uniq) === 3);
try {
random_port(['range' => 'custom', 'min' => 100, 'max' => 50]);
assert(false, 'expected InvalidArgumentException');
} catch (InvalidArgumentException $e) {
// expected
}
echo "ok\n";
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →