S1lentium/IPTools

View on GitHub
src/IP.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php
namespace IPTools;

use IPTools\Exception\IpException;

/**
 * @author Safarov Alisher <alisher.safarov@outlook.com>
 * @link https://github.com/S1lentium/IPTools
 */
class IP
{
    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))));
    }

}