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

mlocati / ip-lib / 13077233848

31 Jan 2025 04:58PM UTC coverage: 98.543% (-0.08%) from 98.622%
13077233848

Pull #93

github

web-flow
Merge e087e6636 into 67ce14c6f
Pull Request #93: Add `split` Method to Subnet for Dividing Network Ranges into Smaller Subnets

20 of 21 new or added lines in 1 file covered. (95.24%)

1 existing line in 1 file now uncovered.

879 of 892 relevant lines covered (98.54%)

220.96 hits per line

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

95.51
/src/Range/Pattern.php
1
<?php
2

3
namespace IPLib\Range;
4

5
use IPLib\Address\AddressInterface;
6
use IPLib\Address\IPv4;
7
use IPLib\Address\IPv6;
8
use IPLib\Address\Type as AddressType;
9
use IPLib\ParseStringFlag;
10

11
/**
12
 * Represents an address range in pattern format (only ending asterisks are supported).
13
 *
14
 * @example 127.0.*.*
15
 * @example ::/8
16
 */
17
class Pattern extends AbstractRange
18
{
19
    /**
20
     * Starting address of the range.
21
     *
22
     * @var \IPLib\Address\AddressInterface
23
     */
24
    protected $fromAddress;
25

26
    /**
27
     * Final address of the range.
28
     *
29
     * @var \IPLib\Address\AddressInterface
30
     */
31
    protected $toAddress;
32

33
    /**
34
     * Number of ending asterisks.
35
     *
36
     * @var int
37
     */
38
    protected $asterisksCount;
39

40
    /**
41
     * The type of the range of this IP range.
42
     *
43
     * @var int|false|null false if this range crosses multiple range types, null if yet to be determined
44
     *
45
     * @since 1.5.0
46
     */
47
    protected $rangeType;
48

49
    /**
50
     * Initializes the instance.
51
     *
52
     * @param \IPLib\Address\AddressInterface $fromAddress
53
     * @param \IPLib\Address\AddressInterface $toAddress
54
     * @param int $asterisksCount
55
     */
56
    public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $asterisksCount)
57
    {
58
        $this->fromAddress = $fromAddress;
143✔
59
        $this->toAddress = $toAddress;
143✔
60
        $this->asterisksCount = $asterisksCount;
143✔
61
    }
143✔
62

63
    /**
64
     * {@inheritdoc}
65
     *
66
     * @see \IPLib\Range\RangeInterface::__toString()
67
     */
68
    public function __toString()
69
    {
70
        return $this->toString();
66✔
71
    }
72

73
    /**
74
     * @deprecated since 1.17.0: use the parseString() method instead.
75
     * For upgrading:
76
     * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
77
     *
78
     * @param string|mixed $range
79
     * @param bool $supportNonDecimalIPv4
80
     *
81
     * @return static|null
82
     *
83
     * @see \IPLib\Range\Pattern::parseString()
84
     * @since 1.10.0 added the $supportNonDecimalIPv4 argument
85
     */
86
    public static function fromString($range, $supportNonDecimalIPv4 = false)
87
    {
88
        return static::parseString($range, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
12✔
89
    }
90

91
    /**
92
     * Try get the range instance starting from its string representation.
93
     *
94
     * @param string|mixed $range
95
     * @param int $flags A combination or zero or more flags
96
     *
97
     * @return static|null
98
     *
99
     * @since 1.17.0
100
     * @see \IPLib\ParseStringFlag
101
     */
102
    public static function parseString($range, $flags = 0)
103
    {
104
        if (!is_string($range) || strpos($range, '*') === false) {
145✔
105
            return null;
55✔
106
        }
107
        if ($range === '*.*.*.*') {
92✔
108
            return new static(IPv4::parseString('0.0.0.0'), IPv4::parseString('255.255.255.255'), 4);
5✔
109
        }
110
        if ($range === '*:*:*:*:*:*:*:*') {
87✔
111
            return new static(IPv6::parseString('::'), IPv6::parseString('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'), 8);
3✔
112
        }
113
        $matches = null;
84✔
114
        if (strpos($range, '.') !== false && preg_match('/^[^*]+((?:\.\*)+)$/', $range, $matches)) {
84✔
115
            $asterisksCount = strlen($matches[1]) >> 1;
50✔
116
            if ($asterisksCount > 0) {
50✔
117
                $missingDots = 3 - substr_count($range, '.');
50✔
118
                if ($missingDots > 0) {
50✔
119
                    $range .= str_repeat('.*', $missingDots);
2✔
120
                    $asterisksCount += $missingDots;
2✔
121
                }
122
            }
123
            $fromAddress = IPv4::parseString(str_replace('*', '0', $range), $flags);
50✔
124
            if ($fromAddress === null) {
50✔
125
                return null;
1✔
126
            }
127
            $fixedBytes = array_slice($fromAddress->getBytes(), 0, -$asterisksCount);
49✔
128
            $otherBytes = array_fill(0, $asterisksCount, 255);
49✔
129
            $toAddress = IPv4::fromBytes(array_merge($fixedBytes, $otherBytes));
49✔
130

131
            return new static($fromAddress, $toAddress, $asterisksCount);
49✔
132
        }
133
        if (strpos($range, ':') !== false && preg_match('/^[^*]+((?::\*)+)$/', $range, $matches)) {
34✔
134
            $asterisksCount = strlen($matches[1]) >> 1;
30✔
135
            $fromAddress = IPv6::parseString(str_replace('*', '0', $range));
30✔
136
            if ($fromAddress === null) {
30✔
137
                return null;
×
138
            }
139
            $fixedWords = array_slice($fromAddress->getWords(), 0, -$asterisksCount);
30✔
140
            $otherWords = array_fill(0, $asterisksCount, 0xffff);
30✔
141
            $toAddress = IPv6::fromWords(array_merge($fixedWords, $otherWords));
30✔
142

143
            return new static($fromAddress, $toAddress, $asterisksCount);
30✔
144
        }
145

146
        return null;
4✔
147
    }
148

149
    /**
150
     * {@inheritdoc}
151
     *
152
     * @see \IPLib\Range\RangeInterface::toString()
153
     */
154
    public function toString($long = false)
155
    {
156
        if ($this->asterisksCount === 0) {
111✔
157
            return $this->fromAddress->toString($long);
56✔
158
        }
159
        switch (true) {
160
            case $this->fromAddress instanceof \IPLib\Address\IPv4:
67✔
161
                $chunks = explode('.', $this->fromAddress->toString());
41✔
162
                $chunks = array_slice($chunks, 0, -$this->asterisksCount);
41✔
163
                $chunks = array_pad($chunks, 4, '*');
41✔
164
                $result = implode('.', $chunks);
41✔
165
                break;
41✔
166
            case $this->fromAddress instanceof \IPLib\Address\IPv6:
26✔
167
                if ($long) {
26✔
168
                    $chunks = explode(':', $this->fromAddress->toString(true));
6✔
169
                    $chunks = array_slice($chunks, 0, -$this->asterisksCount);
6✔
170
                    $chunks = array_pad($chunks, 8, '*');
6✔
171
                    $result = implode(':', $chunks);
6✔
172
                } elseif ($this->asterisksCount === 8) {
26✔
173
                    $result = '*:*:*:*:*:*:*:*';
2✔
174
                } else {
175
                    $bytes = $this->toAddress->getBytes();
24✔
176
                    $bytes = array_slice($bytes, 0, -$this->asterisksCount * 2);
24✔
177
                    $bytes = array_pad($bytes, 16, 1);
24✔
178
                    $address = IPv6::fromBytes($bytes);
24✔
179
                    $before = substr($address->toString(false), 0, -strlen(':101') * $this->asterisksCount);
24✔
180
                    $result = $before . str_repeat(':*', $this->asterisksCount);
24✔
181
                }
182
                break;
26✔
183
            default:
184
                throw new \Exception('@todo'); // @codeCoverageIgnore
185
        }
186

187
        return $result;
67✔
188
    }
189

190
    /**
191
     * {@inheritdoc}
192
     *
193
     * @see \IPLib\Range\RangeInterface::getAddressType()
194
     */
195
    public function getAddressType()
196
    {
197
        return $this->fromAddress->getAddressType();
72✔
198
    }
199

200
    /**
201
     * {@inheritdoc}
202
     *
203
     * @see \IPLib\Range\RangeInterface::getStartAddress()
204
     */
205
    public function getStartAddress()
206
    {
207
        return $this->fromAddress;
30✔
208
    }
209

210
    /**
211
     * {@inheritdoc}
212
     *
213
     * @see \IPLib\Range\RangeInterface::getEndAddress()
214
     */
215
    public function getEndAddress()
216
    {
217
        return $this->toAddress;
30✔
218
    }
219

220
    /**
221
     * {@inheritdoc}
222
     *
223
     * @see \IPLib\Range\RangeInterface::getComparableStartString()
224
     */
225
    public function getComparableStartString()
226
    {
227
        return $this->fromAddress->getComparableString();
35✔
228
    }
229

230
    /**
231
     * {@inheritdoc}
232
     *
233
     * @see \IPLib\Range\RangeInterface::getComparableEndString()
234
     */
235
    public function getComparableEndString()
236
    {
237
        return $this->toAddress->getComparableString();
35✔
238
    }
239

240
    /**
241
     * {@inheritdoc}
242
     *
243
     * @see \IPLib\Range\RangeInterface::asSubnet()
244
     * @since 1.8.0
245
     */
246
    public function asSubnet()
247
    {
248
        return new Subnet($this->getStartAddress(), $this->getEndAddress(), $this->getNetworkPrefix());
26✔
249
    }
250

251
    /**
252
     * {@inheritdoc}
253
     *
254
     * @see \IPLib\Range\RangeInterface::asPattern()
255
     */
256
    public function asPattern()
257
    {
258
        return $this;
14✔
259
    }
260

261
    /**
262
     * {@inheritdoc}
263
     *
264
     * @see \IPLib\Range\RangeInterface::getSubnetMask()
265
     */
266
    public function getSubnetMask()
267
    {
268
        if ($this->getAddressType() !== AddressType::T_IPv4) {
5✔
269
            return null;
1✔
270
        }
271
        switch ($this->asterisksCount) {
4✔
272
            case 0:
4✔
273
                $bytes = array(255, 255, 255, 255);
×
274
                break;
×
275
            case 4:
4✔
276
                $bytes = array(0, 0, 0, 0);
1✔
277
                break;
1✔
278
            default:
279
                $bytes = array_pad(array_fill(0, 4 - $this->asterisksCount, 255), 4, 0);
3✔
280
                break;
3✔
281
        }
282

283
        return IPv4::fromBytes($bytes);
4✔
284
    }
285

286
    /**
287
     * {@inheritdoc}
288
     *
289
     * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
290
     */
291
    public function getReverseDNSLookupName()
292
    {
293
        return $this->asterisksCount === 0 ? array($this->getStartAddress()->getReverseDNSLookupName()) : $this->asSubnet()->getReverseDNSLookupName();
12✔
294
    }
295

296
    /**
297
     * {@inheritdoc}
298
     *
299
     * @see \IPLib\Range\RangeInterface::getSize()
300
     */
301
    public function getSize()
302
    {
303
        $fromAddress = $this->fromAddress;
6✔
304
        $maxPrefix = $fromAddress::getNumberOfBits();
6✔
305
        $prefix = $this->getNetworkPrefix();
6✔
306

307
        return pow(2, ($maxPrefix - $prefix));
6✔
308
    }
309

310
    /**
311
     * @return float|int
312
     */
313
    private function getNetworkPrefix()
314
    {
315
        switch ($this->getAddressType()) {
32✔
316
            case AddressType::T_IPv4:
317
                return 8 * (4 - $this->asterisksCount);
12✔
318
            case AddressType::T_IPv6:
319
                return 16 * (8 - $this->asterisksCount);
20✔
320
        }
UNCOV
321
    }
×
322
}
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