diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 8b0a577..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: CI -on: - - pull_request - - push -jobs: - psalm: - name: Psalm - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 8.0 - coverage: none - - name: Composer install - run: composer install --no-interaction --no-ansi --no-progress - - name: Run Psalm - run: vendor/bin/psalm --no-progress --shepherd --show-info=false --stats - phpunit: - name: "PHPUnit (PHP: ${{ matrix.php-versions }})" - runs-on: ubuntu-latest - strategy: - matrix: - php-versions: - - 8.0 - - 8.1 - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - coverage: none - - name: Composer install - run: composer install --no-interaction --no-ansi --no-progress - - name: Run PHPUnit - run: vendor/bin/phpunit diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..9600ee2 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,9 @@ +name: PHPStan +on: + pull_request: + push: + branches: + - 1.x +jobs: + phpstan: + uses: artemeon/.shared/.github/workflows/phpstan-php84-upwards.yml@main diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml new file mode 100644 index 0000000..39244fd --- /dev/null +++ b/.github/workflows/pint.yml @@ -0,0 +1,27 @@ +name: Pint + +on: + - pull_request + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + pint: + name: Pint (PHP-CS-Fixer) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.4" + coverage: none + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Composer install + run: composer install --no-interaction --no-ansi --no-progress + - name: Run Pint + run: composer pint diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..55536ea --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,22 @@ +name: UNIT-TESTS +on: + pull_request: + push: + branches: + - 1.x +jobs: + phpunit: + name: PHPUnit + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + coverage: none + - name: Composer install + run: composer install --no-interaction --no-ansi --no-progress + - name: Run PHPUnit + run: vendor/phpunit/phpunit/phpunit -c ./phpunit.xml.dist diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..98c72c1 --- /dev/null +++ b/changelog.md @@ -0,0 +1,6 @@ +## 3.0.0 - 2025-01-31 +- Drop support for PHP 8.0, PHP 8.1, PHP 8.2 and PHP 8.3. +- Add Support for PHP 8.4. + +## 0.1.0 - Some time in the past +* Initial release \ No newline at end of file diff --git a/composer.json b/composer.json index 69ec8a0..98ff653 100644 --- a/composer.json +++ b/composer.json @@ -4,18 +4,35 @@ "description": "PHP Library for manipulating network addresses (IPv4 and IPv6)", "keywords": ["IP-Tools", "network", "subnet", "cidr", "IP", "IPv4", "IPv6"], "license": "MIT", + "scripts": { + "phpstan": "php ./vendor/bin/phpstan analyse --memory-limit=4G", + "pint": "./vendor/bin/pint --test -v", + "pint:fix": "./vendor/bin/pint", + "test": "./vendor/bin/pest" + }, "authors": [{ "name": "Safarov Alisher", "email": "alisher.safarov@outlook.com", "homepage": "https://github.com/S1lentium" }], "require": { - "php": ">=8.0", + "php": ">=8.4", "ext-bcmath": "*" }, "require-dev": { - "phpunit/phpunit": "~9.0", - "vimeo/psalm": "^4.0" + "laravel/pint": "^1.20.0", + "phpstan/phpstan": "^2.1.2", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan-phpunit": "^2.0.4", + "rector/rector": "^2.0.7", + "pestphp/pest": "^v3.7", + "pestphp/pest-plugin-type-coverage": "^3.2" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true, + "pestphp/pest-plugin": true + } }, "autoload": { "psr-4": { diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..cd4c7d3 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,7 @@ +includes: + - phar://phpstan.phar/conf/bleedingEdge.neon + +parameters: + level: 3 + paths: + - src diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..052aaec --- /dev/null +++ b/pint.json @@ -0,0 +1,129 @@ +{ + "preset": "psr12", + "rules": { + "align_multiline_comment": true, + "array_indentation": true, + "array_push": true, + "array_syntax": { + "syntax": "short" + }, + "assign_null_coalescing_to_coalesce_equal": true, + "binary_operator_spaces": true, + "blank_line_before_statement": true, + "cast_spaces": true, + "clean_namespace": true, + "combine_consecutive_issets": true, + "combine_consecutive_unsets": true, + "compact_nullable_typehint": true, + "concat_space": { + "spacing": "one" + }, + "fully_qualified_strict_types": true, + "function_to_constant": true, + "get_class_to_class_keyword": true, + "is_null": true, + "lambda_not_used_import": true, + "logical_operators": true, + "method_chaining_indentation": true, + "modernize_types_casting": true, + "multiline_whitespace_before_semicolons": true, + "no_empty_comment": true, + "no_empty_phpdoc": true, + "no_empty_statement": true, + "no_extra_blank_lines": { + "tokens": [ + "attribute", + "break", + "case", + "continue", + "curly_brace_block", + "default", + "extra", + "parenthesis_brace_block", + "return", + "square_brace_block", + "switch", + "throw", + "use", + "use_trait" + ] + }, + "no_multiline_whitespace_around_double_arrow": true, + "no_short_bool_cast": true, + "no_singleline_whitespace_before_semicolons": true, + "no_superfluous_elseif": false, + "no_superfluous_phpdoc_tags": true, + "no_trailing_comma_in_singleline": true, + "no_unneeded_control_parentheses": true, + "no_useless_concat_operator": true, + "no_useless_else": true, + "no_useless_nullsafe_operator": true, + "no_useless_return": true, + "no_whitespace_before_comma_in_array": true, + "not_operator_with_successor_space": false, + "nullable_type_declaration": true, + "object_operator_without_whitespace": true, + "ordered_imports": { + "imports_order": [ + "class", + "function", + "const" + ], + "sort_algorithm": "alpha" + }, + "ordered_interfaces": true, + "ordered_types": { + "null_adjustment": "always_last" + }, + "phpdoc_align": { + "align": "left" + }, + "phpdoc_indent": true, + "phpdoc_no_useless_inheritdoc": true, + "phpdoc_order": true, + "phpdoc_scalar": true, + "phpdoc_single_line_var_spacing": true, + "phpdoc_summary": true, + "phpdoc_tag_casing": true, + "phpdoc_trim": true, + "phpdoc_trim_consecutive_blank_line_separation": true, + "phpdoc_var_without_name": true, + "php_unit_construct": true, + "php_unit_dedicate_assert": true, + "php_unit_dedicate_assert_internal_type": true, + "php_unit_internal_class": true, + "php_unit_method_casing": true, + "return_assignment": true, + "return_type_declaration": true, + "short_scalar_cast": true, + "single_line_comment_spacing": true, + "single_line_comment_style": true, + "single_quote": true, + "single_space_around_construct": true, + "ternary_to_null_coalescing": true, + "trailing_comma_in_multiline": { + "elements": [ + "arguments", + "arrays", + "match", + "parameters" + ] + }, + "trim_array_spaces": true, + "type_declaration_spaces": true, + "types_spaces": { + "space": "single" + }, + "use_arrow_functions": false, + "void_return": true, + "whitespace_after_comma_in_array": { + "ensure_single_space": true + }, + "yoda_style": { + "equal": false, + "identical": false, + "less_and_greater": false, + "always_move_variable": false + } + } +} diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..1b6ab2f --- /dev/null +++ b/rector.php @@ -0,0 +1,62 @@ +withAttributesSets(phpunit: true) + ->withPhpSets(php84: true) + ->withPreparedSets( + deadCode: true, + codeQuality: true, + codingStyle: true, + privatization: true, + naming: true, + rectorPreset: true, + ) + ->withRules([ + Rector\CodeQuality\Rector\Ternary\ArrayKeyExistsTernaryThenValueToCoalescingRector::class, + Rector\CodeQuality\Rector\NullsafeMethodCall\CleanupUnneededNullsafeOperatorRector::class, + Rector\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector::class, + Rector\CodingStyle\Rector\Stmt\NewlineAfterStatementRector::class, + Rector\CodingStyle\Rector\ClassMethod\NewlineBeforeNewAssignSetRector::class, + Rector\Php71\Rector\BooleanOr\IsIterableRector::class, + Rector\Php71\Rector\FuncCall\RemoveExtraParametersRector::class, + Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector::class, + Rector\TypeDeclaration\Rector\FunctionLike\AddReturnTypeDeclarationFromYieldsRector::class, + Rector\TypeDeclaration\Rector\FunctionLike\AddParamTypeForFunctionLikeWithinCallLikeArgDeclarationRector::class, + Rector\TypeDeclaration\Rector\FunctionLike\AddParamTypeSplFixedArrayRector::class, + Rector\Php80\Rector\Catch_\RemoveUnusedVariableInCatchRector::class, + Rector\Php84\Rector\Param\ExplicitNullableParamTypeRector::class, + Rector\DeadCode\Rector\Foreach_\RemoveUnusedForeachKeyRector::class, + Rector\DeadCode\Rector\ClassMethod\RemoveUnusedPromotedPropertyRector::class, + Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictFluentReturnRector::class, + Rector\Php80\Rector\Class_\StringableForToStringRector::class, + Rector\CodeQuality\Rector\Ternary\UnnecessaryTernaryExpressionRector::class, + Rector\CodingStyle\Rector\ArrowFunction\StaticArrowFunctionRector::class, + Rector\CodingStyle\Rector\Closure\StaticClosureRector::class, + Rector\DeadCode\Rector\Node\RemoveNonExistingVarAnnotationRector::class, + Rector\DeadCode\Rector\ClassMethod\RemoveUnusedConstructorParamRector::class, + Rector\DeadCode\Rector\Concat\RemoveConcatAutocastRector::class, + Rector\DeadCode\Rector\ClassMethod\RemoveUnusedPrivateMethodParameterRector::class, + Rector\Php72\Rector\FuncCall\GetClassOnNullRector::class, + Rector\Php73\Rector\FuncCall\ArrayKeyFirstLastRector::class, + Rector\Php80\Rector\ClassMethod\AddParamBasedOnParentClassMethodRector::class, + Rector\Php80\Rector\NotIdentical\StrContainsRector::class, + Rector\Php80\Rector\Identical\StrEndsWithRector::class, + Rector\Php80\Rector\Identical\StrStartsWithRector::class, + Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromReturnNewRector::class, + Rector\TypeDeclaration\Rector\ClassMethod\ParamTypeByMethodCallTypeRector::class, + Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector::class, + Rector\CodeQuality\Rector\If_\ExplicitBoolCompareRector::class, + Rector\CodeQuality\Rector\Foreach_\ForeachItemsAssignToEmptyArrayToAssignRector::class, + Rector\CodeQuality\Rector\Foreach_\ForeachToInArrayRector::class, + Rector\CodeQuality\Rector\BooleanAnd\RemoveUselessIsObjectCheckRector::class, + Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector::class, + ]) + ->withPaths([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->withTypeCoverageLevel(13); diff --git a/src/Exception/IpException.php b/src/Exception/IpException.php index e9c6869..0a41af9 100644 --- a/src/Exception/IpException.php +++ b/src/Exception/IpException.php @@ -1,4 +1,7 @@ * @link https://github.com/S1lentium/IPTools */ -class IP +class IP implements Stringable { - use PropertyTrait; - - const IP_V4 = 'IPv4'; - const IP_V6 = 'IPv6'; - - const IP_V4_MAX_PREFIX_LENGTH = 32; - const IP_V6_MAX_PREFIX_LENGTH = 128; - - const IP_V4_OCTETS = 4; - const IP_V6_OCTETS = 16; - - /** - * @var string - */ - private $in_addr; - - /** - * @param string ip - * @throws IpException - */ - public function __construct($ip) - { - if (!filter_var($ip, FILTER_VALIDATE_IP)) { - throw new IpException("Invalid IP address format"); - } - $this->in_addr = inet_pton($ip); - } - - /** - * @return string - */ - public function __toString() - { - return inet_ntop($this->in_addr); - } - - /** - * @param string ip - * @return IP - */ - public static function parse($ip) - { - if (strpos($ip, '0x') === 0) { - $ip = substr($ip, 2); - return self::parseHex($ip); - } - - if (strpos($ip, '0b') === 0) { - $ip = substr($ip, 2); - return self::parseBin($ip); - } - - if (is_numeric($ip)) { - return self::parseLong($ip); - } - - return new self($ip); - } - - /** - * @param string $binIP - * @throws IpException - * @return IP - */ - public static function parseBin($binIP) - { - if (!preg_match('/^([0-1]{32}|[0-1]{128})$/', $binIP)) { - throw new IpException("Invalid binary IP address format"); - } - - $in_addr = ''; - foreach (array_map('bindec', str_split($binIP, 8)) as $char) { - $in_addr .= pack('C*', $char); - } - - return new self(inet_ntop($in_addr)); - } - - /** - * @param string $hexIP - * @throws IpException - * @return IP - */ - public static function parseHex($hexIP) - { - if (!preg_match('/^([0-9a-fA-F]{8}|[0-9a-fA-F]{32})$/', $hexIP)) { - throw new IpException("Invalid hexadecimal IP address format"); - } - - return new self(inet_ntop(pack('H*', $hexIP))); - } - - /** - * @param string|int $longIP - * @return IP - */ - public static function parseLong($longIP, $version=self::IP_V4) - { - if ($version === self::IP_V4) { - $ip = new self(long2ip($longIP)); - } else { - $binary = array(); - for ($i = 0; $i < self::IP_V6_OCTETS; $i++) { - $binary[] = bcmod($longIP, 256); - $longIP = bcdiv($longIP, 256, 0); - } - $ip = new self(inet_ntop(call_user_func_array('pack', array_merge(array('C*'), array_reverse($binary))))); - } - - return $ip; - } - - /** - * @param string $inAddr - * @return IP - */ - public static function parseInAddr($inAddr) - { - return new self(inet_ntop($inAddr)); - } - - /** - * @return string - */ - public function getVersion() - { - $version = ''; - - if (filter_var(inet_ntop($this->in_addr), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { - $version = self::IP_V4; - } elseif (filter_var(inet_ntop($this->in_addr), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { - $version = self::IP_V6; - } - - return $version; - } - - /** - * @return int - */ - public function getMaxPrefixLength() - { - return $this->getVersion() === self::IP_V4 - ? self::IP_V4_MAX_PREFIX_LENGTH - : self::IP_V6_MAX_PREFIX_LENGTH; - } - - /** - * @return int - */ - public function getOctetsCount() - { - return $this->getVersion() === self::IP_V4 - ? self::IP_V4_OCTETS - : self::IP_V6_OCTETS; - } - - /** - * @return string - */ - public function getReversePointer() - { - if ($this->getVersion() === self::IP_V4) { - $reverseOctets = array_reverse(explode('.', $this->__toString())); - $reversePointer = implode('.', $reverseOctets) . '.in-addr.arpa'; - } else { - $unpacked = unpack('H*hex', $this->in_addr); - $reverseOctets = array_reverse(str_split($unpacked['hex'])); - $reversePointer = implode('.', $reverseOctets) . '.ip6.arpa'; - } - - return $reversePointer; - } - - /** - * @return string - */ - public function inAddr() - { - return $this->in_addr; - } - - /** - * @return string - */ - public function toBin() - { - $binary = array(); - foreach (unpack('C*', $this->in_addr) as $char) { - $binary[] = str_pad(decbin($char), 8, '0', STR_PAD_LEFT); - } - - return implode($binary); - } - - /** - * @return string - */ - public function toHex() - { - return bin2hex($this->in_addr); - } - - /** - * @return string - */ - public function toLong() - { - $long = 0; - if ($this->getVersion() === self::IP_V4) { - $long = sprintf('%u', ip2long(inet_ntop($this->in_addr))); - } else { - $octet = self::IP_V6_OCTETS - 1; - foreach ($chars = unpack('C*', $this->in_addr) as $char) { - $long = bcadd($long, bcmul($char, bcpow(256, $octet--))); - } - } - - return $long; - } - - /** - * @param int $to - * @return IP - * @throws IpException - */ - public function next($to=1) - { - if ($to < 0) { - throw new IpException("Number must be greater than 0"); - } - - $unpacked = unpack('C*', $this->in_addr); - - for ($i = 0; $i < $to; $i++) { - for ($byte = count($unpacked); $byte >= 0; --$byte) { - if ($unpacked[$byte] < 255) { - $unpacked[$byte]++; - break; - } - - $unpacked[$byte] = 0; - } - } - - return new self(inet_ntop(call_user_func_array('pack', array_merge(array('C*'), $unpacked)))); - } - - /** - * @param int $to - * @return IP - * @throws IpException - */ - public function prev($to=1) - { - - if ($to < 0) { - throw new IpException("Number must be greater than 0"); - } - - $unpacked = unpack('C*', $this->in_addr); - - for ($i = 0; $i < $to; $i++) { - for ($byte = count($unpacked); $byte >= 0; --$byte) { - if ($unpacked[$byte] === 0) { - $unpacked[$byte] = 255; - } else { - $unpacked[$byte]--; - break; - } - } - } - - return new self(inet_ntop(call_user_func_array('pack', array_merge(array('C*'), $unpacked)))); - } - + use PropertyTrait; + + public const string IP_V4 = 'IPv4'; + + public const string IP_V6 = 'IPv6'; + + public const int IP_V4_MAX_PREFIX_LENGTH = 32; + + public const int IP_V6_MAX_PREFIX_LENGTH = 128; + + public const int IP_V4_OCTETS = 4; + + public const int IP_V6_OCTETS = 16; + + private string $in_addr; + + /** + * @throws IpException + */ + public function __construct(string $ip) + { + if (!filter_var($ip, FILTER_VALIDATE_IP)) { + throw new IpException('Invalid IP address format'); + } + + $this->in_addr = inet_pton($ip); + } + + public function __toString(): string + { + return (string) inet_ntop($this->in_addr); + } + + /** + * @throws IpException + */ + public static function parse(string $ip): self + { + if (str_starts_with($ip, '0x')) { + $ip = substr($ip, 2); + + return self::parseHex($ip); + } + + if (str_starts_with($ip, '0b')) { + $ip = substr($ip, 2); + + return self::parseBin($ip); + } + + if (is_numeric($ip)) { + return self::parseLong((int) $ip); + } + + return new self($ip); + } + + /** + * @throws IpException + */ + public static function parseBin(string $binIP): self + { + if (!preg_match('/^([0-1]{32}|[0-1]{128})$/', $binIP)) { + throw new IpException('Invalid binary IP address format'); + } + + $in_addr = ''; + foreach (array_map('bindec', str_split($binIP, 8)) as $char) { + $in_addr .= pack('C*', $char); + } + + return new self(inet_ntop($in_addr)); + } + + /** + * @throws IpException + */ + public static function parseHex(string $hexIP): self + { + if (!preg_match('/^([0-9a-fA-F]{8}|[0-9a-fA-F]{32})$/', $hexIP)) { + throw new IpException('Invalid hexadecimal IP address format'); + } + + return new self(inet_ntop(pack('H*', $hexIP))); + } + + /** + * @throws IpException + */ + public static function parseLong(int | string $longIP, string $version = self::IP_V4): self + { + if ($version === self::IP_V4) { + $ip = new self(long2ip($longIP)); + } else { + $binary = []; + for ($i = 0; $i < self::IP_V6_OCTETS; ++$i) { + $binary[] = bcmod($longIP, '256'); + $longIP = bcdiv($longIP, '256'); + } + + $ip = new self(inet_ntop(pack(...array_merge(['C*'], array_reverse($binary))))); + } + + return $ip; + } + + /** + * @throws IpException + */ + public static function parseInAddr(string $inAddr): self + { + return new self(inet_ntop($inAddr)); + } + + public function getVersion(): string + { + $version = ''; + + if (filter_var(inet_ntop($this->in_addr), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + $version = self::IP_V4; + } elseif (filter_var(inet_ntop($this->in_addr), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $version = self::IP_V6; + } + + return $version; + } + + public function getMaxPrefixLength(): int + { + return $this->getVersion() === self::IP_V4 + ? self::IP_V4_MAX_PREFIX_LENGTH + : self::IP_V6_MAX_PREFIX_LENGTH; + } + + public function getOctetsCount(): int + { + return $this->getVersion() === self::IP_V4 + ? self::IP_V4_OCTETS + : self::IP_V6_OCTETS; + } + + public function getReversePointer(): string + { + if ($this->getVersion() === self::IP_V4) { + $reverseOctets = array_reverse(explode('.', $this->__toString())); + $reversePointer = implode('.', $reverseOctets) . '.in-addr.arpa'; + } else { + $unpacked = unpack('H*hex', $this->in_addr); + $reverseOctets = array_reverse(str_split((string) $unpacked['hex'])); + $reversePointer = implode('.', $reverseOctets) . '.ip6.arpa'; + } + + return $reversePointer; + } + + public function inAddr(): false | string + { + return $this->in_addr; + } + + public function toBin(): string + { + $binary = []; + foreach (unpack('C*', $this->in_addr) as $char) { + $binary[] = str_pad(decbin($char), 8, '0', STR_PAD_LEFT); + } + + return implode('', $binary); + } + + public function toHex(): string + { + return bin2hex($this->in_addr); + } + + public function toLong(): string + { + $long = 0; + if ($this->getVersion() === self::IP_V4) { + $long = sprintf('%u', ip2long(inet_ntop($this->in_addr))); + } else { + $octet = self::IP_V6_OCTETS - 1; + foreach (unpack('C*', $this->in_addr) as $char) { + $exponent = (string) $octet--; + $long = bcadd((string) $long, bcmul((string) $char, bcpow('256', $exponent))); + } + } + + return $long; + } + + /** + * @throws IpException + */ + public function next(int $to = 1): self + { + if ($to < 0) { + throw new IpException('Number must be greater than 0'); + } + + $unpacked = unpack('C*', $this->in_addr); + + for ($i = 0; $i < $to; ++$i) { + for ($byte = count($unpacked); $byte >= 0; --$byte) { + if ($unpacked[$byte] < 255) { + ++$unpacked[$byte]; + + break; + } + + $unpacked[$byte] = 0; + } + } + + return new self(inet_ntop(pack(...array_merge(['C*'], $unpacked)))); + } + + /** + * @throws IpException + */ + public function prev(int $to = 1): self + { + if ($to < 0) { + throw new IpException('Number must be greater than 0'); + } + + $unpacked = unpack('C*', $this->in_addr); + + for ($i = 0; $i < $to; ++$i) { + for ($byte = count($unpacked); $byte >= 0; --$byte) { + if ($unpacked[$byte] === 0) { + $unpacked[$byte] = 255; + } else { + --$unpacked[$byte]; + + break; + } + } + } + + return new self(inet_ntop(pack(...array_merge(['C*'], $unpacked)))); + } } diff --git a/src/Network.php b/src/Network.php index 96b6d4a..4dd6649 100644 --- a/src/Network.php +++ b/src/Network.php @@ -1,380 +1,350 @@ * @link https://github.com/S1lentium/IPTools */ -class Network implements \Iterator, \Countable +class Network implements Countable, Iterator, Stringable { - use PropertyTrait; - - /** - * @var IP - */ - private $ip; - /** - * @var IP + use PropertyTrait; + + private IP $ip; + private IP $netmask; + private int $position = 0; + + /** + * @throws NetworkException + */ + public function __construct(IP $ip, IP $netmask) + { + $this->netmask = $netmask; + $this->ip = $ip; + + $this->setNetmask($netmask); + $this->setIP($ip); + } + + /** + * @throws IpException + */ + public function __toString(): string + { + return $this->getCIDR(); + } + + /** + * @throws IpException + * @throws NetworkException + */ + public static function parse(string $data): self + { + if (preg_match('~^(.+?)/(\d+)$~', $data, $matches)) { + $ip = IP::parse($matches[1]); + $netmask = self::prefix2netmask((int) $matches[2], $ip->getVersion()); + } elseif (strpos($data, ' ')) { + [$ip, $netmask] = explode(' ', $data, 2); + $ip = IP::parse($ip); + $netmask = IP::parse($netmask); + } else { + $ip = IP::parse($data); + $netmask = self::prefix2netmask($ip->getMaxPrefixLength(), $ip->getVersion()); + } + + return new self($ip, $netmask); + } + + /** + * @throws IpException + * @throws NetworkException + */ + public static function prefix2netmask(int $prefixLength, string $version): IP + { + if (!in_array($version, [IP::IP_V4, IP::IP_V6])) { + throw new NetworkException('Wrong IP version'); + } + + $maxPrefixLength = $version === IP::IP_V4 + ? IP::IP_V4_MAX_PREFIX_LENGTH + : IP::IP_V6_MAX_PREFIX_LENGTH; + + if (!($prefixLength >= 0 && $prefixLength <= $maxPrefixLength) + ) { + throw new NetworkException('Invalid prefix length'); + } + + $binIP = str_pad(str_pad('', (int) $prefixLength, '1'), $maxPrefixLength, '0'); + + return IP::parseBin($binIP); + } + + /** + * @param IP $ip ip + */ + public static function netmask2prefix(IP $ip): int + { + return strlen(rtrim($ip->toBin(), '0')); + } + + /** + * @throws NetworkException */ - private $netmask; - /** - * @var int - */ - private $position = 0; - - /** - * @param IP $ip - * @param IP $netmask - */ - public function __construct(IP $ip, IP $netmask) - { - $this->setIP($ip); - $this->setNetmask($netmask); - } - - /** - * - * @return string - */ - public function __toString() - { - return $this->getCIDR(); - } - - /** - * @param string $data - * @return Network - */ - public static function parse($data) - { - if (preg_match('~^(.+?)/(\d+)$~', $data, $matches)) { - $ip = IP::parse($matches[1]); - $netmask = self::prefix2netmask((int)$matches[2], $ip->getVersion()); - } elseif (strpos($data,' ')) { - list($ip, $netmask) = explode(' ', $data, 2); - $ip = IP::parse($ip); - $netmask = IP::parse($netmask); - } else { - $ip = IP::parse($data); - $netmask = self::prefix2netmask($ip->getMaxPrefixLength(), $ip->getVersion()); - } - - return new self($ip, $netmask); - } - - /** - * @param int $prefixLength - * @param string $version - * @return IP - * @throws NetworkException - */ - public static function prefix2netmask($prefixLength, $version) - { - if (!in_array($version, array(IP::IP_V4, IP::IP_V6))) { - throw new NetworkException("Wrong IP version"); - } - - $maxPrefixLength = $version === IP::IP_V4 - ? IP::IP_V4_MAX_PREFIX_LENGTH - : IP::IP_V6_MAX_PREFIX_LENGTH; - - if (!is_numeric($prefixLength) - || !($prefixLength >= 0 && $prefixLength <= $maxPrefixLength) - ) { - throw new NetworkException('Invalid prefix length'); - } - - $binIP = str_pad(str_pad('', (int)$prefixLength, '1'), $maxPrefixLength, '0'); - - return IP::parseBin($binIP); - } - - /** - * @param IP ip - * @return int - */ - public static function netmask2prefix(IP $ip) - { - return strlen(rtrim($ip->toBin(), 0)); - } - - /** - * @param IP ip - * @throws NetworkException - */ - public function setIP(IP $ip) - { - if (isset($this->netmask) && $this->netmask->getVersion() !== $ip->getVersion()) { - throw new NetworkException('IP version is not same as Netmask version'); - } - - $this->ip = $ip; - } - - /** - * @param IP ip - * @throws NetworkException - */ - public function setNetmask(IP $ip) - { - if (!preg_match('/^1*0*$/',$ip->toBin())) { - throw new NetworkException('Invalid Netmask address format'); - } - - if (isset($this->ip) && $ip->getVersion() !== $this->ip->getVersion()) { - throw new NetworkException('Netmask version is not same as IP version'); - } - - $this->netmask = $ip; - } - - /** - * @param int $prefixLength - */ - public function setPrefixLength($prefixLength) - { - $this->setNetmask(self::prefix2netmask((int)$prefixLength, $this->ip->getVersion())); - } - - /** - * @return IP - */ - public function getIP() - { - return $this->ip; - } - - /** - * @return IP - */ - public function getNetmask() - { - return $this->netmask; - } - - /** - * @return IP - */ - public function getNetwork() - { - return new IP(inet_ntop($this->getIP()->inAddr() & $this->getNetmask()->inAddr())); - } - - /** - * @return int - */ - public function getPrefixLength() - { - return self::netmask2prefix($this->getNetmask()); - } - - /** - * @return string - */ - public function getCIDR() - { - return sprintf('%s/%s', $this->getNetwork(), $this->getPrefixLength()); - } - - /** - * @return IP - */ - public function getWildcard() - { - return new IP(inet_ntop(~$this->getNetmask()->inAddr())); - } - - /** - * @return IP - */ - public function getBroadcast() - { - return new IP(inet_ntop($this->getNetwork()->inAddr() | ~$this->getNetmask()->inAddr())); - } - - /** - * @return IP - */ - public function getFirstIP() - { - return $this->getNetwork(); - } - - /** - * @return IP + public function setIP(IP $ip): void + { + $this->ip = $ip; + + if ($this->netmask->getVersion() !== $ip->getVersion()) { + throw new NetworkException('IP version is not same as Netmask version'); + } + } + + /** + * @throws NetworkException */ - public function getLastIP() - { - return $this->getBroadcast(); - } - - /** - * @return int|string - */ - public function getBlockSize() - { - $maxPrefixLength = $this->ip->getMaxPrefixLength(); - $prefixLength = $this->getPrefixLength(); - - if ($this->ip->getVersion() === IP::IP_V6) { - return bcpow('2', (string)($maxPrefixLength - $prefixLength)); - } - - return pow(2, $maxPrefixLength - $prefixLength); - } - - /** - * @return Range - */ - public function getHosts() - { - $firstHost = $this->getNetwork(); - $lastHost = $this->getBroadcast(); - - if ($this->ip->getVersion() === IP::IP_V4) { - if ($this->getBlockSize() > 2) { - $firstHost = IP::parseBin(substr($firstHost->toBin(), 0, $firstHost->getMaxPrefixLength() - 1) . '1'); - $lastHost = IP::parseBin(substr($lastHost->toBin(), 0, $lastHost->getMaxPrefixLength() - 1) . '0'); - } - } - - return new Range($firstHost, $lastHost); - } - - /** - * @param IP|Network $exclude - * @return Network[] - * @throws NetworkException - */ - public function exclude($exclude) - { - $exclude = self::parse($exclude); - - if (strcmp($exclude->getFirstIP()->inAddr() , $this->getLastIP()->inAddr()) > 0 - || strcmp($exclude->getLastIP()->inAddr() , $this->getFirstIP()->inAddr()) < 0 - ) { - throw new NetworkException('Exclude subnet not within target network'); - } - - $networks = array(); - - $newPrefixLength = $this->getPrefixLength() + 1; - if ($newPrefixLength > $this->ip->getMaxPrefixLength()) { - return $networks; + public function setNetmask(IP $netMask): void + { + $this->netmask = $netMask; + + if (!preg_match('/^1*0*$/', $netMask->toBin())) { + throw new NetworkException('Invalid Netmask address format'); } - $lower = clone $this; - $lower->setPrefixLength($newPrefixLength); - - $upper = clone $lower; - $upper->setIP($lower->getLastIP()->next()); - - while ($newPrefixLength <= $exclude->getPrefixLength()) { - $range = new Range($lower->getFirstIP(), $lower->getLastIP()); - if ($range->contains($exclude)) { - $matched = $lower; - $unmatched = $upper; - } else { - $matched = $upper; - $unmatched = $lower; - } - - $networks[] = clone $unmatched; - - if (++$newPrefixLength > $this->getNetwork()->getMaxPrefixLength()) break; - - $matched->setPrefixLength($newPrefixLength); - $unmatched->setPrefixLength($newPrefixLength); - $unmatched->setIP($matched->getLastIP()->next()); - } - - sort($networks); - - return $networks; - } - - /** - * @param int $prefixLength - * @return Network[] - * @throws NetworkException - */ - public function moveTo($prefixLength) - { - $maxPrefixLength = $this->ip->getMaxPrefixLength(); - - if ($prefixLength <= $this->getPrefixLength() || $prefixLength > $maxPrefixLength) { - throw new NetworkException('Invalid prefix length '); - } - - $netmask = self::prefix2netmask($prefixLength, $this->ip->getVersion()); - $networks = array(); - - $subnet = clone $this; - $subnet->setPrefixLength($prefixLength); - - while ($subnet->ip->inAddr() <= $this->getLastIP()->inAddr()) { - $networks[] = $subnet; - $subnet = new self($subnet->getLastIP()->next(), $netmask); - } - - return $networks; - } - - /** - * @return IP - */ - #[ReturnTypeWillChange] - public function current() - { - return $this->getFirstIP()->next($this->position); - } - - /** - * @return int - */ - #[ReturnTypeWillChange] - public function key() - { - return $this->position; - } + if ($netMask->getVersion() !== $this->ip->getVersion()) { + throw new NetworkException('Netmask version is not same as IP version'); + } + } /** - * @return void + * @throws NetworkException + * @throws IpException */ - #[ReturnTypeWillChange] - public function next() - { - ++$this->position; - } + public function setPrefixLength(int $prefixLength): void + { + $this->setNetmask(self::prefix2netmask($prefixLength, $this->ip->getVersion())); + } + + public function getIP(): IP + { + return $this->ip; + } + + public function getNetmask(): IP + { + return $this->netmask; + } + + /** + * @throws IpException + */ + public function getNetwork(): IP + { + return new IP(inet_ntop($this->getIP()->inAddr() & $this->getNetmask()->inAddr())); + } + + public function getPrefixLength(): int + { + return self::netmask2prefix($this->getNetmask()); + } /** - * @return void + * @throws IpException */ - #[ReturnTypeWillChange] - public function rewind() - { - $this->position = 0; - } - - /** - * @return bool - */ - #[ReturnTypeWillChange] - public function valid() - { - return strcmp($this->getFirstIP()->next($this->position)->inAddr(), $this->getLastIP()->inAddr()) <= 0; - } - - /** - * @return int - */ - #[ReturnTypeWillChange] - public function count() - { - return (integer)$this->getBlockSize(); - } + public function getCIDR(): string + { + return sprintf('%s/%s', $this->getNetwork(), $this->getPrefixLength()); + } + /** + * @throws IpException + */ + public function getWildcard(): IP + { + return new IP(inet_ntop(~$this->getNetmask()->inAddr())); + } + + /** + * @throws IpException + */ + public function getBroadcast(): IP + { + return new IP(inet_ntop($this->getNetwork()->inAddr() | ~$this->getNetmask()->inAddr())); + } + + /** + * @throws IpException + */ + public function getFirstIP(): IP + { + return $this->getNetwork(); + } + + /** + * @throws IpException + */ + public function getLastIP(): IP + { + return $this->getBroadcast(); + } + + public function getBlockSize(): int | string + { + $maxPrefixLength = $this->ip->getMaxPrefixLength(); + $prefixLength = $this->getPrefixLength(); + + if ($this->ip->getVersion() === IP::IP_V6) { + return bcpow('2', (string) ($maxPrefixLength - $prefixLength)); + } + + return 2 ** ($maxPrefixLength - $prefixLength); + } + + /** + * @throws RangeException + * @throws IpException + */ + public function getHosts(): Range + { + $firstHost = $this->getNetwork(); + $lastHost = $this->getBroadcast(); + + if ($this->ip->getVersion() === IP::IP_V4 && $this->getBlockSize() > 2) { + $firstHost = IP::parseBin(substr($firstHost->toBin(), 0, $firstHost->getMaxPrefixLength() - 1) . '1'); + $lastHost = IP::parseBin(substr($lastHost->toBin(), 0, $lastHost->getMaxPrefixLength() - 1) . '0'); + } + + return new Range($firstHost, $lastHost); + } + + /** + * @throws IpException + * @throws NetworkException + * @throws RangeException + */ + public function exclude(string $exclude): array + { + $exclude = self::parse($exclude); + + if (strcmp($exclude->getFirstIP()->inAddr(), $this->getLastIP()->inAddr()) > 0 + || strcmp($exclude->getLastIP()->inAddr(), $this->getFirstIP()->inAddr()) < 0 + ) { + throw new NetworkException('Exclude subnet not within target network'); + } + + $networks = []; + + $newPrefixLength = $this->getPrefixLength() + 1; + if ($newPrefixLength > $this->ip->getMaxPrefixLength()) { + return $networks; + } + + $lower = clone $this; + $lower->setPrefixLength($newPrefixLength); + + $upper = clone $lower; + $upper->setIP($lower->getLastIP()->next()); + + while ($newPrefixLength <= $exclude->getPrefixLength()) { + $range = new Range($lower->getFirstIP(), $lower->getLastIP()); + if ($range->contains($exclude)) { + $matched = $lower; + $unmatched = $upper; + } else { + $matched = $upper; + $unmatched = $lower; + } + + $networks[] = clone $unmatched; + + if (++$newPrefixLength > $this->getNetwork()->getMaxPrefixLength()) { + break; + } + + $matched->setPrefixLength($newPrefixLength); + $unmatched->setPrefixLength($newPrefixLength); + $unmatched->setIP($matched->getLastIP()->next()); + } + + sort($networks); + + return $networks; + } + + /** + * @throws IpException + * @throws NetworkException + */ + public function moveTo(int $prefixLength): array + { + $maxPrefixLength = $this->ip->getMaxPrefixLength(); + + if ($prefixLength <= $this->getPrefixLength() || $prefixLength > $maxPrefixLength) { + throw new NetworkException('Invalid prefix length '); + } + + $ip = self::prefix2netmask($prefixLength, $this->ip->getVersion()); + $networks = []; + + $subnet = clone $this; + $subnet->setPrefixLength($prefixLength); + + while ($subnet->ip->inAddr() <= $this->getLastIP()->inAddr()) { + $networks[] = $subnet; + $subnet = new self($subnet->getLastIP()->next(), $ip); + } + + return $networks; + } + + /** + * @throws IpException + */ + #[ReturnTypeWillChange] + public function current(): IP + { + return $this->getFirstIP()->next($this->position); + } + + #[ReturnTypeWillChange] + public function key(): int + { + return $this->position; + } + + #[ReturnTypeWillChange] + public function next(): void + { + ++$this->position; + } + + #[ReturnTypeWillChange] + public function rewind(): void + { + $this->position = 0; + } + + /** + * @throws IpException + */ + #[ReturnTypeWillChange] + public function valid(): bool + { + return strcmp($this->getFirstIP()->next($this->position)->inAddr(), $this->getLastIP()->inAddr()) <= 0; + } + + #[ReturnTypeWillChange] + public function count(): int + { + return (int) $this->getBlockSize(); + } } diff --git a/src/PropertyTrait.php b/src/PropertyTrait.php index 5763644..2c7862a 100644 --- a/src/PropertyTrait.php +++ b/src/PropertyTrait.php @@ -1,4 +1,7 @@ $name(); - } - - foreach (array('get', 'to') as $prefix) { + public function __get(string $name) + { + if (method_exists($this, $name)) { + return $this->$name(); + } + + foreach (['get', 'to'] as $prefix) { $method = $prefix . ucfirst($name); - if(method_exists($this, $method)) { + if (method_exists($this, $method)) { return $this->$method(); } } - trigger_error('Undefined property'); - return null; - } - - /** - * @param string $name - * @param mixed $value - */ - public function __set($name, $value) - { - $method = 'set'. ucfirst($name); - if (!method_exists($this, $method)) { - trigger_error('Undefined property'); - return; - } - $this->$method($value); - } + trigger_error('Undefined property'); + + return null; + } + + public function __set(string $name, mixed $value): void + { + $method = 'set' . ucfirst($name); + if (!method_exists($this, $method)) { + trigger_error('Undefined property'); + + return; + } + $this->$method($value); + } } diff --git a/src/Range.php b/src/Range.php index ce809b1..1a6d47d 100644 --- a/src/Range.php +++ b/src/Range.php @@ -1,243 +1,219 @@ * @link https://github.com/S1lentium/IPTools */ -class Range implements \Iterator, \Countable +class Range implements Countable, Iterator { - use PropertyTrait; - - /** - * @var IP - */ - private $firstIP; - /** - * @var IP - */ - private $lastIP; - /** - * @var int - */ - private $position = 0; - - /** - * @param IP $firstIP - * @param IP $lastIP - * @throws RangeException - */ - public function __construct(IP $firstIP, IP $lastIP) - { - $this->setFirstIP($firstIP); - $this->setLastIP($lastIP); - } - - /** - * @param string $data - * @return Range - */ - public static function parse($data) - { - if (strpos($data,'/') || strpos($data,' ')) { - $network = Network::parse($data); - $firstIP = $network->getFirstIP(); - $lastIP = $network->getLastIP(); - } elseif (strpos($data, '*') !== false) { - $firstIP = IP::parse(str_replace('*', '0', $data)); - $lastIP = IP::parse(str_replace('*', '255', $data)); - } elseif (strpos($data, '-')) { - list($first, $last) = explode('-', $data, 2); - $firstIP = IP::parse($first); - $lastIP = IP::parse($last); - } else { - $firstIP = IP::parse($data); - $lastIP = clone $firstIP; - } - - return new self($firstIP, $lastIP); - } - - /** - * @param IP|Network|Range $find - * @return bool - * @throws RangeException - */ - public function contains($find) - { - if ($find instanceof IP) { - $within = (strcmp($find->inAddr(), $this->firstIP->inAddr()) >= 0) - && (strcmp($find->inAddr(), $this->lastIP->inAddr()) <= 0); - } elseif ($find instanceof Range || $find instanceof Network) { - /** - * @var Network|Range $find - */ - $within = (strcmp($find->getFirstIP()->inAddr(), $this->firstIP->inAddr()) >= 0) - && (strcmp($find->getLastIP()->inAddr(), $this->lastIP->inAddr()) <= 0); - } else { - throw new RangeException('Invalid type'); - } - - return $within; - } - - /** - * @param IP $ip - * @throws RangeException - */ - public function setFirstIP(IP $ip) - { - if ($this->lastIP && strcmp($ip->inAddr(), $this->lastIP->inAddr()) > 0) { - throw new RangeException('First IP is grater than second'); - } - - $this->firstIP = $ip; - } - - /** - * @param IP $ip - * @throws RangeException - */ - public function setLastIP(IP $ip) - { - if ($this->firstIP && strcmp($ip->inAddr(), $this->firstIP->inAddr()) < 0) { - throw new RangeException('Last IP is less than first'); - } - - $this->lastIP = $ip; - } - - /** - * @return IP - */ - public function getFirstIP() - { - return $this->firstIP; - } - - /** - * @return IP - */ - public function getLastIP() - { - return $this->lastIP; - } - - /** - * @return Network[] - */ - public function getNetworks() - { - $span = $this->getSpanNetwork(); - - $networks = array(); - - if ($span->getFirstIP()->inAddr() === $this->firstIP->inAddr() - && $span->getLastIP()->inAddr() === $this->lastIP->inAddr() - ) { - $networks = array($span); - } else { - if ($span->getFirstIP()->inAddr() !== $this->firstIP->inAddr()) { - $excluded = $span->exclude($this->firstIP->prev()); - foreach ($excluded as $network) { - if (strcmp($network->getFirstIP()->inAddr(), $this->firstIP->inAddr()) >= 0) { - $networks[] = $network; - } - } - } - - if ($span->getLastIP()->inAddr() !== $this->lastIP->inAddr()) { - if (!$networks) { - $excluded = $span->exclude($this->lastIP->next()); - } else { - $excluded = array_pop($networks); - $excluded = $excluded->exclude($this->lastIP->next()); - } - - foreach ($excluded as $network) { - $networks[] = $network; - if ($network->getLastIP()->inAddr() === $this->lastIP->inAddr()) { - break; - } - } - } - - } - - return $networks; - } - - /** - * @return Network - */ - public function getSpanNetwork() - { - $xorIP = IP::parseInAddr($this->getFirstIP()->inAddr() ^ $this->getLastIP()->inAddr()); - - preg_match('/^(0*)/', $xorIP->toBin(), $match); - - $prefixLength = strlen($match[1]); - - $ip = IP::parseBin(str_pad(substr($this->getFirstIP()->toBin(), 0, $prefixLength), $xorIP->getMaxPrefixLength(), '0')); - - return new Network($ip, Network::prefix2netmask($prefixLength, $ip->getVersion())); - } - - /** - * @return IP - */ - #[ReturnTypeWillChange] - public function current() - { - return $this->firstIP->next($this->position); - } - - /** - * @return int - */ - #[ReturnTypeWillChange] - public function key() - { - return $this->position; - } + use PropertyTrait; + + private IP $firstIP; + private IP $lastIP; + private int $position = 0; + + /** + * @throws RangeException + */ + public function __construct(IP $firstIP, IP $lastIP) + { + $this->firstIP = $firstIP; + $this->lastIP = $lastIP; + + $this->setFirstIP($firstIP); + $this->setLastIP($lastIP); + } + + /** + * @throws IpException + * @throws RangeException + * @throws NetworkException + */ + public static function parse(string $data): self + { + if (strpos($data, '/') || strpos($data, ' ')) { + $network = Network::parse($data); + $firstIP = $network->getFirstIP(); + $lastIP = $network->getLastIP(); + } elseif (str_contains($data, '*')) { + $firstIP = IP::parse(str_replace('*', '0', $data)); + $lastIP = IP::parse(str_replace('*', '255', $data)); + } elseif (strpos($data, '-')) { + [$first, $last] = explode('-', $data, 2); + $firstIP = IP::parse($first); + $lastIP = IP::parse($last); + } else { + $firstIP = IP::parse($data); + $lastIP = clone $firstIP; + } + + return new self($firstIP, $lastIP); + } + + /** + * @throws RangeException + * @throws IpException + */ + public function contains(IP | Network | Range $find): bool + { + return match (true) { + $find instanceof IP => (strcmp($find->inAddr(), $this->firstIP->inAddr()) >= 0) + && (strcmp($find->inAddr(), $this->lastIP->inAddr()) <= 0), + $find instanceof Network => (strcmp($find->getFirstIP()->inAddr(), $this->firstIP->inAddr()) >= 0) + && (strcmp($find->getLastIP()->inAddr(), $this->lastIP->inAddr()) <= 0), + $find instanceof Range => (strcmp($find->getFirstIP()->inAddr(), $this->firstIP->inAddr()) >= 0) + && (strcmp($find->getLastIP()->inAddr(), $this->lastIP->inAddr()) <= 0), + default => throw new RangeException('Invalid type'), + }; + } + + /** + * @throws RangeException + */ + public function setFirstIP(IP $ip): void + { + if (strcmp($ip->inAddr(), $this->lastIP->inAddr()) > 0) { + throw new RangeException('First IP is grater than second'); + } + + $this->firstIP = $ip; + } + + /** + * @throws RangeException + */ + public function setLastIP(IP $ip): void + { + if (strcmp($ip->inAddr(), $this->firstIP->inAddr()) < 0) { + throw new RangeException('Last IP is less than first'); + } + + $this->lastIP = $ip; + } + + public function getFirstIP(): IP + { + return $this->firstIP; + } + + public function getLastIP(): IP + { + return $this->lastIP; + } + + /** + * @throws IpException + * @throws NetworkException + * @throws RangeException + * @return Network[] + */ + public function getNetworks(): array + { + $span = $this->getSpanNetwork(); + + $networks = []; + + if ($span->getFirstIP()->inAddr() === $this->firstIP->inAddr() + && $span->getLastIP()->inAddr() === $this->lastIP->inAddr() + ) { + $networks = [$span]; + } else { + if ($span->getFirstIP()->inAddr() !== $this->firstIP->inAddr()) { + $excluded = $span->exclude($this->firstIP->prev()->__toString()); + foreach ($excluded as $network) { + if (strcmp($network->getFirstIP()->inAddr(), $this->firstIP->inAddr()) >= 0) { + $networks[] = $network; + } + } + } + + if ($span->getLastIP()->inAddr() !== $this->lastIP->inAddr()) { + if ($networks === []) { + $excluded = $span->exclude($this->lastIP->next()->__toString()); + } else { + $excluded = array_pop($networks); + $excluded = $excluded->exclude($this->lastIP->next()->__toString()); + } + + foreach ($excluded as $network) { + $networks[] = $network; + if ($network->getLastIP()->inAddr() === $this->lastIP->inAddr()) { + break; + } + } + } + } + + return $networks; + } /** - * @return void + * @throws NetworkException + * @throws IpException */ - #[ReturnTypeWillChange] - public function next() - { - ++$this->position; - } + public function getSpanNetwork(): Network + { + $xorIP = IP::parseInAddr($this->getFirstIP()->inAddr() ^ $this->getLastIP()->inAddr()); + + preg_match('/^(0*)/', $xorIP->toBin(), $match); + + $prefixLength = strlen($match[1]); + + $ip = IP::parseBin(str_pad(substr($this->getFirstIP()->toBin(), 0, $prefixLength), $xorIP->getMaxPrefixLength(), '0')); + + return new Network($ip, Network::prefix2netmask($prefixLength, $ip->getVersion())); + } /** - * @return void + * @throws IpException */ - #[ReturnTypeWillChange] - public function rewind() - { - $this->position = 0; - } - - /** - * @return bool - */ - #[ReturnTypeWillChange] - public function valid() - { - return strcmp($this->firstIP->next($this->position)->inAddr(), $this->lastIP->inAddr()) <= 0; - } - - /** - * @return int - */ - #[ReturnTypeWillChange] - public function count() - { - return (integer)bcadd(bcsub($this->lastIP->toLong(), $this->firstIP->toLong()), 1); - } + #[ReturnTypeWillChange] + public function current(): IP + { + return $this->firstIP->next($this->position); + } + + #[ReturnTypeWillChange] + public function key(): int + { + return $this->position; + } + + #[ReturnTypeWillChange] + public function next(): void + { + ++$this->position; + } + + #[ReturnTypeWillChange] + public function rewind(): void + { + $this->position = 0; + } + /** + * @throws IpException + */ + #[ReturnTypeWillChange] + public function valid(): bool + { + return strcmp($this->firstIP->next($this->position)->inAddr(), $this->lastIP->inAddr()) <= 0; + } + + #[ReturnTypeWillChange] + public function count(): int + { + return (int) bcadd(bcsub($this->lastIP->toLong(), $this->firstIP->toLong()), '1'); + } } diff --git a/tests/IPTest.php b/tests/IPTest.php index 8967e30..6c4e137 100644 --- a/tests/IPTest.php +++ b/tests/IPTest.php @@ -1,14 +1,23 @@ assertEquals(IP::IP_V6_OCTETS, $ipv6->getOctetsCount()); } - /** - * @dataProvider getTestContructorExceptionData - */ - public function testConstructorException($string) + #[DataProvider('getTestConstructorExceptionData')] + public function testConstructorException($string): void { $this->expectException(IpException::class); new IP($string); } - public function testProperties() + public function testProperties(): void { $ip = new IP('127.0.0.1'); @@ -51,28 +58,30 @@ public function testProperties() } /** - * @dataProvider getToStringData + * @throws IpException */ - public function testToString($actual, $expected) + #[DataProvider('getToStringData')] + public function testToString($actual, $expected): void { $ip = new IP($actual); - $this->assertEquals($expected, (string)$ip); - + $this->assertEquals($expected, (string) $ip); } /** - * @dataProvider getTestParseData + * @throws IpException */ - public function testParse($ipString, $expected) + #[DataProvider('getTestParseData')] + public function testParse($ipString, $expected): void { $ip = IP::parse($ipString); $this->assertEquals($expected, (string) $ip); } /** - * @dataProvider getParseBinData + * @throws IpException */ - public function testParseBin($bin, $expectedString) + #[DataProvider('getParseBinData')] + public function testParseBin($bin, $expectedString): void { $ip = IP::parseBin($bin); @@ -80,46 +89,54 @@ public function testParseBin($bin, $expectedString) $this->assertEquals($bin, $ip->toBin()); } - public function testParseBinException() + public function testParseBinException(): void { $this->expectException(IpException::class); IP::parseBin('192.168.1.1'); } - public function testParseLong() + /** + * @throws IpException + */ + public function testParseLong(): void { - $ipv4long = '2130706433'; + $ipv4long = 2130706433; $ipv4 = IP::parseLong($ipv4long); $ipv6Long = '340277174624079928635746076935438991360'; $ipv6 = IP::parseLong($ipv6Long, IP::IP_V6); - $this->assertEquals('127.0.0.1', (string)$ipv4); + $this->assertEquals('127.0.0.1', (string) $ipv4); $this->assertEquals($ipv4long, $ipv4->toLong()); - $this->assertEquals('ffff::', (string)$ipv6); + $this->assertEquals('ffff::', (string) $ipv6); $this->assertEquals($ipv6Long, $ipv6->toLong()); } - public function testParseHex() + /** + * @throws IpException + */ + public function testParseHex(): void { $hex = '7f000001'; $ip = IP::parseHex($hex); - $this->assertEquals('127.0.0.1', (string)$ip); + $this->assertEquals('127.0.0.1', (string) $ip); $this->assertEquals($hex, $ip->toHex()); - } - public function testParseHexException() + public function testParseHexException(): void { $this->expectException(IpException::class); IP::parseHex('192.168.1.1'); } - public function testParseInAddr() + /** + * @throws IpException + */ + public function testParseInAddr(): void { $inAddr = inet_pton('127.0.0.1'); $ip = IP::parseInAddr($inAddr); @@ -133,9 +150,10 @@ public function testParseInAddr() } /** - * @dataProvider getTestNextData + * @throws IpException */ - public function testNext($ip, $step, $expected) + #[DataProvider('getTestNextData')] + public function testNext($ip, $step, $expected): void { $object = new IP($ip); $next = $object->next($step); @@ -144,9 +162,10 @@ public function testNext($ip, $step, $expected) } /** - * @dataProvider getTestPrevData + * @throws IpException */ - public function testPrev($ip, $step, $expected) + #[DataProvider('getTestPrevData')] + public function testPrev($ip, $step, $expected): void { $object = new IP($ip); $prev = $object->prev($step); @@ -154,100 +173,101 @@ public function testPrev($ip, $step, $expected) $this->assertEquals($expected, (string) $prev); } - public function testPrevException() + public function testPrevException(): void { $this->expectException(IpException::class); - $object = new IP('192.168.1.1'); - $object->prev(-1); + $ip = new IP('192.168.1.1'); + $ip->prev(-1); } /** - * @dataProvider getReversePointerData + * @throws IpException */ - public function testReversePointer($ip, $expected) + #[DataProvider('getReversePointerData')] + public function testReversePointer($ip, $expected): void { $object = new IP($ip); $reversePointer = $object->getReversePointer(); $this->assertEquals($expected, $reversePointer); } - public function getTestContructorExceptionData() + public static function getTestConstructorExceptionData(): array { - return array( - array('256.0.0.1'), - array('127.-1.0.1'), - array(123.45), - array(-123.45), - array('cake'), - array('12345'), - array('-12345'), - array('0000:0000:0000:ffff:0127:0000:0000:0001:0000'), - ); + return [ + ['256.0.0.1'], + ['127.-1.0.1'], + ['123.45'], + ['-123.45'], + ['cake'], + ['12345'], + ['-12345'], + ['0000:0000:0000:ffff:0127:0000:0000:0001:0000'], + ]; } - public function getToStringData() + public static function getToStringData(): array { - return array( - array('127.0.0.1', '127.0.0.1'), - array('2001::', '2001::'), - array('2001:0000:0000:0000:0000:0000:0000:0000', '2001::'), - array('2001:0000:0000:0000:8000:0000:0000:0000', '2001::8000:0:0:0') - ); + return [ + ['127.0.0.1', '127.0.0.1'], + ['2001::', '2001::'], + ['2001:0000:0000:0000:0000:0000:0000:0000', '2001::'], + ['2001:0000:0000:0000:8000:0000:0000:0000', '2001::8000:0:0:0'], + ]; } - public function getTestParseData() + public static function getTestParseData(): array { - return array( - array(2130706433, '127.0.0.1'), //long - array('0b01111111000000000000000000000001', '127.0.0.1'), //bin - array('0x7f000001', '127.0.0.1'), //hex, - array('0x20010000000000008000000000000000', '2001::8000:0:0:0'), //hex - array('127.0.0.1', '127.0.0.1'), - array('2001::', '2001::') - ); + return [ + ['2130706433', '127.0.0.1'], // long + ['0b01111111000000000000000000000001', '127.0.0.1'], // bin + ['0x7f000001', '127.0.0.1'], // hex, + ['0x20010000000000008000000000000000', '2001::8000:0:0:0'], // hex + ['127.0.0.1', '127.0.0.1'], + ['2001::', '2001::'], + ]; } - public function getParseBinData() + public static function getParseBinData(): array { - return array( - array( + return [ + [ '00100000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - '2001::' - ), - array('01111111000000000000000000000001', '127.0.0.1') - ); + '2001::', + ], + ['01111111000000000000000000000001', '127.0.0.1'], + ]; } - public function getTestNextData() + public static function getTestNextData(): array { - return array( - array('192.168.0.1', 1, '192.168.0.2'), - array('192.168.0.1', 254, '192.168.0.255'), - array('192.168.0.1', 255, '192.168.1.0'), - array('2001::', 1, '2001::1'), - array('2001::', 65535, '2001::ffff'), - array('2001::', 65536, '2001::1:0') - ); + return [ + ['192.168.0.1', 1, '192.168.0.2'], + ['192.168.0.1', 254, '192.168.0.255'], + ['192.168.0.1', 255, '192.168.1.0'], + ['2001::', 1, '2001::1'], + ['2001::', 65535, '2001::ffff'], + ['2001::', 65536, '2001::1:0'], + ]; } - public function getTestPrevData() + public static function getTestPrevData(): array { - return array( - array('192.168.1.1', 1, '192.168.1.0'), - array('192.168.1.0', 1, '192.168.0.255'), - array('192.168.1.1', 258, '192.167.255.255'), - array('2001::1', 1, '2001::'), - array('2001::1:0', 1, '2001::ffff'), - array('2001::1:0', 65536, '2001::'), - ); + return [ + ['192.168.1.1', 1, '192.168.1.0'], + ['192.168.1.0', 1, '192.168.0.255'], + ['192.168.1.1', 258, '192.167.255.255'], + ['2001::1', 1, '2001::'], + ['2001::1:0', 1, '2001::ffff'], + ['2001::1:0', 65536, '2001::'], + ]; } - public function getReversePointerData() + public static function getReversePointerData(): array { - return array( - array('192.0.2.5', '5.2.0.192.in-addr.arpa'), - array('2001:db8::567:89ab', 'b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa'), - ); + return [ + ['192.0.2.5', '5.2.0.192.in-addr.arpa'], + ['2001:db8::567:89ab', 'b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa'], + ]; } } diff --git a/tests/NetworkTest.php b/tests/NetworkTest.php index 54962ed..1a95072 100644 --- a/tests/NetworkTest.php +++ b/tests/NetworkTest.php @@ -1,16 +1,26 @@ assertEquals('127.0.0.0/24', (string)$ipv4Network); - $this->assertEquals('2001::/112', (string)$ipv6Network); + $this->assertEquals('127.0.0.0/24', (string) $ipv4Network); + $this->assertEquals('2001::/112', (string) $ipv6Network); } - public function testProperties() + /** + * @throws NetworkException + * @throws IpException + */ + public function testProperties(): void { $network = Network::parse('127.0.0.1/24'); $network->ip = new IP('192.0.0.2'); $this->assertEquals('192.0.0.2', $network->ip); - $this->assertEquals('192.0.0.0/24', (string)$network); - $this->assertEquals('0.0.0.255', (string)$network->wildcard); - $this->assertEquals('192.0.0.0', (string)$network->firstIP); - $this->assertEquals('192.0.0.255', (string)$network->lastIP); + $this->assertEquals('192.0.0.0/24', (string) $network); + $this->assertEquals('0.0.0.255', (string) $network->wildcard); + $this->assertEquals('192.0.0.0', (string) $network->firstIP); + $this->assertEquals('192.0.0.255', (string) $network->lastIP); } /** - * @dataProvider getTestParseData + * @throws NetworkException + * @throws IpException */ - public function testParse($data, $expected) + #[DataProvider('getTestParseData')] + public function testParse($data, $expected): void { - $this->assertEquals($expected, (string)Network::parse($data)); + $this->assertEquals($expected, (string) Network::parse($data)); } - public function testParseWrongNetwork() + /** + * @throws NetworkException + */ + public function testParseWrongNetwork(): void { $this->expectException(IpException::class); @@ -54,24 +73,30 @@ public function testParseWrongNetwork() } /** - * @dataProvider getPrefixData + * @throws NetworkException + * @throws IpException */ - public function testPrefix2Mask($prefix, $version, $mask) + #[DataProvider('getPrefixData')] + public function testPrefix2Mask($prefix, $version, $mask): void { $this->assertEquals($mask, Network::prefix2netmask($prefix, $version)); } - public function testPrefix2MaskWrongIPVersion() + /** + * @throws IpException + */ + public function testPrefix2MaskWrongIPVersion(): void { $this->expectException(NetworkException::class); - Network::prefix2netmask('128', 'ip_version'); + Network::prefix2netmask(128, 'ip_version'); } /** - * @dataProvider getInvalidPrefixData + * @throws IpException */ - public function testPrefix2MaskInvalidPrefix($prefix, $version) + #[DataProvider('getInvalidPrefixData')] + public function testPrefix2MaskInvalidPrefix($prefix, $version): void { $this->expectException(NetworkException::class); @@ -79,35 +104,43 @@ public function testPrefix2MaskInvalidPrefix($prefix, $version) } /** - * @dataProvider getHostsData + * @throws NetworkException + * @throws IpException */ - public function testHosts($data, $expected) + #[DataProvider('getHostsData')] + public function testHosts($data, $expected): void { - foreach(Network::parse($data)->getHosts as $ip) { - $result[] = (string)$ip; + $result = []; + foreach (Network::parse($data)->getHosts as $ip) { + $result[] = (string) $ip; } $this->assertEquals($expected, $result); } /** - * @dataProvider getExcludeData + * @throws RangeException + * @throws NetworkException + * @throws IpException */ - public function testExclude($data, $exclude, $expected) + #[DataProvider('getExcludeData')] + public function testExclude($data, $exclude, $expected): void { - $result = array(); + $result = []; - foreach(Network::parse($data)->exclude($exclude) as $network) { - $result[] = (string)$network; + foreach (Network::parse($data)->exclude($exclude) as $network) { + $result[] = (string) $network; } $this->assertEquals($expected, $result); } /** - * @dataProvider getExcludeExceptionData + * @throws IpException + * @throws RangeException */ - public function testExcludeException($data, $exclude) + #[DataProvider('getExcludeExceptionData')] + public function testExcludeException($data, $exclude): void { $this->expectException(NetworkException::class); @@ -115,162 +148,171 @@ public function testExcludeException($data, $exclude) } /** - * @dataProvider getMoveToData + * @throws NetworkException + * @throws IpException */ - public function testMoveTo($network, $prefixLength, $expected) + #[DataProvider('getMoveToData')] + public function testMoveTo($network, $prefixLength, $expected): void { - $result = array(); + $result = []; foreach (Network::parse($network)->moveTo($prefixLength) as $network) { - $result[] = (string)$network; + $result[] = (string) $network; } $this->assertEquals($expected, $result); } /** - * @dataProvider getMoveToExceptionData + * @throws IpException */ - public function testMoveToException($network, $prefixLength) + #[DataProvider('getMoveToExceptionData')] + public function testMoveToException($network, $prefixLength): void { $this->expectException(NetworkException::class); Network::parse($network)->moveTo($prefixLength); } - /** - * @dataProvider getTestIterationData + /** + * @throws NetworkException + * @throws IpException */ - public function testNetworkIteration($data, $expected) + #[DataProvider('getTestIterationData')] + public function testNetworkIteration($data, $expected): void { - foreach (Network::parse($data) as $key => $ip) { - $result[] = (string)$ip; + $result = []; + foreach (Network::parse($data) as $network) { + $result[] = (string) $network; } $this->assertEquals($expected, $result); } - /** - * @dataProvider getTestCountData + /** + * @throws NetworkException + * @throws IpException */ - public function testCount($data, $expected) + #[DataProvider('getTestCountData')] + public function testCount($data, $expected): void { - $this->assertEquals($expected, count(Network::parse($data))); + $this->assertCount($expected, Network::parse($data)); } - public function getTestParseData() + public static function getTestParseData(): array { - return array( - array('192.168.0.54/24', '192.168.0.0/24'), - array('2001::2001:2001/32', '2001::/32'), - array('127.168.0.1 255.255.255.255', '127.168.0.1/32'), - array('1234::1234', '1234::1234/128'), - ); + return [ + ['192.168.0.54/24', '192.168.0.0/24'], + ['2001::2001:2001/32', '2001::/32'], + ['127.168.0.1 255.255.255.255', '127.168.0.1/32'], + ['1234::1234', '1234::1234/128'], + ]; } - public function getPrefixData() + /** + * @throws IpException + */ + public static function getPrefixData(): array { - return array( - array('24', IP::IP_V4, IP::parse('255.255.255.0')), - array('32', IP::IP_V4, IP::parse('255.255.255.255')), - array('64', IP::IP_V6, IP::parse('ffff:ffff:ffff:ffff::')), - array('128', IP::IP_V6, IP::parse('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')) - ); + return [ + [24, IP::IP_V4, IP::parse('255.255.255.0')], + [32, IP::IP_V4, IP::parse('255.255.255.255')], + [64, IP::IP_V6, IP::parse('ffff:ffff:ffff:ffff::')], + [128, IP::IP_V6, IP::parse('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')], + ]; } - public function getInvalidPrefixData() + public static function getInvalidPrefixData(): array { - return array( - array('-1', IP::IP_V4), - array('33', IP::IP_V4), - array('prefix', IP::IP_V4), - array('-1', IP::IP_V6), - array('129', IP::IP_V6), - ); + return [ + [-1, IP::IP_V4], + [33, IP::IP_V4], + [-1, IP::IP_V6], + [129, IP::IP_V6], + ]; } - public function getHostsData() + public static function getHostsData(): array { - return array( - array('192.0.2.0/29', - array( + return [ + ['192.0.2.0/29', + [ '192.0.2.1', '192.0.2.2', '192.0.2.3', '192.0.2.4', '192.0.2.5', '192.0.2.6', - ) - ), - ); + ], + ], + ]; } - public function getExcludeData() + public static function getExcludeData(): array { - return array( - array('192.0.2.0/28', '192.0.2.1/32', - array( + return [ + ['192.0.2.0/28', '192.0.2.1/32', + [ '192.0.2.0/32', '192.0.2.2/31', '192.0.2.4/30', '192.0.2.8/29', - ) - ), - array('192.0.2.2/32', '192.0.2.2/32', array()), - ); + ], + ], + ['192.0.2.2/32', '192.0.2.2/32', []], + ]; } - public function getExcludeExceptionData() + public static function getExcludeExceptionData(): array { - return array( - array('192.0.2.0/28', '192.0.3.0/24'), - array('192.0.2.2/32', '192.0.2.3/32'), - ); + return [ + ['192.0.2.0/28', '192.0.3.0/24'], + ['192.0.2.2/32', '192.0.2.3/32'], + ]; } - public function getMoveToData() + public static function getMoveToData(): array { - return array( - array('192.168.0.0/22', '24', - array( + return [ + ['192.168.0.0/22', 24, + [ '192.168.0.0/24', '192.168.1.0/24', '192.168.2.0/24', - '192.168.3.0/24' - ) - ), - array('192.168.2.0/24', '25', - array( + '192.168.3.0/24', + ], + ], + ['192.168.2.0/24', 25, + [ '192.168.2.0/25', - '192.168.2.128/25' - ) - ), - array('192.168.2.0/30', '32', - array( + '192.168.2.128/25', + ], + ], + ['192.168.2.0/30', 32, + [ '192.168.2.0/32', '192.168.2.1/32', '192.168.2.2/32', - '192.168.2.3/32' - ) - ), - ); + '192.168.2.3/32', + ], + ], + ]; } - public function getMoveToExceptionData() + public static function getMoveToExceptionData(): array { - return array( - array('192.168.0.0/22', '22'), - array('192.168.0.0/22', '21'), - array('192.168.0.0/22', '33'), - array('192.168.0.0/22', 'prefixLength') - ); + return [ + ['192.168.0.0/22', 22], + ['192.168.0.0/22', 21], + ['192.168.0.0/22', 33], + ]; } - public function getTestIterationData() + public static function getTestIterationData(): array { - return array( - array('192.168.2.0/29', - array( + return [ + ['192.168.2.0/29', + [ '192.168.2.0', '192.168.2.1', '192.168.2.2', @@ -279,10 +321,10 @@ public function getTestIterationData() '192.168.2.5', '192.168.2.6', '192.168.2.7', - ) - ), - array('2001:db8::/125', - array( + ], + ], + ['2001:db8::/125', + [ '2001:db8::', '2001:db8::1', '2001:db8::2', @@ -291,16 +333,16 @@ public function getTestIterationData() '2001:db8::5', '2001:db8::6', '2001:db8::7', - ) - ), - ); + ], + ], + ]; } - public function getTestCountData() + public static function getTestCountData(): array { - return array( - array('127.0.0.0/31', 2), - array('2001:db8::/120', 256), - ); + return [ + ['127.0.0.0/31', 2], + ['2001:db8::/120', 256], + ]; } } diff --git a/tests/RangeTest.php b/tests/RangeTest.php index 9ce3544..180ccdd 100644 --- a/tests/RangeTest.php +++ b/tests/RangeTest.php @@ -1,17 +1,29 @@ getNetworks() as $network) { - $result[] = (string)$network; + $result[] = (string) $network; } - $this->assertEquals($expected, $result); + $this->assertEquals($expected, $result); } /** - * @dataProvider getTestContainsData + * @throws RangeException + * @throws NetworkException + * @throws IpException */ - public function testContains($data, $find, $expected) + #[DataProvider('getTestContainsData')] + public function testContains($data, $find, $expected): void { $this->assertEquals($expected, Range::parse($data)->contains(new IP($find))); } /** - * @dataProvider getTestIterationData + * @throws NetworkException + * @throws IpException + * @throws RangeException */ - public function testRangeIteration($data, $expected) + #[DataProvider('getTestIterationData')] + public function testRangeIteration($data, $expected): void { - foreach (Range::parse($data) as $key => $ip) { - $result[] = (string)$ip; + $result = []; + foreach (Range::parse($data) as $range) { + $result[] = (string) $range; } $this->assertEquals($expected, $result); } /** - * @dataProvider getTestCountData + * @throws NetworkException + * @throws RangeException + * @throws IpException */ - public function testCount($data, $expected) + #[DataProvider('getTestCountData')] + public function testCount($data, $expected): void { - $this->assertEquals($expected, count(Range::parse($data))); + $this->assertCount($expected, Range::parse($data)); } - public function getTestParseData() + public static function getTestParseData(): array { - return array( - array('127.0.0.1-127.255.255.255', array('127.0.0.1', '127.255.255.255')), - array('127.0.0.1/24', array('127.0.0.0', '127.0.0.255')), - array('127.*.0.0', array('127.0.0.0', '127.255.0.0')), - array('127.255.255.0', array('127.255.255.0', '127.255.255.0')), - ); + return [ + ['127.0.0.1-127.255.255.255', ['127.0.0.1', '127.255.255.255']], + ['127.0.0.1/24', ['127.0.0.0', '127.0.0.255']], + ['127.*.0.0', ['127.0.0.0', '127.255.0.0']], + ['127.255.255.0', ['127.255.255.0', '127.255.255.0']], + ]; } - public function getTestNetworksData() + public static function getTestNetworksData(): array { - return array( - array('192.168.1.*', array('192.168.1.0/24')), - array('192.168.1.208-192.168.1.255', array( + return [ + ['192.168.1.*', ['192.168.1.0/24']], + ['192.168.1.208-192.168.1.255', [ '192.168.1.208/28', - '192.168.1.224/27' - )), - array('192.168.1.0-192.168.1.191', array( + '192.168.1.224/27', + ]], + ['192.168.1.0-192.168.1.191', [ '192.168.1.0/25', - '192.168.1.128/26' - )), - array('192.168.1.125-192.168.1.126', array( + '192.168.1.128/26', + ]], + ['192.168.1.125-192.168.1.126', [ '192.168.1.125/32', '192.168.1.126/32', - )), - ); + ]], + ]; } - public function getTestContainsData() + public static function getTestContainsData(): array { - return array( - array('192.168.*.*', '192.168.245.15', true), - array('192.168.*.*', '192.169.255.255', false), + return [ + ['192.168.*.*', '192.168.245.15', true], + ['192.168.*.*', '192.169.255.255', false], /** - * 10.10.45.48 --> 00001010 00001010 00101101 00110000 - * the last 0000 leads error + * 10.10.45.48 --> 00001010 00001010 00101101 00110000 + * the last 0000 leads error. */ - array('10.10.45.48/28', '10.10.45.58', true), + ['10.10.45.48/28', '10.10.45.58', true], - array('2001:db8::/64', '2001:db8::ffff', true), - array('2001:db8::/64', '2001:db8:ffff::', false), - ); + ['2001:db8::/64', '2001:db8::ffff', true], + ['2001:db8::/64', '2001:db8:ffff::', false], + ]; } - public function getTestIterationData() + public static function getTestIterationData(): array { - return array( - array('192.168.2.0-192.168.2.7', - array( + return [ + ['192.168.2.0-192.168.2.7', + [ '192.168.2.0', '192.168.2.1', '192.168.2.2', @@ -120,10 +145,10 @@ public function getTestIterationData() '192.168.2.5', '192.168.2.6', '192.168.2.7', - ) - ), - array('2001:db8::/125', - array( + ], + ], + ['2001:db8::/125', + [ '2001:db8::', '2001:db8::1', '2001:db8::2', @@ -132,17 +157,16 @@ public function getTestIterationData() '2001:db8::5', '2001:db8::6', '2001:db8::7', - ) - ), - ); + ], + ], + ]; } - public function getTestCountData() + public static function getTestCountData(): array { - return array( - array('127.0.0.0/31', 2), - array('2001:db8::/120', 256), - ); + return [ + ['127.0.0.0/31', 2], + ['2001:db8::/120', 256], + ]; } - }