1<?php 2 3/** 4 * This file is part of the FreeDSx LDAP package. 5 * 6 * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace FreeDSx\Ldap\Entry; 13 14use ArrayIterator; 15use Countable; 16use FreeDSx\Ldap\Exception\InvalidArgumentException; 17use FreeDSx\Ldap\Exception\UnexpectedValueException; 18use IteratorAggregate; 19use Traversable; 20use function array_slice; 21use function count; 22use function implode; 23use function ltrim; 24use function preg_split; 25 26/** 27 * Represents a Distinguished Name. 28 * 29 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 30 */ 31class Dn implements IteratorAggregate, Countable 32{ 33 /** 34 * @var string 35 */ 36 protected $dn; 37 38 /** 39 * @var null|Rdn[] 40 */ 41 protected $pieces; 42 43 /** 44 * @param string $dn 45 */ 46 public function __construct(string $dn) 47 { 48 $this->dn = $dn; 49 } 50 51 /** 52 * @return Rdn 53 * @throws UnexpectedValueException 54 */ 55 public function getRdn(): Rdn 56 { 57 if ($this->pieces === null) { 58 $this->parse(); 59 } 60 if (!isset($this->pieces[0])) { 61 throw new UnexpectedValueException('The DN has no RDN.'); 62 } 63 64 return $this->pieces[0]; 65 } 66 67 /** 68 * @return null|Dn 69 * @throws UnexpectedValueException 70 */ 71 public function getParent(): ?Dn 72 { 73 if ($this->pieces === null) { 74 $this->parse(); 75 } 76 if (count((array) $this->pieces) < 2) { 77 return null; 78 } 79 80 return new Dn(implode(',', array_slice((array) $this->pieces, 1))); 81 } 82 83 /** 84 * @inheritDoc 85 * @@psalm-return \ArrayIterator<array-key, Rdn> 86 * @throws UnexpectedValueException 87 */ 88 public function getIterator(): Traversable 89 { 90 return new ArrayIterator($this->toArray()); 91 } 92 93 /** 94 * @return string 95 */ 96 public function toString(): string 97 { 98 return $this->dn; 99 } 100 101 /** 102 * @inheritDoc 103 * @psalm-return 0|positive-int 104 * @throws UnexpectedValueException 105 */ 106 public function count(): int 107 { 108 if ($this->pieces === null) { 109 $this->parse(); 110 } 111 112 return count((array) $this->pieces); 113 } 114 115 /** 116 * @return string 117 */ 118 public function __toString() 119 { 120 return $this->dn; 121 } 122 123 /** 124 * @return Rdn[] 125 * @throws UnexpectedValueException 126 */ 127 public function toArray(): array 128 { 129 if ($this->pieces !== null) { 130 return $this->pieces; 131 } 132 $this->parse(); 133 134 return ($this->pieces === null) ? [] : $this->pieces; 135 } 136 137 /** 138 * @param string $dn 139 * @return bool 140 */ 141 public static function isValid(string $dn): bool 142 { 143 try { 144 (new self($dn))->toArray(); 145 146 return true; 147 } catch (UnexpectedValueException | InvalidArgumentException $e) { 148 return false; 149 } 150 } 151 152 /** 153 * @todo This needs proper handling. But the regex would probably be rather crazy. 154 * 155 * @throws UnexpectedValueException 156 */ 157 protected function parse(): void 158 { 159 if ($this->dn === '') { 160 $this->pieces = []; 161 162 return; 163 } 164 $pieces = preg_split('/(?<!\\\\),/', $this->dn); 165 $pieces = ($pieces === false) ? [] : $pieces; 166 167 if (count($pieces) === 0) { 168 throw new UnexpectedValueException(sprintf( 169 'The DN value "%s" is not valid.', 170 $this->dn 171 )); 172 } 173 174 $rdns = []; 175 foreach ($pieces as $i => $piece) { 176 $rdns[$i] = Rdn::create(ltrim($piece)); 177 } 178 179 $this->pieces = $rdns; 180 } 181} 182