diff --git a/src/PriorityList.php b/src/PriorityList.php new file mode 100644 index 000000000..9ed0ba41e --- /dev/null +++ b/src/PriorityList.php @@ -0,0 +1,263 @@ +sorted = false; + $this->count++; + + $this->items[$name] = array( + 'data' => $value, + 'priority' => (int) $priority, + 'serial' => $this->serial++, + ); + } + + public function setPriority($name, $priority) + { + if (!isset($this->items[$name])) { + throw new \Exception("item $name not found"); + } + $this->items[$name]['priority'] = (int) $priority; + $this->sorted = false; + return $this; + } + + /** + * Remove a item. + * + * @param string $name + * @return void + */ + public function remove($name) + { + if (!isset($this->items[$name])) { + return; + } + + $this->count--; + unset($this->items[$name]); + } + + /** + * Remove all items. + * + * @return void + */ + public function clear() + { + $this->items = array(); + $this->serial = 0; + $this->count = 0; + $this->sorted = false; + } + + /** + * Get a item. + * + * @param string $name + * @return mixed + */ + public function get($name) + { + if (!isset($this->items[$name])) { + return null; + } + + return $this->items[$name]['data']; + } + + /** + * Sort all items. + * + * @return void + */ + protected function sort() + { + if (!$this->sorted) { + uasort($this->items, array($this, 'compare')); + $this->sorted = true; + } + } + + /** + * Compare the priority of two items. + * + * @param array $item1, + * @param array $item2 + * @return int + */ + protected function compare(array $item1, array $item2) + { + return ($item1['priority'] === $item2['priority']) + ? ($item1['serial'] > $item2['serial'] ? -1 : 1) * $this->isLIFO + : ($item1['priority'] > $item2['priority'] ? -1 : 1); + } + + /** + * Get/Set serial order mode + * + * @param bool $flag + * @return bool + */ + public function isLIFO($flag = null) + { + if ($flag !== null) { + if (($flag = ($flag === true ? 1 : -1)) !== $this->isLIFO) { + $this->isLIFO = $flag; + $this->sorted = false; + } + } + return $this->isLIFO === 1; + } + + /** + * rewind(): defined by Iterator interface. + * + * @see Iterator::rewind() + * @return void + */ + public function rewind() + { + $this->sort(); + reset($this->items); + } + + /** + * current(): defined by Iterator interface. + * + * @see Iterator::current() + * @return mixed + */ + public function current() + { + $node = current($this->items); + return ($node !== false ? $node['data'] : false); + } + + /** + * key(): defined by Iterator interface. + * + * @see Iterator::key() + * @return string + */ + public function key() + { + return key($this->items); + } + + /** + * next(): defined by Iterator interface. + * + * @see Iterator::next() + * @return mixed + */ + public function next() + { + $node = next($this->items); + return ($node !== false ? $node['data'] : false); + } + + /** + * valid(): defined by Iterator interface. + * + * @see Iterator::valid() + * @return bool + */ + public function valid() + { + return ($this->current() !== false); + } + + /** + * count(): defined by Countable interface. + * + * @see Countable::count() + * @return int + */ + public function count() + { + return $this->count; + } + + /** + * Return list as array + * + * @param type $raw + * @return array + */ + public function toArray($flag = self::EXTR_DATA) + { + $this->sort(); + if ($flag == self::EXTR_BOTH) { + return $this->items; + } + return array_map( + ($flag == self::EXTR_PRIORITY) + ? function ($item) { return $item['priority']; } + : function ($item) { return $item['data']; }, + $this->items + ); + } +} diff --git a/test/PriorityListTest.php b/test/PriorityListTest.php new file mode 100644 index 000000000..e93688539 --- /dev/null +++ b/test/PriorityListTest.php @@ -0,0 +1,198 @@ +list = new PriorityList(); + } + + public function testInsert() + { + $this->list->insert('foo', new \stdClass(), 0); + + $this->assertEquals(1, count($this->list)); + + foreach ($this->list as $key => $value) { + $this->assertEquals('foo', $key); + } + } + + public function testRemove() + { + $this->list->insert('foo', new \stdClass(), 0); + $this->list->insert('bar', new \stdClass(), 0); + + $this->assertEquals(2, count($this->list)); + + $this->list->remove('foo'); + + $this->assertEquals(1, count($this->list)); + } + + public function testRemovingNonExistentRouteDoesNotYieldError() + { + $this->list->remove('foo'); + } + + public function testClear() + { + $this->list->insert('foo', new \stdClass(), 0); + $this->list->insert('bar', new \stdClass(), 0); + + $this->assertEquals(2, count($this->list)); + + $this->list->clear(); + + $this->assertEquals(0, count($this->list)); + $this->assertSame(false, $this->list->current()); + } + + public function testGet() + { + $route = new \stdClass(); + + $this->list->insert('foo', $route, 0); + + $this->assertEquals($route, $this->list->get('foo')); + $this->assertNull($this->list->get('bar')); + } + + public function testLIFOOnly() + { + $this->list->insert('foo', new \stdClass()); + $this->list->insert('bar', new \stdClass()); + $this->list->insert('baz', new \stdClass()); + $this->list->insert('foobar', new \stdClass()); + $this->list->insert('barbaz', new \stdClass()); + + $order = array(); + + foreach ($this->list as $key => $value) { + $orders[] = $key; + } + + $this->assertEquals(array('barbaz', 'foobar', 'baz', 'bar', 'foo'), $orders); + } + + public function testPriorityOnly() + { + $this->list->insert('foo', new \stdClass(), 1); + $this->list->insert('bar', new \stdClass(), 0); + $this->list->insert('baz', new \stdClass(), 2); + + $order = array(); + + foreach ($this->list as $key => $value) { + $orders[] = $key; + } + + $this->assertEquals(array('baz', 'foo', 'bar'), $orders); + } + + public function testLIFOWithPriority() + { + $this->list->insert('foo', new \stdClass(), 0); + $this->list->insert('bar', new \stdClass(), 0); + $this->list->insert('baz', new \stdClass(), 1); + + $orders = array(); + + foreach ($this->list as $key => $value) { + $orders[] = $key; + } + + $this->assertEquals(array('baz', 'bar', 'foo'), $orders); + } + + public function testFIFOWithPriority() + { + $this->list->isLIFO(false); + $this->list->insert('foo', new \stdClass(), 0); + $this->list->insert('bar', new \stdClass(), 0); + $this->list->insert('baz', new \stdClass(), 1); + + $order = array(); + + foreach ($this->list as $key => $value) { + $orders[] = $key; + } + + $this->assertEquals(array('baz', 'foo', 'bar'), $orders); + } + + public function testFIFOOnly() + { + $this->list->isLIFO(false); + $this->list->insert('foo', new \stdClass()); + $this->list->insert('bar', new \stdClass()); + $this->list->insert('baz', new \stdClass()); + $this->list->insert('foobar', new \stdClass()); + $this->list->insert('barbaz', new \stdClass()); + + $order = array(); + + foreach ($this->list as $key => $value) { + $orders[] = $key; + } + + $this->assertEquals(array('foo', 'bar', 'baz', 'foobar', 'barbaz'), $orders); + } + + public function testPriorityWithNegativesAndNull() + { + $this->list->insert('foo', new \stdClass(), null); + $this->list->insert('bar', new \stdClass(), 1); + $this->list->insert('baz', new \stdClass(), -1); + + $order = array(); + + foreach ($this->list as $key => $value) { + $orders[] = $key; + } + + $this->assertEquals(array('bar', 'foo', 'baz'), $orders); + } + + public function testToArray() + { + $this->list->insert('foo', 'foo_value', null); + $this->list->insert('bar', 'bar_value', 1); + $this->list->insert('baz', 'baz_value', -1); + + $this->assertEquals( + array( + 'bar' => 'bar_value', + 'foo' => 'foo_value', + 'baz' => 'baz_value' + ), + $this->list->toArray() + ); + + $this->assertEquals( + array( + 'bar' => array('data' => 'bar_value', 'priority' => 1, 'serial' => 1), + 'foo' => array('data' => 'foo_value', 'priority' => 0, 'serial' => 0), + 'baz' => array('data' => 'baz_value', 'priority' => -1, 'serial' => 2), + ), + $this->list->toArray(PriorityList::EXTR_BOTH) + ); + } +}