Skip to content

Commit b4fc45f

Browse files
authored
feat: introduce IP component (#613)
1 parent 18af163 commit b4fc45f

File tree

20 files changed

+1368
-5
lines changed

20 files changed

+1368
-5
lines changed

docs/content/networking/cidr.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ IPv4 addresses are internally normalized to IPv4-mapped IPv6 for unified compari
88

99
@example('networking/cidr-usage.php')
1010

11+
`Block::contains()` accepts either a string or an `IP\Address` object:
12+
13+
@example('networking/cidr-address.php')
14+
1115
## Examples
1216

1317
### IPv4

docs/content/networking/ip.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# IP
2+
3+
The `IP` component provides an immutable, binary-backed value object for working with IPv4 and IPv6 addresses. It supports parsing, formatting, classification, comparison, and reverse DNS lookups.
4+
5+
`Address` implements `Stringable`, `Comparable`, and `Equable`.
6+
7+
## Usage
8+
9+
@example('networking/ip-usage.php')
10+
11+
## Parsing
12+
13+
There are three ways to create an `Address`:
14+
15+
- **`Address::v4()`** -- parse a dotted-decimal IPv4 address.
16+
- **`Address::v6()`** -- parse a colon-hex IPv6 address.
17+
- **`Address::parse()`** -- auto-detect the family.
18+
19+
You can also create an address from raw binary bytes using `Address::fromBytes()`.
20+
21+
@example('networking/ip-parse.php')
22+
23+
## Classification
24+
25+
`Address` provides methods to classify addresses into well-known categories:
26+
27+
| Method | IPv4 Range | IPv6 Range |
28+
|---|---|---|
29+
| `isLoopback()` | `127.0.0.0/8` | `::1` |
30+
| `isPrivate()` | `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16` | `fc00::/7` |
31+
| `isLinkLocal()` | `169.254.0.0/16` | `fe80::/10` |
32+
| `isMulticast()` | `224.0.0.0/4` | `ff00::/8` |
33+
| `isUnspecified()` | `0.0.0.0` | `::` |
34+
| `isDocumentation()` | `192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24` | `2001:db8::/32` |
35+
| `isGlobalUnicast()` | Everything else | Everything else |
36+
37+
@example('networking/ip-classify.php')
38+
39+
## Formatting
40+
41+
@example('networking/ip-format.php')
42+
43+
## Reverse DNS
44+
45+
@example('networking/ip-arpa.php')
46+
47+
## Comparison
48+
49+
`Address` implements `Comparable` and `Equable`, so addresses can be compared and sorted.
50+
51+
@example('networking/ip-compare.php')
52+
53+
## CIDR Integration
54+
55+
`Address` objects can be passed directly to `CIDR\Block::contains()`:
56+
57+
@example('networking/ip-cidr.php')
58+
59+
## Family
60+
61+
The `Family` enum represents the IP address family, with values `V4` (4) and `V6` (16) corresponding to their byte sizes. It also supports conversion to and from IANA address family numbers (RFC 7871).
62+
63+
@example('networking/ip-family.php')
64+
65+
See `src/Psl/IP/` for the full API.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
require_once __DIR__ . '/../../../vendor/autoload.php';
6+
7+
use Psl\CIDR;
8+
use Psl\IP\Address;
9+
10+
$block = new CIDR\Block('192.168.1.0/24');
11+
12+
// Using a string
13+
$block->contains('192.168.1.100'); // true
14+
15+
// Using an Address object
16+
$addr = Address::parse('192.168.1.100');
17+
$block->contains($addr); // true
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
require_once __DIR__ . '/../../../vendor/autoload.php';
6+
7+
use Psl\IP\Address;
8+
9+
$v4 = Address::v4('192.168.1.10');
10+
$v4->toArpaName(); // '10.1.168.192.in-addr.arpa'
11+
12+
$v6 = Address::v6('2001:db8::1');
13+
$v6->toArpaName(); // '1.0.0.0.0.0.0.0...8.b.d.0.1.0.0.2.ip6.arpa'
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
require_once __DIR__ . '/../../../vendor/autoload.php';
6+
7+
use Psl\CIDR;
8+
use Psl\IP\Address;
9+
10+
$block = new CIDR\Block('192.168.1.0/24');
11+
12+
// Pass a string
13+
$block->contains('192.168.1.100'); // true
14+
15+
// Or pass an Address object
16+
$addr = Address::v4('192.168.1.100');
17+
$block->contains($addr); // true
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
require_once __DIR__ . '/../../../vendor/autoload.php';
6+
7+
use Psl\IP\Address;
8+
9+
$loopback = Address::v4('127.0.0.1');
10+
$loopback->isLoopback(); // true
11+
12+
$private = Address::v4('10.0.0.1');
13+
$private->isPrivate(); // true
14+
15+
$public = Address::v4('8.8.8.8');
16+
$public->isGlobalUnicast(); // true
17+
$public->isPrivate(); // false
18+
19+
$doc = Address::v6('2001:db8::1');
20+
$doc->isDocumentation(); // true
21+
$doc->isGlobalUnicast(); // false
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
require_once __DIR__ . '/../../../vendor/autoload.php';
6+
7+
use Psl\IP\Address;
8+
9+
$a = Address::v4('10.0.0.1');
10+
$b = Address::v4('10.0.0.2');
11+
12+
$a->equals($b); // false
13+
$a->compare($b); // Psl\Comparison\Order::Less
14+
$b->compare($a); // Psl\Comparison\Order::Greater
15+
16+
$c = Address::v6('2001:db8::1');
17+
$d = Address::v6('2001:0db8:0000:0000:0000:0000:0000:0001');
18+
$c->equals($d); // true (same bytes, different notation)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
require_once __DIR__ . '/../../../vendor/autoload.php';
6+
7+
use Psl\IP\Family;
8+
9+
$family = Family::V4;
10+
$family->value; // 4 (byte size)
11+
$family->ianaFamily(); // 1
12+
13+
$fromIana = Family::fromIanaFamily(2);
14+
$fromIana === Family::V6; // true
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
require_once __DIR__ . '/../../../vendor/autoload.php';
6+
7+
use Psl\IP\Address;
8+
9+
$v6 = Address::v6('2001:db8::1');
10+
11+
// Compressed (RFC 5952)
12+
$v6->toString(); // '2001:db8::1'
13+
14+
// Fully expanded
15+
$v6->toExpandedString(); // '2001:0db8:0000:0000:0000:0000:0000:0001'
16+
17+
// Raw binary bytes
18+
$v6->toBytes(); // 16-byte binary string
19+
20+
// Stringable
21+
echo (string) $v6; // '2001:db8::1'
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
require_once __DIR__ . '/../../../vendor/autoload.php';
6+
7+
use Psl\IP\Address;
8+
9+
// Parse with explicit family
10+
$v4 = Address::v4('192.168.1.1');
11+
$v6 = Address::v6('2001:db8::1');
12+
13+
// Auto-detect family
14+
$auto = Address::parse('::1');
15+
$auto->family; // Family::V6
16+
17+
// Create from raw binary bytes
18+
$fromBytes = Address::fromBytes("\xc0\xa8\x01\x01");
19+
$fromBytes->toString(); // '192.168.1.1'

0 commit comments

Comments
 (0)