1<?php 2 3/** 4 * Hoa 5 * 6 * 7 * @license 8 * 9 * New BSD License 10 * 11 * Copyright © 2007-2017, Hoa community. All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions are met: 15 * * Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * * Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * * Neither the name of the Hoa nor the names of its contributors may be 21 * used to endorse or promote products derived from this software without 22 * specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE 28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 * POSSIBILITY OF SUCH DAMAGE. 35 */ 36 37namespace Hoa\Protocol\Node; 38 39use Hoa\Consistency; 40use Hoa\Protocol; 41 42/** 43 * Class \Hoa\Protocol\Node\Node. 44 * 45 * Abstract class for all `hoa://`'s nodes. 46 * 47 * @copyright Copyright © 2007-2017 Hoa community 48 * @license New BSD License 49 */ 50class Node implements \ArrayAccess, \IteratorAggregate 51{ 52 /** 53 * Node's name. 54 * 55 * @var string 56 */ 57 protected $_name = null; 58 59 /** 60 * Path for the `reach` method. 61 * 62 * @var string 63 */ 64 protected $_reach = null; 65 66 /** 67 * Children of the node. 68 * 69 * @var array 70 */ 71 private $_children = []; 72 73 74 75 /** 76 * Construct a protocol's node. 77 * If it is not a data object (i.e. if it does not extend this class to 78 * overload the `$_name` attribute), we can set the `$_name` attribute 79 * dynamically. This is useful to create a node on-the-fly. 80 * 81 * @param string $name Node's name. 82 * @param string $reach Path for the `reach` method. 83 * @param array $children Node's children. 84 */ 85 public function __construct($name = null, $reach = null, array $children = []) 86 { 87 if (null !== $name) { 88 $this->_name = $name; 89 } 90 91 if (null !== $reach) { 92 $this->_reach = $reach; 93 } 94 95 foreach ($children as $child) { 96 $this[] = $child; 97 } 98 99 return; 100 } 101 102 /** 103 * Add a node. 104 * 105 * @param string $name Node's name. If null, will be 106 * set to name of `$node`. 107 * @param \Hoa\Protocol\Protocol $node Node to add. 108 * @return \Hoa\Protocol\Protocol 109 * @throws \Hoa\Protocol\Exception 110 */ 111 public function offsetSet($name, $node) 112 { 113 if (!($node instanceof self)) { 114 throw new Protocol\Exception( 115 'Protocol node must extend %s.', 116 0, 117 __CLASS__ 118 ); 119 } 120 121 if (empty($name)) { 122 $name = $node->getName(); 123 } 124 125 if (empty($name)) { 126 throw new Protocol\Exception( 127 'Cannot add a node to the `hoa://` protocol without a name.', 128 1 129 ); 130 } 131 132 $this->_children[$name] = $node; 133 134 return; 135 } 136 137 /** 138 * Get a specific node. 139 * 140 * @param string $name Node's name. 141 * @return \Hoa\Protocol\Protocol 142 * @throws \Hoa\Protocol\Exception 143 */ 144 public function offsetGet($name) 145 { 146 if (!isset($this[$name])) { 147 throw new Protocol\Exception( 148 'Node %s does not exist.', 149 2, 150 $name 151 ); 152 } 153 154 return $this->_children[$name]; 155 } 156 157 /** 158 * Check if a node exists. 159 * 160 * @param string $name Node's name. 161 * @return bool 162 */ 163 public function offsetExists($name) 164 { 165 return true === array_key_exists($name, $this->_children); 166 } 167 168 /** 169 * Remove a node. 170 * 171 * @param string $name Node's name to remove. 172 * @return void 173 */ 174 public function offsetUnset($name) 175 { 176 unset($this->_children[$name]); 177 178 return; 179 } 180 181 /** 182 * Resolve a path, i.e. iterate the nodes tree and reach the queue of 183 * the path. 184 * 185 * @param string $path Path to resolve. 186 * @param array &$accumulator Combination of all possibles paths. 187 * @param string $id ID. 188 * @return mixed 189 */ 190 protected function _resolve($path, &$accumulator, $id = null) 191 { 192 if (substr($path, 0, 6) == 'hoa://') { 193 $path = substr($path, 6); 194 } 195 196 if (empty($path)) { 197 return null; 198 } 199 200 if (null === $accumulator) { 201 $accumulator = []; 202 $posId = strpos($path, '#'); 203 204 if (false !== $posId) { 205 $id = substr($path, $posId + 1); 206 $path = substr($path, 0, $posId); 207 } else { 208 $id = null; 209 } 210 } 211 212 $path = trim($path, '/'); 213 $pos = strpos($path, '/'); 214 215 if (false !== $pos) { 216 $next = substr($path, 0, $pos); 217 } else { 218 $next = $path; 219 } 220 221 if (isset($this[$next])) { 222 if (false === $pos) { 223 if (null === $id) { 224 $this->_resolveChoice($this[$next]->reach(), $accumulator); 225 226 return true; 227 } 228 229 $accumulator = null; 230 231 return $this[$next]->reachId($id); 232 } 233 234 $tnext = $this[$next]; 235 $this->_resolveChoice($tnext->reach(), $accumulator); 236 237 return $tnext->_resolve(substr($path, $pos + 1), $accumulator, $id); 238 } 239 240 $this->_resolveChoice($this->reach($path), $accumulator); 241 242 return true; 243 } 244 245 /** 246 * Resolve choices, i.e. a reach value has a “;”. 247 * 248 * @param string $reach Reach value. 249 * @param array &$accumulator Combination of all possibles paths. 250 * @return void 251 */ 252 protected function _resolveChoice($reach, array &$accumulator) 253 { 254 if (empty($accumulator)) { 255 $accumulator = explode(RS, $reach); 256 257 return; 258 } 259 260 if (false === strpos($reach, RS)) { 261 if (false !== $pos = strrpos($reach, "\r")) { 262 $reach = substr($reach, $pos + 1); 263 264 foreach ($accumulator as &$entry) { 265 $entry = null; 266 } 267 } 268 269 foreach ($accumulator as &$entry) { 270 $entry .= $reach; 271 } 272 273 return; 274 } 275 276 $choices = explode(RS, $reach); 277 $ref = $accumulator; 278 $accumulator = []; 279 280 foreach ($choices as $choice) { 281 if (false !== $pos = strrpos($choice, "\r")) { 282 $choice = substr($choice, $pos + 1); 283 284 foreach ($ref as $entry) { 285 $accumulator[] = $choice; 286 } 287 } else { 288 foreach ($ref as $entry) { 289 $accumulator[] = $entry . $choice; 290 } 291 } 292 } 293 294 unset($ref); 295 296 return; 297 } 298 299 /** 300 * Queue of the node. 301 * Generic one. Must be overrided in children classes. 302 * 303 * @param string $queue Queue of the node (generally a filename, 304 * with probably a query). 305 * @return mixed 306 */ 307 public function reach($queue = null) 308 { 309 return empty($queue) ? $this->_reach : $queue; 310 } 311 312 /** 313 * ID of the component. 314 * Generic one. Should be overrided in children classes. 315 * 316 * @param string $id ID of the component. 317 * @return mixed 318 * @throws \Hoa\Protocol\Exception 319 */ 320 public function reachId($id) 321 { 322 throw new Protocol\Exception( 323 'The node %s has no ID support (tried to reach #%s).', 324 4, 325 [$this->getName(), $id] 326 ); 327 } 328 329 /** 330 * Set a new reach value. 331 * 332 * @param string $reach Reach value. 333 * @return string 334 */ 335 public function setReach($reach) 336 { 337 $old = $this->_reach; 338 $this->_reach = $reach; 339 340 return $old; 341 } 342 343 /** 344 * Get node's name. 345 * 346 * @return string 347 */ 348 public function getName() 349 { 350 return $this->_name; 351 } 352 353 /** 354 * Get reach's root. 355 * 356 * @return string 357 */ 358 protected function getReach() 359 { 360 return $this->_reach; 361 } 362 363 /** 364 * Get an iterator. 365 * 366 * @return \ArrayIterator 367 */ 368 public function getIterator() 369 { 370 return new \ArrayIterator($this->_children); 371 } 372 373 /** 374 * Get root the protocol. 375 * 376 * @return \Hoa\Protocol\Protocol 377 */ 378 public static function getRoot() 379 { 380 return Protocol::getInstance(); 381 } 382 383 /** 384 * Print a tree of component. 385 * 386 * @return string 387 */ 388 public function __toString() 389 { 390 static $i = 0; 391 392 $out = str_repeat(' ', $i) . $this->getName() . "\n"; 393 394 foreach ($this as $node) { 395 ++$i; 396 $out .= $node; 397 --$i; 398 } 399 400 return $out; 401 } 402} 403 404/** 405 * Flex entity. 406 */ 407Consistency::flexEntity('Hoa\Protocol\Node\Node'); 408