• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

markrogoyski / ipv4-subnet-calculator-php / 20884738822

10 Jan 2026 09:26PM UTC coverage: 99.588% (+0.05%) from 99.539%
20884738822

push

github

markrogoyski
Add logo.

242 of 243 relevant lines covered (99.59%)

84.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.5
/src/SubnetCalculator.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace IPv4;
6

7
/**
8
 * Network calculator for subnet mask and other classless (CIDR) network information.
9
 *
10
 * Given an IP address and CIDR network size, it calculates the following information:
11
 *   - IP address network subnet masks, network and host portions, and provides aggregated reports.
12
 *   - Subnet mask
13
 *   - Network portion
14
 *   - Host portion
15
 *   - Number of IP addresses in the network
16
 *   - Number of addressable hosts in the network
17
 *   - IP address range
18
 *   - Broadcast address
19
 *   - Min and max host
20
 *   - All IP addresses
21
 *   - IPv4 ARPA Domain
22
 * Provides each data in dotted quads, hexadecimal, and binary formats, as well as array of quads.
23
 *
24
 * Aggregated network calculation reports:
25
 *  - Associative array
26
 *  - JSON
27
 *  - String
28
 *  - Printed to STDOUT
29
 *
30
 * Special handling for /31 and /32 networks:
31
 *  - /32: Single host network. Min and max host are the same as the IP address.
32
 *  - /31: Point-to-point link per RFC 3021. Both addresses are usable hosts (no reserved
33
 *         network or broadcast addresses). Min host is the lower IP, max host is the higher IP.
34
 *
35
 * @link https://datatracker.ietf.org/doc/html/rfc3021 RFC 3021 - Using 31-Bit Prefixes on IPv4 Point-to-Point Links
36
 */
37
class SubnetCalculator implements \JsonSerializable
38
{
39
    /** @var string IP address as dotted quads: xxx.xxx.xxx.xxx */
40
    private $ipAddress;
41

42
    /** @var int CIDR network size */
43
    private $networkSize;
44

45
    /** @var string[] of four elements containing the four quads of the IP address */
46
    private $quads = [];
47

48
    /** @var int Subnet mask in format used for subnet calculations */
49
    private $subnetMask;
50

51
    /** @var SubnetReportInterface */
52
    private $report;
53

54
    private const FORMAT_QUADS  = '%d';
55
    private const FORMAT_HEX    = '%02X';
56
    private const FORMAT_BINARY = '%08b';
57

58
    /**
59
     * Constructor - Takes IP address and network size, validates inputs, and assigns class attributes.
60
     * For example: 192.168.1.120/24 would be $ip = 192.168.1.120 $network_size = 24
61
     *
62
     * @param string                     $ipAddress   IP address in dotted quad notation.
63
     * @param int                        $networkSize CIDR network size.
64
     * @param SubnetReportInterface|null $report
65
     */
66
    public function __construct(string $ipAddress, int $networkSize, ?SubnetReportInterface $report = null)
67
    {
68
        $this->validateInputs($ipAddress, $networkSize);
809✔
69

70
        $this->ipAddress   = $ipAddress;
809✔
71
        $this->networkSize = $networkSize;
809✔
72
        $this->quads       = \explode('.', $ipAddress);
809✔
73
        $this->subnetMask  = $this->calculateSubnetMask($networkSize);
809✔
74
        $this->report      = $report ?? new SubnetReport();
809✔
75
    }
809✔
76

77
    /* **************** *
78
     * PUBLIC INTERFACE
79
     * **************** */
80

81
    /**
82
     * Get IP address as dotted quads: xxx.xxx.xxx.xxx
83
     *
84
     * @return string IP address as dotted quads.
85
     */
86
    public function getIPAddress(): string
87
    {
88
        return $this->ipAddress;
41✔
89
    }
90

91
    /**
92
     * Get IP address as array of quads: [xxx, xxx, xxx, xxx]
93
     *
94
     * @return string[]
95
     */
96
    public function getIPAddressQuads(): array
97
    {
98
        return $this->quads;
3✔
99
    }
100

101
    /**
102
     * Get IP address as hexadecimal
103
     *
104
     * @return string IP address in hex
105
     */
106
    public function getIPAddressHex(): string
107
    {
108
        return $this->ipAddressCalculation(self::FORMAT_HEX);
12✔
109
    }
110

111
    /**
112
     * Get IP address as binary
113
     *
114
     * @return string IP address in binary
115
     */
116
    public function getIPAddressBinary(): string
117
    {
118
        return $this->ipAddressCalculation(self::FORMAT_BINARY);
12✔
119
    }
120

121
    /**
122
     * Get the IP address as an integer
123
     *
124
     * @return int
125
     */
126
    public function getIPAddressInteger(): int
127
    {
128
        return $this->convertIpToInt($this->ipAddress);
12✔
129
    }
130

131
    /**
132
     * Get network size
133
     *
134
     * @return int network size
135
     */
136
    public function getNetworkSize(): int
137
    {
138
        return $this->networkSize;
58✔
139
    }
140

141
    /**
142
     * Get the CIDR notation of the subnet: IP Address/Network size.
143
     * Example: 192.168.0.15/24
144
     *
145
     * @return string
146
     */
147
    public function getCidrNotation(): string
148
    {
149
        return $this->ipAddress . '/' . $this->networkSize;
57✔
150
    }
151

152
    /**
153
     * Get the number of IP addresses in the network
154
     *
155
     * @return int Number of IP addresses
156
     */
157
    public function getNumberIPAddresses(): int
158
    {
159
        return $this->getNumberIPAddressesOfNetworkSize($this->networkSize);
415✔
160
    }
161

162
    /**
163
     * Get the number of addressable hosts in the network
164
     *
165
     * For most networks, this is the total IP count minus 2 (network and broadcast addresses).
166
     * Special cases per RFC 3021:
167
     *  - /32: Returns 1 (single host network)
168
     *  - /31: Returns 2 (point-to-point link where both addresses are usable)
169
     *
170
     * @return int Number of IP addresses that are addressable
171
     */
172
    public function getNumberAddressableHosts(): int
173
    {
174
        if ($this->networkSize == 32) {
41✔
175
            return 1;
1✔
176
        }
177
        if ($this->networkSize == 31) {
40✔
178
            return 2;
1✔
179
        }
180

181
        return ($this->getNumberIPAddresses() - 2);
39✔
182
    }
183

184
    /**
185
     * Get range of IP addresses in the network
186
     *
187
     * @return string[] containing start and end of IP address range. IP addresses in dotted quad notation.
188
     */
189
    public function getIPAddressRange(): array
190
    {
191
        return [$this->getNetworkPortion(), $this->getBroadcastAddress()];
183✔
192
    }
193

194
    /**
195
     * Get range of IP addresses in the network
196
     *
197
     * @return string[] containing start and end of IP address range. IP addresses in dotted quad notation.
198
     */
199
    public function getAddressableHostRange(): array
200
    {
201
        return [$this->getMinHost(), $this->getMaxHost()];
38✔
202
    }
203

204
    /**
205
     * Get the broadcast IP address
206
     *
207
     * @return string IP address as dotted quads
208
     */
209
    public function getBroadcastAddress(): string
210
    {
211
        $network_quads       = $this->getNetworkPortionQuads();
257✔
212
        $number_ip_addresses = $this->getNumberIPAddresses();
257✔
213

214
        $network_range_quads = [
215
            \sprintf(self::FORMAT_QUADS, ((int) $network_quads[0] & ($this->subnetMask >> 24)) + ((($number_ip_addresses - 1) >> 24) & 0xFF)),
257✔
216
            \sprintf(self::FORMAT_QUADS, ((int) $network_quads[1] & ($this->subnetMask >> 16)) + ((($number_ip_addresses - 1) >> 16) & 0xFF)),
257✔
217
            \sprintf(self::FORMAT_QUADS, ((int) $network_quads[2] & ($this->subnetMask >>  8)) + ((($number_ip_addresses - 1) >>  8) & 0xFF)),
257✔
218
            \sprintf(self::FORMAT_QUADS, ((int) $network_quads[3] & ($this->subnetMask >>  0)) + ((($number_ip_addresses - 1) >>  0) & 0xFF)),
257✔
219
        ];
220

221
        return \implode('.', $network_range_quads);
257✔
222
    }
223

224
    /**
225
     * Get minimum host IP address as dotted quads: xxx.xxx.xxx.xxx
226
     *
227
     * For most networks, this is the network address + 1.
228
     * Special cases:
229
     *  - /32: Returns the IP address itself (single host)
230
     *  - /31: Returns the network portion (lower IP of the point-to-point pair per RFC 3021)
231
     *
232
     * @return string min host as dotted quads
233
     */
234
    public function getMinHost(): string
235
    {
236
        if ($this->networkSize === 32) {
85✔
237
            return $this->ipAddress;
2✔
238
        }
239
        if ($this->networkSize === 31) {
83✔
240
            return $this->getNetworkPortion();
14✔
241
        }
242
        return $this->minHostCalculation(self::FORMAT_QUADS, '.');
69✔
243
    }
244

245
    /**
246
     * Get minimum host IP address as array of quads: [xxx, xxx, xxx, xxx]
247
     *
248
     * @return string[] min host portion as dotted quads.
249
     */
250
    public function getMinHostQuads(): array
251
    {
252
        if ($this->networkSize === 32) {
17✔
253
            return $this->quads;
1✔
254
        }
255
        if ($this->networkSize === 31) {
16✔
256
            return $this->getNetworkPortionQuads();
7✔
257
        }
258
        return \explode('.', $this->minHostCalculation(self::FORMAT_QUADS, '.'));
9✔
259
    }
260

261
    /**
262
     * Get minimum host IP address as hex
263
     *
264
     * @return string min host portion as hex
265
     */
266
    public function getMinHostHex(): string
267
    {
268
        if ($this->networkSize === 32) {
17✔
269
            return \implode('', \array_map(
1✔
270
                function ($quad) {
271
                    return \sprintf(self::FORMAT_HEX, $quad);
1✔
272
                },
1✔
273
                $this->quads
1✔
274
            ));
275
        }
276
        if ($this->networkSize === 31) {
16✔
277
            return $this->getNetworkPortionHex();
7✔
278
        }
279
        return $this->minHostCalculation(self::FORMAT_HEX);
9✔
280
    }
281

282
    /**
283
     * Get minimum host IP address as binary
284
     *
285
     * @return string min host portion as binary
286
     */
287
    public function getMinHostBinary(): string
288
    {
289
        if ($this->networkSize === 32) {
17✔
290
            return \implode('', \array_map(
1✔
291
                function ($quad) {
292
                    return \sprintf(self::FORMAT_BINARY, $quad);
1✔
293
                },
1✔
294
                $this->quads
1✔
295
            ));
296
        }
297
        if ($this->networkSize === 31) {
16✔
298
            return $this->getNetworkPortionBinary();
7✔
299
        }
300
        return $this->minHostCalculation(self::FORMAT_BINARY);
9✔
301
    }
302

303
    /**
304
     * Get minimum host IP address as an Integer
305
     *
306
     * @return int min host portion as integer
307
     */
308
    public function getMinHostInteger(): int
309
    {
310
        if ($this->networkSize === 32) {
17✔
311
            return $this->convertIpToInt(\implode('.', $this->quads));
1✔
312
        }
313
        if ($this->networkSize === 31) {
16✔
314
            return $this->getNetworkPortionInteger();
7✔
315
        }
316
        return $this->convertIpToInt($this->minHostCalculation(self::FORMAT_QUADS, '.'));
9✔
317
    }
318

319
    /**
320
     * Get maximum host IP address as dotted quads: xxx.xxx.xxx.xxx
321
     *
322
     * For most networks, this is the broadcast address - 1.
323
     * Special cases:
324
     *  - /32: Returns the IP address itself (single host)
325
     *  - /31: Returns the broadcast address (higher IP of the point-to-point pair per RFC 3021)
326
     *
327
     * @return string max host as dotted quads.
328
     */
329
    public function getMaxHost(): string
330
    {
331
        if ($this->networkSize === 32) {
85✔
332
            return $this->ipAddress;
2✔
333
        }
334
        if ($this->networkSize === 31) {
83✔
335
            return $this->getBroadcastAddress();
14✔
336
        }
337
        return $this->maxHostCalculation(self::FORMAT_QUADS, '.');
69✔
338
    }
339

340
    /**
341
     * Get maximum host IP address as array of quads: [xxx, xxx, xxx, xxx]
342
     *
343
     * @return string[] max host portion as dotted quads
344
     */
345
    public function getMaxHostQuads(): array
346
    {
347
        if ($this->networkSize === 32) {
17✔
348
            return $this->quads;
1✔
349
        }
350
        if ($this->networkSize === 31) {
16✔
351
            return \explode('.', $this->getBroadcastAddress());
7✔
352
        }
353
        return \explode('.', $this->maxHostCalculation(self::FORMAT_QUADS, '.'));
9✔
354
    }
355

356
    /**
357
     * Get maximum host IP address as hex
358
     *
359
     * @return string max host portion as hex
360
     */
361
    public function getMaxHostHex(): string
362
    {
363
        if ($this->networkSize === 32) {
17✔
364
            return \implode('', \array_map(
1✔
365
                function ($quad) {
366
                    return \sprintf(self::FORMAT_HEX, $quad);
1✔
367
                },
1✔
368
                $this->quads
1✔
369
            ));
370
        }
371
        if ($this->networkSize === 31) {
16✔
372
            return \implode('', \array_map(
7✔
373
                function ($quad) {
374
                    return \sprintf(self::FORMAT_HEX, $quad);
7✔
375
                },
7✔
376
                \explode('.', $this->getBroadcastAddress())
7✔
377
            ));
378
        }
379
        return $this->maxHostCalculation(self::FORMAT_HEX);
9✔
380
    }
381

382
    /**
383
     * Get maximum host IP address as binary
384
     *
385
     * @return string max host portion as binary
386
     */
387
    public function getMaxHostBinary(): string
388
    {
389
        if ($this->networkSize === 32) {
17✔
390
            return \implode('', \array_map(
1✔
391
                function ($quad) {
392
                    return \sprintf(self::FORMAT_BINARY, $quad);
1✔
393
                },
1✔
394
                $this->quads
1✔
395
            ));
396
        }
397
        if ($this->networkSize === 31) {
16✔
398
            return \implode('', \array_map(
7✔
399
                function ($quad) {
400
                    return \sprintf(self::FORMAT_BINARY, $quad);
7✔
401
                },
7✔
402
                \explode('.', $this->getBroadcastAddress())
7✔
403
            ));
404
        }
405
        return $this->maxHostCalculation(self::FORMAT_BINARY);
9✔
406
    }
407

408
    /**
409
     * Get maximum host IP address as an Integer
410
     *
411
     * @return int max host portion as integer
412
     */
413
    public function getMaxHostInteger(): int
414
    {
415
        if ($this->networkSize === 32) {
17✔
416
            return $this->convertIpToInt(\implode('.', $this->quads));
1✔
417
        }
418
        if ($this->networkSize === 31) {
16✔
419
            return $this->convertIpToInt($this->getBroadcastAddress());
7✔
420
        }
421
        return $this->convertIpToInt($this->maxHostCalculation(self::FORMAT_QUADS, '.'));
9✔
422
    }
423

424
    /**
425
     * Get subnet mask as dotted quads: xxx.xxx.xxx.xxx
426
     *
427
     * @return string subnet mask as dotted quads
428
     */
429
    public function getSubnetMask(): string
430
    {
431
        return $this->subnetCalculation(self::FORMAT_QUADS, '.');
41✔
432
    }
433

434
    /**
435
     * Get subnet mask as array of quads: [xxx, xxx, xxx, xxx]
436
     *
437
     * @return string[] of four elements containing the four quads of the subnet mask.
438
     */
439
    public function getSubnetMaskQuads(): array
440
    {
441
        return \explode('.', $this->subnetCalculation(self::FORMAT_QUADS, '.'));
32✔
442
    }
443

444
    /**
445
     * Get subnet mask as hexadecimal
446
     *
447
     * @return string subnet mask in hex
448
     */
449
    public function getSubnetMaskHex(): string
450
    {
451
        return $this->subnetCalculation(self::FORMAT_HEX);
41✔
452
    }
453

454
    /**
455
     * Get subnet mask as binary
456
     *
457
     * @return string subnet mask in binary
458
     */
459
    public function getSubnetMaskBinary(): string
460
    {
461
        return $this->subnetCalculation(self::FORMAT_BINARY);
41✔
462
    }
463

464
    /**
465
     * Get subnet mask as an integer
466
     *
467
     * @return int
468
     */
469
    public function getSubnetMaskInteger(): int
470
    {
471
        return $this->convertIpToInt($this->subnetCalculation(self::FORMAT_QUADS, '.'));
41✔
472
    }
473

474

475
    /**
476
     * Split the network into smaller networks
477
     *
478
     * @param int $networkSize
479
     * @return SubnetCalculator[]
480
     */
481
    public function split(int $networkSize): array
482
    {
483
        if ($networkSize <= $this->networkSize) {
30✔
484
            throw new \RuntimeException('New networkSize must be larger than the base networkSize.');
6✔
485
        }
486

487
        if ($networkSize > 32) {
24✔
488
            throw new \RuntimeException('New networkSize must be smaller than the maximum networkSize.');
8✔
489
        }
490

491
        [$startIp, $endIp] = $this->getIPAddressRangeAsInts();
16✔
492

493
        $addressCount = $this->getNumberIPAddressesOfNetworkSize($networkSize);
16✔
494

495
        $ranges = [];
16✔
496
        for ($ip = $startIp; $ip <= $endIp; $ip += $addressCount) {
16✔
497
            $ranges[] = new SubnetCalculator($this->convertIpToDottedQuad($ip), $networkSize);
16✔
498
        }
499

500
        return $ranges;
16✔
501
    }
502

503
    /**
504
     * Get network portion of IP address as dotted quads: xxx.xxx.xxx.xxx
505
     *
506
     * @return string network portion as dotted quads
507
     */
508
    public function getNetworkPortion(): string
509
    {
510
        return $this->networkCalculation(self::FORMAT_QUADS, '.');
261✔
511
    }
512

513
    /**
514
     * Get network portion as array of quads: [xxx, xxx, xxx, xxx]
515
     *
516
     * @return string[] of four elements containing the four quads of the network portion
517
     */
518
    public function getNetworkPortionQuads(): array
519
    {
520
        return \explode('.', $this->networkCalculation(self::FORMAT_QUADS, '.'));
392✔
521
    }
522

523
    /**
524
     * Get network portion of IP address as hexadecimal
525
     *
526
     * @return string network portion in hex
527
     */
528
    public function getNetworkPortionHex(): string
529
    {
530
        return $this->networkCalculation(self::FORMAT_HEX);
48✔
531
    }
532

533
    /**
534
     * Get network portion of IP address as binary
535
     *
536
     * @return string network portion in binary
537
     */
538
    public function getNetworkPortionBinary(): string
539
    {
540
        return $this->networkCalculation(self::FORMAT_BINARY);
48✔
541
    }
542

543
    /**
544
     * Get network portion of IP address as an integer
545
     *
546
     * @return int
547
     */
548
    public function getNetworkPortionInteger(): int
549
    {
550
        return $this->convertIpToInt($this->networkCalculation(self::FORMAT_QUADS, '.'));
48✔
551
    }
552

553
    /**
554
     * Get host portion of IP address as dotted quads: xxx.xxx.xxx.xxx
555
     *
556
     * @return string host portion as dotted quads
557
     */
558
    public function getHostPortion(): string
559
    {
560
        return $this->hostCalculation(self::FORMAT_QUADS, '.');
41✔
561
    }
562

563
    /**
564
     * Get host portion as array of quads: [xxx, xxx, xxx, xxx]
565
     *
566
     * @return string[] of four elements containing the four quads of the host portion
567
     */
568
    public function getHostPortionQuads(): array
569
    {
570
        return \explode('.', $this->hostCalculation(self::FORMAT_QUADS, '.'));
32✔
571
    }
572

573
    /**
574
     * Get host portion of IP address as hexadecimal
575
     *
576
     * @return string host portion in hex
577
     */
578
    public function getHostPortionHex(): string
579
    {
580
        return $this->hostCalculation(self::FORMAT_HEX);
41✔
581
    }
582

583
    /**
584
     * Get host portion of IP address as binary
585
     *
586
     * @return string host portion in binary
587
     */
588
    public function getHostPortionBinary(): string
589
    {
590
        return $this->hostCalculation(self::FORMAT_BINARY);
41✔
591
    }
592

593
    /**
594
     * Get host portion of IP address as an integer
595
     *
596
     * @return int
597
     */
598
    public function getHostPortionInteger(): int
599
    {
600
        return $this->convertIpToInt($this->hostCalculation(self::FORMAT_QUADS, '.'));
41✔
601
    }
602

603
    /**
604
     * Get all IP addresses
605
     *
606
     * @return \Generator|string[]|false[]
607
     */
608
    public function getAllIPAddresses(): \Generator
609
    {
610
        [$startIp, $endIp] = $this->getIPAddressRangeAsInts();
25✔
611

612
        for ($ip = $startIp; $ip <= $endIp; $ip++) {
24✔
613
            yield $this->convertIpToDottedQuad($ip);
24✔
614
        }
615
    }
24✔
616

617
    /**
618
     * Get all host IP addresses
619
     * Removes broadcast and network address if they exist.
620
     *
621
     * @return \Generator|string[]|false[]
622
     *
623
     * @throws \RuntimeException if there is an error in the IP address range calculation
624
     */
625
    public function getAllHostIPAddresses(): \Generator
626
    {
627
        [$startIp, $endIp] = $this->getIPAddressRangeAsInts();
22✔
628

629
        if ($this->getNetworkSize() < 31) {
21✔
630
            $startIp += 1;
18✔
631
            $endIp   -= 1;
18✔
632
        }
633

634
        for ($ip = $startIp; $ip <= $endIp; $ip++) {
21✔
635
            yield $this->convertIpToDottedQuad($ip);
21✔
636
        }
637
    }
21✔
638

639
    /**
640
     * Is the IP address in the subnet?
641
     *
642
     * @param string $ipAddressString
643
     *
644
     * @return bool
645
     */
646
    public function isIPAddressInSubnet($ipAddressString): bool
647
    {
648
        $ipAddress = \ip2long($ipAddressString);
82✔
649
        [$startIp, $endIp] = $this->getIPAddressRangeAsInts();
82✔
650

651
        return $ipAddress >= $startIp && $ipAddress <= $endIp;
82✔
652
    }
653

654
    /**
655
     * Get the IPv4 Arpa Domain
656
     *
657
     * Reverse DNS lookups for IPv4 addresses use the special domain in-addr.arpa.
658
     * In this domain, an IPv4 address is represented as a concatenated sequence of four decimal numbers,
659
     * separated by dots, to which is appended the second level domain suffix .in-addr.arpa.
660
     *
661
     * The four decimal numbers are obtained by splitting the 32-bit IPv4 address into four octets and converting
662
     * each octet into a decimal number. These decimal numbers are then concatenated in the order:
663
     * least significant octet first (leftmost), to most significant octet last (rightmost).
664
     * It is important to note that this is the reverse order to the usual dotted-decimal convention for writing
665
     * IPv4 addresses in textual form.
666
     *
667
     * Ex: to do a reverse lookup of the IP address 8.8.4.4 the PTR record for the domain name 4.4.8.8.in-addr.arpa would be looked up.
668
     *
669
     * @link https://en.wikipedia.org/wiki/Reverse_DNS_lookup
670
     *
671
     * @return string
672
     */
673
    public function getIPv4ArpaDomain(): string
674
    {
675
        $reverseQuads = \implode('.', \array_reverse($this->quads));
17✔
676
        return $reverseQuads . '.in-addr.arpa';
17✔
677
    }
678

679
    /**
680
     * Get subnet calculations as an associated array
681
     *
682
     * @return mixed[] of subnet calculations
683
     */
684
    public function getSubnetArrayReport(): array
685
    {
686
        return $this->report->createArrayReport($this);
1✔
687
    }
688

689
    /**
690
     * Get subnet calculations as a JSON string
691
     *
692
     * @return string JSON string of subnet calculations
693
     *
694
     * @throws \RuntimeException if there is a JSON encode error
695
     */
696
    public function getSubnetJsonReport(): string
697
    {
698
        $json = $this->report->createJsonReport($this);
2✔
699

700
        if ($json === false) {
2✔
701
            throw new \RuntimeException('JSON report failure: ' . json_last_error_msg());
1✔
702
        }
703

704
        return $json;
1✔
705
    }
706

707
    /**
708
     * Print a report of subnet calculations
709
     */
710
    public function printSubnetReport(): void
711
    {
712
        $this->report->printReport($this);
1✔
713
    }
1✔
714

715
    /**
716
     * Print a report of subnet calculations
717
     *
718
     * @return string Subnet Calculator report
719
     */
720
    public function getPrintableReport(): string
721
    {
722
        return $this->report->createPrintableReport($this);
1✔
723
    }
724

725
    /**
726
     * String representation of a report of subnet calculations
727
     *
728
     * @return string
729
     */
730
    public function __toString(): string
731
    {
732
        return $this->report->createPrintableReport($this);
2✔
733
    }
734

735
    /* ************** *
736
     * PHP INTERFACES
737
     * ************** */
738

739
    /**
740
     * \JsonSerializable interface
741
     *
742
     * @return mixed[]
743
     */
744
    public function jsonSerialize(): array
745
    {
746
        return $this->report->createArrayReport($this);
1✔
747
    }
748

749
    /* ********************** *
750
     * PRIVATE IMPLEMENTATION
751
     * ********************** */
752

753
    /**
754
     * Calculate subnet mask
755
     *
756
     * @param  int $networkSize
757
     *
758
     * @return int
759
     */
760
    private function calculateSubnetMask(int $networkSize): int
761
    {
762
        return 0xFFFFFFFF << (32 - $networkSize);
809✔
763
    }
764

765
    /**
766
     * Calculate IP address for formatting
767
     *
768
     * @param string $format    sprintf format to determine if decimal, hex or binary
769
     * @param string $separator implode separator for formatting quads vs hex and binary
770
     *
771
     * @return string formatted IP address
772
     */
773
    private function ipAddressCalculation(string $format, string $separator = ''): string
774
    {
775
        return \implode($separator, array_map(
15✔
776
            function ($quad) use ($format) {
777
                return \sprintf($format, $quad);
15✔
778
            },
15✔
779
            $this->quads
15✔
780
        ));
781
    }
782

783
    /**
784
     * Subnet calculation
785
     *
786
     * @param string $format    sprintf format to determine if decimal, hex or binary
787
     * @param string $separator implode separator for formatting quads vs hex and binary
788
     *
789
     * @return string subnet
790
     */
791
    private function subnetCalculation(string $format, string $separator = ''): string
792
    {
793
        $maskQuads = [
794
            \sprintf($format, ($this->subnetMask >> 24) & 0xFF),
41✔
795
            \sprintf($format, ($this->subnetMask >> 16) & 0xFF),
41✔
796
            \sprintf($format, ($this->subnetMask >>  8) & 0xFF),
41✔
797
            \sprintf($format, ($this->subnetMask >>  0) & 0xFF),
41✔
798
        ];
799

800
        return implode($separator, $maskQuads);
41✔
801
    }
802

803
    /**
804
     * Calculate network portion for formatting
805
     *
806
     * @param string $format    sprintf format to determine if decimal, hex or binary
807
     * @param string $separator implode separator for formatting quads vs hex and binary
808
     *
809
     * @return string formatted subnet mask
810
     */
811
    private function networkCalculation(string $format, string $separator = ''): string
812
    {
813
        $networkQuads = [
814
            \sprintf($format, (int) $this->quads[0] & ($this->subnetMask >> 24)),
452✔
815
            \sprintf($format, (int) $this->quads[1] & ($this->subnetMask >> 16)),
452✔
816
            \sprintf($format, (int) $this->quads[2] & ($this->subnetMask >>  8)),
452✔
817
            \sprintf($format, (int) $this->quads[3] & ($this->subnetMask >>  0)),
452✔
818
        ];
819

820
        return implode($separator, $networkQuads);
452✔
821
    }
822

823
    /**
824
     * Calculate host portion for formatting
825
     *
826
     * @param string $format    sprintf format to determine if decimal, hex or binary
827
     * @param string $separator implode separator for formatting quads vs hex and binary
828
     *
829
     * @return string formatted subnet mask
830
     */
831
    private function hostCalculation(string $format, string $separator = ''): string
832
    {
833
        $networkQuads = [
834
            \sprintf($format, (int) $this->quads[0] & ~($this->subnetMask >> 24)),
41✔
835
            \sprintf($format, (int) $this->quads[1] & ~($this->subnetMask >> 16)),
41✔
836
            \sprintf($format, (int) $this->quads[2] & ~($this->subnetMask >>  8)),
41✔
837
            \sprintf($format, (int) $this->quads[3] & ~($this->subnetMask >>  0)),
41✔
838
        ];
839

840
        return implode($separator, $networkQuads);
41✔
841
    }
842

843
    /**
844
     * Calculate min host for formatting
845
     *
846
     * @param string $format    sprintf format to determine if decimal, hex or binary
847
     * @param string $separator implode separator for formatting quads vs hex and binary
848
     *
849
     * @return string formatted min host
850
     */
851
    private function minHostCalculation(string $format, string $separator = ''): string
852
    {
853
        $networkQuads = [
854
            \sprintf($format, (int) $this->quads[0] & ($this->subnetMask >> 24)),
105✔
855
            \sprintf($format, (int) $this->quads[1] & ($this->subnetMask >> 16)),
105✔
856
            \sprintf($format, (int) $this->quads[2] & ($this->subnetMask >>  8)),
105✔
857
            \sprintf($format, ((int) $this->quads[3] & ($this->subnetMask >> 0)) + 1),
105✔
858
        ];
859

860
        return implode($separator, $networkQuads);
105✔
861
    }
862

863
    /**
864
     * Calculate max host for formatting
865
     *
866
     * @param string $format    sprintf format to determine if decimal, hex or binary
867
     * @param string $separator implode separator for formatting quads vs hex and binary
868
     *
869
     * @return string formatted max host
870
     */
871
    private function maxHostCalculation(string $format, string $separator = ''): string
872
    {
873
        $networkQuads      = $this->getNetworkPortionQuads();
105✔
874
        $numberIpAddresses = $this->getNumberIPAddresses();
105✔
875

876
        $network_range_quads = [
877
            \sprintf($format, ((int) $networkQuads[0] & ($this->subnetMask >> 24)) + ((($numberIpAddresses - 1) >> 24) & 0xFF)),
105✔
878
            \sprintf($format, ((int) $networkQuads[1] & ($this->subnetMask >> 16)) + ((($numberIpAddresses - 1) >> 16) & 0xFF)),
105✔
879
            \sprintf($format, ((int) $networkQuads[2] & ($this->subnetMask >>  8)) + ((($numberIpAddresses - 1) >>  8) & 0xFF)),
105✔
880
            \sprintf($format, ((int) $networkQuads[3] & ($this->subnetMask >>  0)) + ((($numberIpAddresses - 1) >>  0) & 0xFE)),
105✔
881
        ];
882

883
        return implode($separator, $network_range_quads);
105✔
884
    }
885

886
    /**
887
     * Validate IP address and network
888
     *
889
     * @param string $ipAddress   IP address in dotted quads format
890
     * @param int    $networkSize Network size
891
     *
892
     * @throws \UnexpectedValueException IP or network size not valid
893
     */
894
    private function validateInputs(string $ipAddress, int $networkSize): void
895
    {
896
        if (!\filter_var($ipAddress, FILTER_VALIDATE_IP)) {
809✔
897
            throw new \UnexpectedValueException("IP address $ipAddress not valid.");
6✔
898
        }
899
        if (($networkSize < 1) || ($networkSize > 32)) {
809✔
900
            throw new \UnexpectedValueException("Network size $networkSize not valid.");
12✔
901
        }
902
    }
809✔
903

904
    /**
905
     * Get the start and end of the IP address range as ints
906
     *
907
     * @return int[] [start IP, end IP]
908
     */
909
    private function getIPAddressRangeAsInts(): array
910
    {
911
        [$startIp, $endIp] = $this->getIPAddressRange();
144✔
912
        $startIp = \ip2long($startIp);
144✔
913
        $endIp   = \ip2long($endIp);
144✔
914

915
        if ($startIp === false || $endIp === false) {
144✔
916
            throw new \RuntimeException('IP address range calculation failed: ' . \print_r($this->getIPAddressRange(), true));
2✔
917
        }
918

919
        return [$startIp, $endIp];
142✔
920
    }
921

922
    /**
923
     * Get the number of IP addresses in the given network size
924
     *
925
     * @param int $networkSize
926
     *
927
     * @return int Number of IP addresses
928
     */
929
    private function getNumberIPAddressesOfNetworkSize($networkSize): int
930
    {
931
        return \pow(2, (32 - $networkSize));
415✔
932
    }
933

934

935
    /**
936
     * Convert a dotted-quad IP address to an integer
937
     *
938
     * @param string $ipAddress Dotted-quad IP address
939
     *
940
     * @return int Integer representation of an IP address
941
     */
942
    private function convertIpToInt(string $ipAddress): int
943
    {
944
        $ipAsInt = \ip2long($ipAddress);
143✔
945
        if ($ipAsInt === false) {
143✔
946
            throw new \RuntimeException('Invalid IP address string. Could not convert dotted-quad string address to an integer: ' . $ipAddress);
1✔
947
        }
948
        return $ipAsInt;
142✔
949
    }
950

951
    /**
952
     * Convert an integer IP address to a dotted-quad IP string
953
     *
954
     * @param int $ipAsInt Integer representation of an IP address
955
     *
956
     * @return string Dotted-quad IP address
957
     */
958
    private function convertIpToDottedQuad(int $ipAsInt): string
959
    {
960
        $ipDottedQuad = \long2ip($ipAsInt);
61✔
961
        if ($ipDottedQuad == false) {
61✔
962
            throw new \RuntimeException('Invalid IP address integer. Could not convert integer address to dotted-quad string: ' . $ipAsInt);
×
963
        }
964
        return $ipDottedQuad;
61✔
965
    }
966
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc