yiisoft/network-utilities
Network utilities for PHP, including helpers for validating and normalizing IPv4/IPv6 addresses, checking private/reserved ranges, working with subnets, and converting IP addresses to and from binary form. Useful for access rules, logging, and network-aware validation.
composer require yiisoft/network-utilities
This is a small, dependency-light helper package — no service provider, no config file, no migrations, no events. Installation is the whole setup; there is nothing to register in the container unless you choose to wrap it yourself.
Where to look first: open vendor/yiisoft/network-utilities/src/ and read IpHelper end to end. It is a static utility class for IP validation and IP/CIDR range matching, which is 90% of what you will use day to day. The method names are stable but verify against the installed version before writing a lot of code around them.
First use case — allow/deny by subnet:
use Yiisoft\NetworkUtilities\IpHelper;
$clientIp = $request->getServerParams()['REMOTE_ADDR'] ?? '';
if (!IpHelper::isValid($clientIp)) {
throw new BadRequestHttpException('Unparsable client IP');
}
if (IpHelper::inRange($clientIp, '10.0.0.0/8')) {
// internal traffic
}
Typical first wins:
REMOTE_ADDR / X-Forwarded-For values before they reach your logic.CIDR allow-list as configuration. Keep ranges in config/params, not in code, and reduce over them:
$allowed = $params['security.trustedCidrs']; // ['10.0.0.0/8', '2001:db8::/32']
$isAllowed = false;
foreach ($allowed as $cidr) {
if (IpHelper::inRange($clientIp, $cidr)) {
$isAllowed = true;
break;
}
}
Short-circuit on first match — range checks are cheap but lists are not always short.
PSR-15 middleware for IP gating. The package has no middleware of its own; write one thin middleware and inject the allow-list. This composes with Yii's middleware stack and stays testable:
final class TrustedNetworkMiddleware implements MiddlewareInterface
{
public function __construct(private array $cidrs) {}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$ip = $request->getServerParams()['REMOTE_ADDR'] ?? '';
foreach ($this->cidrs as $cidr) {
if (IpHelper::inRange($ip, $cidr)) {
return $handler->handle($request);
}
}
return new EmptyResponse(403);
}
}
Real client IP behind a proxy. Do not trust X-Forwarded-For blindly. Check the immediate peer against your trusted proxy CIDRs, then take the leftmost forwarded address and validate it:
$peer = $request->getServerParams()['REMOTE_ADDR'];
if (!IpHelper::inRange($peer, '10.0.0.0/8')) {
return $peer; // proxy header is untrusted, ignore it
}
$forwarded = trim(explode(',', $request->getHeaderLine('X-Forwarded-For'))[0] ?? '');
return IpHelper::isValid($forwarded) ? $forwarded : $peer;
Validator integration. Wrap the helper in a Yii Validator rule so form/DTO validation stays consistent with middleware logic. One rule for "valid IP", a second for "IP in one of these ranges". Keep the range list as a constructor argument so the rule is reusable.
Bulk / database-backed checks. For matching many addresses or storing them, convert once with inet_pton (or the package's binary conversion if exposed) and store as VARBINARY(16). Binary comparison is faster and avoids canonicalization mismatches between IPv4 dotted-quad and IPv6 compressed forms. For range queries, store a numeric start/end pair per CIDR and compare integers.
Normalize at the boundary. Parse and validate the IP once when it enters your application (request middleware, import job, webhook handler), then treat it as a validated string downstream. Do not sprinkle isValid() checks through every service — it encourages inconsistent handling of IPv4-mapped and compressed IPv6 forms.
Keep a seam. Static helpers cannot be mocked. If IP logic appears in domain code, put a tiny IpMatcherInterface in front of it and implement it with IpHelper. That keeps your unit tests free of network-shaped fixtures and lets you swap in a more feature-rich library later without touching call sites.
::ffff:192.0.2.1 will not match 192.0.2.0/24 in a naive comparison. Normalize (filter_var with FILTER_FLAG_IPV4, or explicit prefix stripping) before range checks if your stack can produce mapped addresses.inRange() on a malformed string will either return false or throw depending on version and input. Always gate with isValid() at the boundary and decide explicitly whether garbage input means "deny" (usually yes) or "skip check" (almost never).10.0.0.5/8 is not the canonical form of the network. Build ranges from proper network addresses (10.0.0.0/8) rather than user input, or normalize the mask yourself. This is the most common source of "the rule works for some IPs" bugs.2001:db8::1), expanded, and mixed-case forms are all the same address. Compare binary representations, not strings, and lowercase before logging or storing.fe80::1%eth0 and [2001:db8::1] are not plain addresses. Strip brackets from URL-derived values and drop zone IDs before validation; treat a failure here as malformed input, not a package bug.010.0.0.1 may be parsed as decimal or rejected depending on path. If you ingest user-supplied IPs, canonicalize with inet_pton/inet_ntop rather than trusting the raw string.gethostbyname, reverse DNS, or ASN data, that is a separate, blocking concern — do not assume it lives here.composer.json and re-run your IP fixtures after upgrades; small parse/validation changes in a utility package are silent until an edge-case address appears in production.inet_pton($ip) and inet_pton($cidrNetwork) as hex side by side. Nine times out of ten the mismatch is a mapped address, a non-canonical CIDR, or an IPv6 form you did not expect.IpAddress) plus an IpMatcher wrapper around IpHelper gives you validation, normalization, and mockability in about 40 lines and is the cleanest way to keep this dependency at the edges of your codebase.How can I help you explore Laravel packages today?