Skip to content

Merge IP constructors into base class #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
language: php
php:
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
- 7.3
install:
- composer install
matrix:
include:
- php: 5.3
dist: precise
- composer install
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"homepage": "https://github.com/rlanvin/php-ip",
"license": "MIT",
"require": {
"php": ">=5.3.0",
"php": "~7.0",
"ext-gmp": "*"
},
"require-dev": {
Expand Down
127 changes: 126 additions & 1 deletion src/IP.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ function gmp_shiftr($x, $n)
*/
abstract class IP
{
const IP_VERSION = null;
const MAX_INT = null;
const NB_BITS = null;
const NB_BYTES = null;

/**
* Internal representation of the IP as a numeric format.
* For IPv4, this will be an SIGNED int (32 bits).
Expand All @@ -69,6 +74,126 @@ abstract class IP
*/
protected $class;

/**
* Constructor tries to guess what is the $ip.
*
* @param $ip mixed String, binary string, int or float
*/
public function __construct($ip)
{
if (is_int($ip)) {
$this->ip = self::fromInt($ip);

return;
}

// float (or double) with an integer value
if (is_float($ip) && $ip == floor($ip)) {
$this->ip = self::fromFloat($ip);

return;
}

if (is_string($ip)) {
$this->ip = self::fromString($ip);

return;
}

if ((is_resource($ip) && get_resource_type($ip) === 'GMP integer') || $ip instanceof \GMP) {
$this->ip = self::fromGMP($ip);

return;
}

throw new \InvalidArgumentException(sprintf('Unsupported argument type: "%s".', gettype($ip)));
}

/**
* @param int $ip
*
* @return \GMP
*/
private static function fromInt(int $ip): \GMP
{
$ip = gmp_init(sprintf('%u', $ip), 10);

if (gmp_cmp($ip, static::MAX_INT) > 0) {
throw new \InvalidArgumentException(sprintf('The integer "%s" is not a valid IPv%d address.', gmp_strval($ip), static::IP_VERSION));
}

return $ip;
}

/**
* @param float $ip
*
* @return \GMP
*/
private static function fromFloat(float $ip): \GMP
{
$ip = gmp_init(sprintf('%s', $ip), 10);

if (gmp_cmp($ip, 0) < 0 || gmp_cmp($ip, static::MAX_INT) > 0) {
throw new \InvalidArgumentException(sprintf('The double "%s" is not a valid IPv%d address.', gmp_strval($ip), static::IP_VERSION));
}

return $ip;
}

/**
* @param string $ip
*
* @return \GMP
*/
private static function fromString(string $ip): \GMP
{
// binary, packed string
if (@inet_ntop($ip) !== false) {
if (strlen($ip) != static::NB_BYTES) {
throw new \InvalidArgumentException(sprintf('The binary string "%s" is not a valid IPv%d address.', $ip, static::IP_VERSION));
}

$hex = unpack('H*', $ip);

return gmp_init($hex[1], 16);
}

$filterFlag = constant('FILTER_FLAG_IPV'.static::IP_VERSION);
if (filter_var($ip, FILTER_VALIDATE_IP, $filterFlag)) {
$ip = inet_pton($ip);
$hex = unpack('H*', $ip);

return gmp_init($hex[1], 16);
}

// numeric string (decimal)
if (ctype_digit($ip)) {
$ip = gmp_init($ip, 10);
if (gmp_cmp($ip, static::MAX_INT) > 0) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid decimal IPv%d address.', gmp_strval($ip), static::IP_VERSION));
}

return $ip;
}

throw new \InvalidArgumentException(sprintf('The string "%s" is not a valid IPv%d address.', $ip, static::IP_VERSION));
}

/**
* @param \GMP $ip
*
* @return \GMP
*/
private function fromGMP(\GMP $ip): \GMP
{
if (gmp_cmp($ip, 0) < 0 || gmp_cmp($ip, static::MAX_INT) > 0) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid decimal IPv%d address.', gmp_strval($ip), static::IP_VERSION));
}

return $ip;
}

/**
* Take an IP string/int and return an object of the correct type.
*
Expand Down Expand Up @@ -137,7 +262,7 @@ public function numeric($base = 10)
*/
public function binary()
{
$hex = str_pad($this->numeric(16), static::NB_BITS/4, '0', STR_PAD_LEFT);
$hex = str_pad($this->numeric(16), static::NB_BITS / 4, '0', STR_PAD_LEFT);

return pack('H*', $hex);
}
Expand Down
61 changes: 4 additions & 57 deletions src/IPv4.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,73 +20,20 @@ class IPv4 extends IP
const IP_VERSION = 4;
const MAX_INT = '4294967295';
const NB_BITS = 32;
const NB_BYTES = 4;

/**
* Workaround for lack of late static binding in PHP 5.2
* so I can use "new $this->class()"" instead of "new static()".
*/
protected $class = __CLASS__;

public function getVersion()
{
return self::IP_VERSION;
}

/**
* Constructor tries to guess what is the $ip.
*
* @param $ip mixed String, binary string, int or float
* {@inheritdoc}
*/
public function __construct($ip)
public function getVersion()
{
if (is_int($ip)) {
// if an integer is provided, we have to be careful of the architecture
// on 32 bits plateform, it's always a valid IP
// on 64 bits plateform, we have to test the value
$ip = gmp_init(sprintf('%u', $ip), 10);
if (gmp_cmp($ip, self::MAX_INT) > 0) {
throw new \InvalidArgumentException(sprintf('The integer %s is not a valid IPv4 address', gmp_strval($ip)));
}
$this->ip = $ip;
} elseif (is_float($ip) && $ip == floor($ip)) {
// float (or double) with an integer value
$ip = gmp_init(sprintf('%s', $ip), 10);
if (gmp_cmp($ip, 0) < 0 || gmp_cmp($ip, self::MAX_INT) > 0) {
throw new \InvalidArgumentException(sprintf('The double %s is not a valid IPv4 address', gmp_strval($ip)));
}
$this->ip = $ip;
} elseif (is_string($ip)) {
// binary string
if (!ctype_print($ip)) {
if (strlen($ip) != 4) {
throw new \InvalidArgumentException('The binary string is not a valid IPv4 address');
}
$hex = unpack('H*', $ip);
$this->ip = gmp_init($hex[1], 16);
}
// human readable IPv4
elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$this->ip = gmp_init(sprintf('%u', ip2long($ip)));
}
// numeric string (decimal)
elseif (ctype_digit($ip)) {
$ip = gmp_init($ip);
if (gmp_cmp($ip, self::MAX_INT) > 0) {
throw new \InvalidArgumentException(sprintf('%s is not a valid decimal IPv4 address', gmp_strval($ip)));
}

$this->ip = $ip;
} else {
throw new \InvalidArgumentException("$ip is not a valid IPv4 address");
}
} elseif ((is_resource($ip) && get_resource_type($ip) == 'GMP integer') || $ip instanceof \GMP) {
if (gmp_cmp($ip, 0) < 0 || gmp_cmp($ip, self::MAX_INT) > 0) {
throw new \InvalidArgumentException(sprintf('%s is not a valid decimal IPv4 address', gmp_strval($ip)));
}
$this->ip = $ip;
} else {
throw new \InvalidArgumentException('Unsupported argument type: '.gettype($ip));
}
return self::IP_VERSION;
}

/**
Expand Down
58 changes: 4 additions & 54 deletions src/IPv6.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,70 +22,20 @@ class IPv6 extends IP
const IP_VERSION = 6;
const MAX_INT = '340282366920938463463374607431768211455';
const NB_BITS = 128;
const NB_BYTES = 16;

/**
* Workaround for lack of late static binding in PHP 5.2
* so I can use "new $this->class()"" instead of "new static()".
*/
protected $class = __CLASS__;

public function getVersion()
{
return self::IP_VERSION;
}

/**
* Constuctor tries to guess what is $ip.
*
* @param mixed $ip
* {@inheritdoc}
*/
public function __construct($ip)
public function getVersion()
{
if (is_int($ip)) {
// always a valid IP, since even in 64bits plateform, it's less than max value
$this->ip = gmp_init(sprintf('%u', $ip), 10);
} elseif (is_float($ip) && $ip == floor($ip)) {
// float (or double) with an integer value
$ip = gmp_init(sprintf('%s', $ip), 10);
if (gmp_cmp($ip, 0) < 0 || gmp_cmp($ip, self::MAX_INT) > 0) {
throw new \InvalidArgumentException(sprintf('The double %s is not a valid IPv6 address', gmp_strval($ip)));
}
$this->ip = $ip;
} elseif (is_string($ip)) {
// binary string
if (!ctype_print($ip)) {
// probably the result of inet_pton
// must be 16 bytes exactly to be valid
if (strlen($ip) != 16) {
throw new \InvalidArgumentException('The binary string is not a valid IPv6 address');
}
$hex = unpack('H*', $ip);
$this->ip = gmp_init($hex[1], 16);
}
// valid human readable representation
elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$ip = inet_pton($ip);
$hex = unpack('H*', $ip);
$this->ip = gmp_init($hex[1], 16);
}
// numeric string (decimal)
elseif (ctype_digit($ip)) {
$ip = gmp_init($ip, 10);
if (gmp_cmp($ip, '340282366920938463463374607431768211455') > 0) {
throw new \InvalidArgumentException(sprintf('%s is not a valid decimal IPv6 address', gmp_strval($ip)));
}
$this->ip = $ip;
} else {
throw new \InvalidArgumentException("$ip is not a valid IPv6 address");
}
} elseif ((is_resource($ip) && get_resource_type($ip) == 'GMP integer') || $ip instanceof \GMP) {
if (gmp_cmp($ip, 0) < 0 || gmp_cmp($ip, self::MAX_INT) > 0) {
throw new \InvalidArgumentException(sprintf('%s is not a valid decimal IPv6 address', gmp_strval($ip)));
}
$this->ip = $ip;
} else {
throw new \InvalidArgumentException('Unsupported argument type: '.gettype($ip));
}
return self::IP_VERSION;
}

/**
Expand Down