2015-12-28 3 views
1

У меня есть многомерный массив, состоящие из IP-адреса и маски подсети:PHP - создать иерархический список из массива подсетей

array(4) { 
    [0]=> 
    array(3) { 
    ["ip"]=> 
    string(12) "192.168.0.0" 
    ["mask"]=> 
    int(22) 
    } 
    [1]=> 
    array(3) { 
    ["ip"]=> 
    string(12) "192.168.0.0" 
    ["mask"]=> 
    int(30) 
    } 
    [2]=> 
    array(3) { 
    ["ip"]=> 
    string(12) "192.168.0.4" 
    ["mask"]=> 
    int(31) 
    } 
    [3]=> 
    array(3) { 
    ["ip"]=> 
    string(12) "192.168.0.4" 
    ["mask"]=> 
    int(32) 
    } 
} 

Массив уже находится в правильном порядке. Следующее, что нужно сделать, это преобразовать этот массив в иерархический список, то желаемый результат должен быть что-то вроде этого:

192.168.0.0/22 
- 192.168.0.0/30 
- 192.168.0.4/31 
-- 192.168.0.4/32 

Каждый раза, когда подсеть попадет под большими подсетями, отступ («-») должно быть увеличено на 1, как показано на вышесказанном выше. Я не работаю с предопределенными родительскими идентификаторами, потому что список подсетей может меняться в любой момент. Это преобразование должно выполняться «на лету».

Хотелось бы, чтобы я попробовал что-нибудь, но я не знаю с чего начать. Единственное, о чем я могу думать, это сравнить 2 подсетей на каждой итерации через массив. Сначала сравнить подсети 1 и 2, а затем 2 и 3, затем 3 и 4, и так далее ...

+0

вы пытались что-то? поделитесь им пожалуйста. – Mohammad

+0

@Mohammad Не совсем, как я заявил в своем вопросе, я понятия не имею, с чего начать. Я предложил решение, которое я мог бы попробовать, но насколько это приемлемо? – Beeelze

+0

Одна из проблем, которые у вас есть с вашим примером, заключается в том, что вы рассматриваете IP-адрес и диапазон как взаимозаменяемые. Диапазон не обязательно попадает в другой диапазон. Вы можете запускать проверку функций, если данный IP находится в диапазоне, но диапазоны не обязательно являются иерархическими, как показывает ваш пример. – Ian

ответ

1

Я написал небольшой класс для обработки логики CIDR (упрощая процесс вложенности).

<?php 
class Cidr { 

    /** 
    * @var int 
    */ 
    private $subnet; 

    /** 
    * @var int 
    */ 
    private $mask; 

    /** 
    * @var int 
    */ 
    private $upperBound; 

    /** 
    * @var self[] 
    */ 
    private $children = []; 

    /** 
    * Cidr constructor 
    * 
    * @param string $subnet 
    * @param int $mask 
    */ 
    public function __construct($subnet, $mask) { 
     $this->subnet  = ip2long($subnet); 
     $this->mask  = (int) $mask; 
     $this->upperBound = $this->subnet + pow(2, 32 - $this->mask) - 1; 
    } 

    /** 
    * @param string $rangeStr 
    * 
    * @return Cidr 
    */ 
    public static function fromString($rangeStr) { 
     list($subnet, $mask) = explode('/', $rangeStr); 
     return new self($subnet, $mask); 
    } 

    /** 
    * @return string 
    */ 
    public function __toString() { 
     return "{$this->minIp()}/{$this->mask}"; 
    } 

    /** 
    * @param Cidr $cidr 
    * 
    * @return bool 
    */ 
    public function addChild(self $cidr) { 
     if ($cidr === $this) { 
      return false; 
     } 

     if ($this->isRangeInRange($cidr)) { 
      foreach ($this->children as $child) { 
       if ($child->addChild($cidr)) { 
        return true; 
       } 
      } 

      $this->children[] = $cidr; 
      return true; 
     } 

     return false; 
    } 

    /** 
    * @return Cidr[] 
    */ 
    public function getChildren() { 
     return $this->children; 
    } 

    /** 
    * @return string 
    */ 
    public function minIp() { 
     return long2ip($this->subnet); 
    } 

    /** 
    * @return string 
    */ 
    public function maxIp() { 
     return long2ip($this->upperBound); 
    } 

    /** 
    * Check if an IP falls within this range 
    * 
    * @param string $ip 
    * 
    * @return bool 
    */ 
    public function isIpInRange($ip) { 
     $mask = -1 << (32 - $this->mask); 
     return (ip2long($ip) & $mask) === ($this->subnet & $mask); 
    } 

    /** 
    * @param Cidr $cidr 
    * 
    * @return bool 
    */ 
    public function isRangeInRange(self $cidr) { 
     return $cidr->subnet >= $this->subnet && $cidr->upperBound <= $this->upperBound; 
    } 

    /** 
    * @param self[] $cidrs 
    */ 
    public static function nestCidrs(array &$cidrs) { 
     foreach ($cidrs as $a) { 
      foreach ($cidrs as $k => $b) { 
       if ($a !== $b && $a->addChild($b)) { 
        unset($cidrs[$k]); 
       } 
      } 
     } 
    } 

    /** 
    * @param self[] $cidrs 
    * @param int $depth 
    */ 
    public static function displayCidrs(array $cidrs, $depth = 0) { 
     foreach ($cidrs as $cidr) { 
      echo str_repeat('-', $depth) . "{$cidr}\n"; 
      self::displayCidrs($cidr->getChildren(), $depth + 1); 
     } 
    } 
} 

Используя массив подсетей (как вы показали в вашем примере):

$subnets = [ 
    [ 
     'ip' => '192.168.0.0', 
     'mask' => 22, 
    ], 
    [ 
     'ip' => '192.168.0.0', 
     'mask' => 30, 
    ], 
    [ 
     'ip' => '192.168.0.4', 
     'mask' => 31, 
    ], 
    [ 
     'ip' => '192.168.0.4', 
     'mask' => 32, 
    ], 
]; 

Создать массив Cidr объектов:

$cidrs = []; 
foreach ($subnets as $subnet) { 
    $cidr = new Cidr($subnet['ip'], $subnet['mask']); 
    $cidrs[(string) $cidr] = $cidr; 
} 

Run вложенности функции (рекурсивно будет добавьте каждый диапазон подсети к себе, если необходимо:

Cidr::nestCidrs($cidrs); 

Тогда отображение результатов:

Cidr::displayCidrs($cidrs); 

Результаты:

192.168.0.0/22 
-192.168.0.0/30 
-192.168.0.4/31 
--192.168.0.4/32 
+0

Спасибо, это делает работу очень красиво!Я изучаю код, чтобы понять, понимаю ли я логику, которую вы используете :-) – Beeelze

+0

Как бы получить список CIDR в виде массива? – Beeelze

+0

Вы можете просто не запускать 'Cidr :: displayCidrs ($ cidrs);'. После запуска 'Cidr :: nestCidrs ($ cidrs);', '$ cidrs' array является вложенным массивом объектов Cidr. Если вы хотите, вы можете добавить в класс функцию 'toArray()' и вернуть массив с ключами 'ip' и' mask'. – Ian

-1

пример образец для того, что вы хотите

 $h = array(
     array("ip_bin"=>"192.168.0.0","mask"=>22), 
     array("ip_bin"=>"192.168.1.0","mask"=>30), 
     array("ip_bin"=>"192.168.4.0","mask"=>31),); 

     $dash = ""; 

     foreach($h as $s){ 
      $dash .="-"; 
      echo $dash. $s['ip_bin']."/".$s['mask']."<br>"; 
      } 

Выход будет

 -192.168.0.0 
    --192.168.1.0 
    ---192.168.4.0 
Смежные вопросы