1<?php 2 3/** 4 * @see https://github.com/laminas/laminas-tag for the canonical source repository 5 * @copyright https://github.com/laminas/laminas-tag/blob/master/COPYRIGHT.md 6 * @license https://github.com/laminas/laminas-tag/blob/master/LICENSE.md New BSD License 7 */ 8 9namespace Laminas\Tag; 10 11use ArrayAccess; 12use Countable; 13use Laminas\Tag\Exception\InvalidArgumentException; 14use Laminas\Tag\Exception\OutOfBoundsException; 15use SeekableIterator; 16 17class ItemList implements Countable, SeekableIterator, ArrayAccess 18{ 19 /** 20 * Items in this list 21 * 22 * @var array 23 */ 24 protected $items = []; 25 26 /** 27 * Count all items 28 * 29 * @return int 30 */ 31 public function count() 32 { 33 return count($this->items); 34 } 35 36 /** 37 * Spread values in the items relative to their weight 38 * 39 * @param array $values 40 * @throws InvalidArgumentException When value list is empty 41 * @return void 42 */ 43 public function spreadWeightValues(array $values) 44 { 45 // Don't allow an empty value list 46 if (count($values) === 0) { 47 throw new InvalidArgumentException('Value list may not be empty'); 48 } 49 50 // Re-index the array 51 $values = array_values($values); 52 53 // If just a single value is supplied simply assign it to to all tags 54 if (count($values) === 1) { 55 foreach ($this->items as $item) { 56 $item->setParam('weightValue', $values[0]); 57 } 58 } else { 59 // Calculate min- and max-weight 60 $minWeight = null; 61 $maxWeight = null; 62 63 foreach ($this->items as $item) { 64 if ($minWeight === null && $maxWeight === null) { 65 $minWeight = $item->getWeight(); 66 $maxWeight = $item->getWeight(); 67 } else { 68 $minWeight = min($minWeight, $item->getWeight()); 69 $maxWeight = max($maxWeight, $item->getWeight()); 70 } 71 } 72 73 // Calculate the thresholds 74 $steps = count($values); 75 $delta = ($maxWeight - $minWeight) / ($steps - 1); 76 $thresholds = []; 77 78 for ($i = 0; $i < $steps; $i++) { 79 $thresholds[$i] = floor(100 * log(($minWeight + $i * $delta) + 2)); 80 } 81 82 // Then assign the weight values 83 foreach ($this->items as $item) { 84 $threshold = floor(100 * log($item->getWeight() + 2)); 85 86 for ($i = 0; $i < $steps; $i++) { 87 if ($threshold <= $thresholds[$i]) { 88 $item->setParam('weightValue', $values[$i]); 89 break; 90 } 91 } 92 } 93 } 94 } 95 96 /** 97 * Seek to an absolute position 98 * 99 * @param int $index 100 * @throws OutOfBoundsException When the seek position is invalid 101 * @return void 102 */ 103 public function seek($index) 104 { 105 $this->rewind(); 106 $position = 0; 107 108 while ($position < $index && $this->valid()) { 109 $this->next(); 110 $position++; 111 } 112 113 if (! $this->valid()) { 114 throw new OutOfBoundsException('Invalid seek position'); 115 } 116 } 117 118 /** 119 * Return the current element 120 * 121 * @return mixed 122 */ 123 public function current() 124 { 125 return current($this->items); 126 } 127 128 /** 129 * Move forward to next element 130 * 131 * @return mixed 132 */ 133 public function next() 134 { 135 return next($this->items); 136 } 137 138 /** 139 * Return the key of the current element 140 * 141 * @return mixed 142 */ 143 public function key() 144 { 145 return key($this->items); 146 } 147 148 /** 149 * Check if there is a current element after calls to rewind() or next() 150 * 151 * @return bool 152 */ 153 public function valid() 154 { 155 return ($this->current() !== false); 156 } 157 158 /** 159 * Rewind the Iterator to the first element 160 * 161 * @return void 162 */ 163 public function rewind() 164 { 165 reset($this->items); 166 } 167 168 /** 169 * Check if an offset exists 170 * 171 * @param mixed $offset 172 * @return bool 173 */ 174 public function offsetExists($offset) 175 { 176 return array_key_exists($offset, $this->items); 177 } 178 179 /** 180 * Get the value of an offset 181 * 182 * @param mixed $offset 183 * @return TaggableInterface 184 */ 185 public function offsetGet($offset) 186 { 187 return $this->items[$offset]; 188 } 189 190 /** 191 * Append a new item 192 * 193 * @param mixed $offset 194 * @param TaggableInterface $item 195 * @throws OutOfBoundsException When item does not implement Laminas\Tag\TaggableInterface 196 * @return void 197 */ 198 public function offsetSet($offset, $item) 199 { 200 // We need to make that check here, as the method signature must be 201 // compatible with ArrayAccess::offsetSet() 202 if (! ($item instanceof TaggableInterface)) { 203 throw new OutOfBoundsException('Item must implement Laminas\Tag\TaggableInterface'); 204 } 205 206 if ($offset === null) { 207 $this->items[] = $item; 208 } else { 209 $this->items[$offset] = $item; 210 } 211 } 212 213 /** 214 * Unset an item 215 * 216 * @param mixed $offset 217 * @return void 218 */ 219 public function offsetUnset($offset) 220 { 221 unset($this->items[$offset]); 222 } 223} 224