Skip to content

Instantly share code, notes, and snippets.

@vespakoen
Created September 3, 2013 09:36
Show Gist options
  • Save vespakoen/6421683 to your computer and use it in GitHub Desktop.
Save vespakoen/6421683 to your computer and use it in GitHub Desktop.
What's on the menu?
<?php
namespace Illuminate\Support;
use Closure;
use Countable;
use ArrayAccess;
use ArrayIterator;
use IteratorAggregate;
class Collection implements ArrayAccess, Countable , IteratorAggregate {
/**
* The items contained in the collection.
*
* @var array
*/
protected $items = array();
/**
* Create a new collection.
*
* @param array $items
* @return void
*/
public function __construct(array $items = array())
{
$this->items = $items;
}
/**
* Create a new collection instance if the value isn't one already.
*
* @param mixed $items
* @return \Illuminate\Support\Collection
*/
public static function make($items)
{
if (is_null($items)) return new static;
if ($items instanceof Collection) return $items;
return new static(is_array($items) ? $items : array($items));
}
/**
* Determine if an item exists in the collection by key.
*
* @param mixed $key
* @return bool
*/
public function has($key)
{
return array_key_exists($key, $this->items);
}
/**
* Get an item from the collection by key.
*
* @param mixed $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default = null)
{
if (array_key_exists($key, $this->items))
{
return $this->items[$key];
}
return value($default);
}
/**
* Get all of the items in the collection.
*
* @return array
*/
public function all()
{
return $this->items;
}
/**
* Put an item in the collection by key.
*
* @param mixed $key
* @param mixed $value
* @return void
*/
public function put($key, $value)
{
$this->items[$key] = $value;
}
/**
* Get the first item from the collection.
*
* @return mixed|null
*/
public function first()
{
return count($this->items) > 0 ? reset($this->items) : null;
}
/**
* Get the last item from the collection.
*
* @return mixed|null
*/
public function last()
{
return count($this->items) > 0 ? end($this->items) : null;
}
/**
* Get and remove the first item from the collection.
*
* @return mixed|null
*/
public function shift()
{
return array_shift($this->items);
}
/**
* Push an item onto the beginning of the collection.
*
* @param mixed $value
* @return void
*/
public function push($value)
{
array_unshift($this->items, $value);
}
/**
* Get and remove the last item from the collection.
*
* @return mixed|null
*/
public function pop()
{
return array_pop($this->items);
}
/**
* Remove an item from the collection by key.
*
* @param mixed $key
* @return void
*/
public function forget($key)
{
unset($this->items[$key]);
}
/**
* Execute a callback over each item.
*
* @param Closure $callback
* @return \Illuminate\Support\Collection
*/
public function each(Closure $callback)
{
array_map($callback, $this->items);
return $this;
}
/**
* Run a map over each of the items.
*
* @param Closure $callback
* @return array
*/
public function map(Closure $callback)
{
return new static(array_map($callback, $this->items));
}
/**
* Run a filter over each of the items.
*
* @param Closure $callback
* @return \Illuminate\Support\Collection
*/
public function filter(Closure $callback)
{
$this->items = array_filter($this->items, $callback);
return $this;
}
/**
* Sort through each item with a callback.
*
* @param Closure $callback
* @return \Illuminate\Support\Collection
*/
public function sort(Closure $callback)
{
uasort($this->items, $callback);
return $this;
}
/**
* Sort the collection using the given Closure.
*
* @param \Closure $callback
* @return \Illuminate\Support\Collection
*/
public function sortBy(Closure $callback)
{
$results = array();
// First we will loop through the items and get the comparator from a callback
// function which we were given. Then, we will sort the returned values and
// and grab the corresponding values for the sorted keys from this array.
foreach ($this->items as $key => $value)
{
$results[$key] = $callback($value);
}
asort($results);
// Once we have sorted all of the keys in the array, we will loop through them
// and grab the corresponding model so we can set the underlying items list
// to the sorted version. Then we'll just return the collection instance.
foreach (array_keys($results) as $key)
{
$results[$key] = $this->items[$key];
}
$this->items = $results;
return $this;
}
/**
* Reverse items order.
*
* @return \Illuminate\Support\Collection
*/
public function reverse()
{
return new static(array_reverse($this->items));
}
/**
* Reset the keys on the underlying array.
*
* @return \Illuminate\Support\Collection
*/
public function values()
{
$this->items = array_values($this->items);
return $this;
}
/**
* Fetch a nested element of the collection.
*
* @param string $key
* @return \Illuminate\Support\Collection
*/
public function fetch($key)
{
return new static(array_fetch($this->items, $key));
}
/**
* Get a flattened array of the items in the collection.
*
* @return array
*/
public function flatten()
{
return new static(array_flatten($this->items));
}
/**
* Collapse the collection items into a single array.
*
* @return \Illuminate\Support\Collection
*/
public function collapse()
{
$results = array();
foreach ($this->items as $values)
{
$results = array_merge($results, $values);
}
return new static($results);
}
/**
* Merge items with the collection items.
*
* @param \Illuminate\Support\Collection|\Illuminate\Support\Contracts\ArrayableInterface|array $items
* @return \Illuminate\Support\Collection
*/
public function merge($items)
{
if ($items instanceof Collection)
{
$items = $items->all();
}
elseif ($items instanceof ArrayableInterface)
{
$items = $items->toArray();
}
$results = array_merge($this->items, $items);
return new static($results);
}
/**
* Slice the underlying collection array.
*
* @param int $offset
* @param int $length
* @param bool $preserveKeys
* @return \Illuminate\Support\Collection
*/
public function slice($offset, $length = null, $preserveKeys = false)
{
return new static(array_slice($this->items, $offset, $length, $preserveKeys));
}
/**
* Splice portion of the underlying collection array.
*
* @param int $offset
* @param int $length
* @param mixed $replacement
* @return \Illuminate\Support\Collection
*/
public function splice($offset, $length = 0, $replacement = array())
{
array_splice($this->items, $offset, $length, $replacement);
}
/**
* Take the first or last {$limit} items.
*
* @param int $limit
* @return \Illuminate\Support\Collection
*/
public function take($limit = null)
{
if ($limit < 0) return $this->slice($limit, abs($limit));
return $this->slice(0, $limit);
}
/**
* Get an array with the values of a given key.
*
* @param string $value
* @param string $key
* @return array
*/
public function lists($value, $key = null)
{
$results = array();
foreach ($this->items as $item)
{
$itemValue = $this->getListValue($item, $value);
// If the key is "null", we will just append the value to the array and keep
// looping. Otherwise we will key the array using the value of the key we
// received from the developer. Then we'll return the final array form.
if (is_null($key))
{
$results[] = $itemValue;
}
else
{
$itemKey = $this->getListValue($item, $key);
$results[$itemKey] = $itemValue;
}
}
return $results;
}
/**
* Get the value of a list item object.
*
* @param mixed $item
* @param mixed $key
* @return mixed
*/
protected function getListValue($item, $key)
{
return is_object($item) ? $item->{$key} : $item[$key];
}
/**
* Concatenate values of a given key as a string.
*
* @param string $value
* @param string $glue
* @return string
*/
public function implode($value, $glue = null)
{
if (is_null($glue)) return implode($this->lists($value));
return implode($glue, $this->lists($value));
}
/**
* Determine if the collection is empty or not.
*
* @return bool
*/
public function isEmpty()
{
return empty($this->items);
}
/**
* Get the collection of items as a plain array.
*
* @return array
*/
public function toArray()
{
return array_map(function($value)
{
return $value instanceof ArrayableInterface ? $value->toArray() : $value;
}, $this->items);
}
/**
* Get the collection of items as JSON.
*
* @param int $options
* @return string
*/
public function toJson($options = 0)
{
return json_encode($this->toArray(), $options);
}
/**
* Get an iterator for the items.
*
* @return ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->items);
}
/**
* Count the number of items in the collection.
*
* @return int
*/
public function count()
{
return count($this->items);
}
/**
* Determine if an item exists at an offset.
*
* @param mixed $key
* @return bool
*/
public function offsetExists($key)
{
return array_key_exists($key, $this->items);
}
/**
* Get an item at a given offset.
*
* @param mixed $key
* @return mixed
*/
public function offsetGet($key)
{
return $this->items[$key];
}
/**
* Set the item at a given offset.
*
* @param mixed $key
* @param mixed $value
* @return void
*/
public function offsetSet($key, $value)
{
if (is_null($key))
{
$this->items[] = $value;
}
else
{
$this->items[$key] = $value;
}
}
/**
* Unset the item at a given offset.
*
* @param string $key
* @return void
*/
public function offsetUnset($key)
{
unset($this->items[$key]);
}
/**
* Convert the collection to its string representation.
*
* @return string
*/
public function __toString()
{
return $this->toJson();
}
}
namespace Menu;
trait HTMLElement {
public $element = 'div';
public $attributes = array();
public $content = '';
public function setElement($element)
{
$this->element = $element;
}
public function getElement()
{
return $this->element;
}
public function addClass($class)
{
$classes = $this->getClasses();
$classes[] = $class;
$this->setClasses($classes);
return $this;
}
public function setClasses($classes)
{
$this->attributes['class'] = implode(' ', $classes);
}
public function getClasses()
{
if( ! array_key_exists('class', $this->attributes))
{
return array();
}
return explode(' ', $this->attributes['class']);
}
public function setId($id)
{
$this->attributes['id'] = $id;
}
public function getId()
{
return $this->attributes['id'];
}
public function setAttribute($key, $value)
{
$this->attributes[$key] = $value;
}
public function setAttributes($attributes)
{
$this->attributes = $attributes;
}
public function getAttributes()
{
return $this->attributes;
}
public function setContent($content)
{
$this->content = $content;
}
public function getContent()
{
return $this->content;
}
public function render()
{
extract((array) $this);
$processedAttributes = array();
foreach($attributes as $key => $value)
{
$processedAttributes[] = $key.'="'.$value.'"';
}
$attributesString = count($processedAttributes) ? ' '.implode(' ', $processedAttributes) : '';
if( ! is_null($element))
{
$content = '<'.$element.$attributesString.'>'.$content.'</'.$element.'>';
}
return $content;
}
}
trait MenuElement {
public $activeClass;
public $activeChildClass;
public function setActiveClass($activeClass)
{
$this->activeClass = $activeClass;
}
public function setActiveChildClass($activeChildClass)
{
$this->activeChildClass = $activeChildClass;
}
}
interface Retrievable {
#public function getAllChildren();
}
class ItemList extends \Illuminate\Support\Collection implements Retrievable, Renderable {
use MenuElement;
use HTMLElement {
render as renderComponent;
}
public $items = array();
public $name;
public $parentItem;
public $by = array(
'name'
);
public function __construct($parentItem = null, $name = '', $items = array())
{
$this->parentItem = $parentItem;
$this->name = $name;
$this->items = $items;
$this->setElement('ul');
}
public function add($url, $label = '', $children = null, $interact = null)
{
$renderable = new LinkItem($url, $label);
return $this->addItem($renderable, $children, $interact);
}
public function addRaw($content = '', $children = null, $interact = null)
{
$renderable = new RawItem($content);
return $this->addItem($renderable, $children, $interact);
}
public function addCustom(Renderable $renderable, $children = null, $interact = null)
{
return $this->addItem($renderable, $children, $interact);
}
public function addItem($renderable, $children = null, $interact = null)
{
if(is_callable($children))
{
$itemList = new ItemList();
$children($itemList);
$children = $itemList;
}
$item = new Item($this, $renderable, $children);
if(is_callable($interact))
{
$interact($item);
}
$this->items[] = $item;
return $this;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
public function getName()
{
return $this->name;
}
public function setParentItem($item)
{
$this->parentItem = $item;
return $this;
}
public function getParentItem()
{
return $this->parentItem;
}
public function setItems($items)
{
if($items instanceof ItemList)
{
$items = $items->getItems();
}
$this->items = $items;
return $this;
}
public function getItems()
{
return $this->items;
}
public function getItemsWithDepth()
{
return $this->getItemsRecursivelyWithDepth($this->getItems());
}
protected function getItemsRecursivelyWithDepth($items, $depth = 1)
{
$results = array();
foreach($items as $item)
{
$results[$depth][] = $item;
foreach($this->getItemsRecursivelyWithDepth($item->getChildren(), $depth + 1) as $childDepth => $children)
{
foreach($children as $child)
{
$results[$childDepth][] = $child;
}
}
}
return $results;
}
public function getItemListsWithDepth()
{
return $this->getItemListsRecursivelyWithDepth($this);
}
protected function getItemListsRecursivelyWithDepth($itemList, $depth = 1)
{
$results = array();
$results[$depth][] = $itemList;
foreach($itemList as $item)
{
foreach($this->getItemListsRecursivelyWithDepth($item->getChildren(), $depth + 1) as $childDepth => $children)
{
foreach($children as $child)
{
$results[$childDepth][] = $child;
}
}
}
return $results;
}
public function getAllItems()
{
$results = array();
foreach($this->getItemsWithDepth() as $depth => $items)
{
foreach($items as $item)
{
$results[] = $item;
}
}
return new ItemList(null, '', $results);
}
public function getAllItemLists()
{
$results = array();
foreach($this->getItemListsWithDepth() as $depth => $items)
{
foreach($items as $item)
{
$results[] = $item;
}
}
return new Handler($results);
}
public function getItemListsAtDepth($depth)
{
$itemListsWithDepth = $this->getItemListsWithDepth();
return new Handler($itemListsWithDepth[$depth]);
}
public function getItemListsAtDepthRange($from, $to)
{
$itemListsWithDepth = $this->getItemListsWithDepth();
$results = array();
foreach($itemListsWithDepth as $depth => $itemLists)
{
if($depth >= $from && $depth <= $to)
{
foreach($itemLists as $itemList)
{
$results[] = $itemList;
}
}
}
return new Handler($results);
}
public function getItemsAtDepth($depth)
{
$itemsWithDepth = $this->getItemsWithDepth();
return new ItemList(null, '', $itemsWithDepth[$depth]);
}
public function getItemsAtDepthRange($from, $to)
{
$itemsWithDepth = $this->getItemsWithDepth();
$results = array();
foreach($itemsWithDepth as $depth => $items)
{
if($depth >= $from && $depth <= $to)
{
foreach($items as $item)
{
$results[] = $item;
}
}
}
return new ItemList(null, '', $results);
}
public function findByName()
{
return $this;
}
public function findByAttribute()
{
return $this;
}
public function findItemByUrl()
{
return $this;
}
public function findItemByName()
{
return $this;
}
public function findItemByAttribute()
{
return $this;
}
public function render()
{
$content = '';
if($this->items)
{
foreach($this->items as $item)
{
$content .= $item->render();
}
}
$this->setContent($content);
return $this->renderComponent();
}
}
interface Renderable {
public function render();
}
class Item implements Retrievable, Renderable {
use MenuElement;
use HTMLElement {
render as renderComponent;
}
public $renderable;
public $children;
public $parentItemList;
public $parentItem;
public $by = array(
'name'
);
public function __construct($parentItemList, Renderable $renderable, $children = null)
{
$this->parentItemList = $parentItemList;
$this->parentItem = $parentItemList->getParentItem();
$this->renderable = $renderable;
if(is_null($children))
{
$children = new ItemList($this);
}
$this->setChildren($children);
$this->setElement('li');
}
public function setChildren(ItemList $children)
{
$children->setParentItem($this);
$this->children = $children;
return $this;
}
public function getChildren()
{
return $this->children;
}
public function setParentItem($item)
{
$this->parentItem = $item;
return $this;
}
public function getParentItem()
{
return $this->parentItem;
}
public function setParentItemList($itemList)
{
$this->parentItemList = $itemList;
return $this;
}
public function getParentItemList()
{
return $this->parentItemList;
}
public function findByName($name)
{
$children = $this->getChildren();
$itemLists = $children->getAllItemLists();
foreach($itemLists as $itemList)
{
if($itemList->getName() == $name)
{
return $itemList;
}
}
return false;
}
// public function findByAttribute($attribute)
// {
// }
// public function findItemByUrl($url)
// {
// }
// public function findItemByName($name)
// {
// }
// public function findItemByAttribute($attribute)
// {
// }
public function render()
{
$content = $this->renderable->render();
if($this->children)
{
$content .= $this->children->render();
}
$this->setContent($content);
return $this->renderComponent();
}
}
class LinkItem implements Renderable {
use HTMLElement;
public function __construct($url, $label)
{
$this->setUrl($url);
$this->setLabel($label);
$this->setElement('a');
}
public function setUrl($url)
{
$this->setAttribute('href', $url);
}
public function setLabel($label)
{
$this->setContent($label);
}
}
class RawItem implements Renderable {
use HTMLElement;
public function __construct($content)
{
$this->content = $content;
$this->setElement(null);
}
}
class Handler {
public $menuObjects;
public $lastResults;
public $override = array(
'add',
'addRaw',
'addCustom',
'addItem',
'setName',
'setParentItem',
'setItems'
);
public function __construct($menuObjects)
{
$this->menuObjects = $menuObjects;
}
public function getMenuObjects()
{
return $this->menuObjects;
}
public function getItemsWithDepth()
{
$this->__call('getItemsWithDepth');
$results = array();
foreach ($this->lastResults as $result)
{
foreach($result as $depth => $items)
{
foreach($items as $item)
{
$results[$depth][] = $item;
}
}
}
return $results;
}
public function getItemListsWithDepth()
{
$this->__call('getItemListsWithDepth');
$results = array();
foreach ($this->lastResults as $result)
{
foreach($result as $depth => $items)
{
foreach($items as $item)
{
$results[$depth][] = $item;
}
}
}
return $results;
}
public function getAllItems()
{
$this->__call('getAllItems', array());
return new ItemList(null, '', $this->getItemsFromResults());
}
public function getAllItemLists()
{
$this->__call('getAllItemLists', array());
return new Handler($this->getMenuObjectsFromResults());
}
public function getItemsAtDepth($depth)
{
$this->__call('getItemsAtDepth', array($depth));
return new ItemList(null, '', $this->getItemsFromResults());
}
public function getItemsAtDepthRange($from, $to)
{
$this->__call('getItemsAtDepthRange', array($from, $to));
return new ItemList(null, '', $this->getItemsFromResults());
}
public function getItemListsAtDepth($depth)
{
$this->__call('getItemListsAtDepth', array($depth));
return new Handler($this->getMenuObjectsFromResults());
}
public function getItemListsAtDepthRange($from, $to)
{
$this->__call('getItemListsAtDepthRange', array($from, $to));
return new Handler($this->getMenuObjectsFromResults());
}
public function render()
{
$this->__call('render', array());
return implode('', $this->lastResults);
}
public function filter($callback)
{
$this->__call('filter', array($callback), false);
return new Handler($this->lastResults);
}
public function __call($method, $parameters = array())
{
$results = array();
foreach($this->menuObjects as &$menuObject)
{
$result = call_user_func_array(array($menuObject, $method), $parameters);
if(in_array($method, $this->override))
{
$menuObject = $result;
}
$results[] = $result;
}
$this->lastResults = $results;
return $this;
}
public function __toString()
{
return $this->render();
}
protected function getMenuObjectsFromResults()
{
$results = array();
foreach($this->lastResults as $result)
{
foreach($result->getMenuObjects() as $item)
{
$results[] = $item;
}
}
return $results;
}
protected function getItemsFromResults()
{
$results = array();
foreach($this->lastResults as $result)
{
foreach($result->getItems() as $item)
{
$results[] = $item;
}
}
return $results;
}
}
class Menu {
public static $itemLists = array();
public static function handler($names = '')
{
$names = (array) $names;
$itemLists = array();
foreach($names as $name)
{
if( ! array_key_exists($name, static::$itemLists))
{
static::$itemLists[$name] = new ItemList(null, $name);
}
$itemLists[] = static::$itemLists[$name];
}
return new Handler($itemLists);
}
public static function items($name = '', $children = null)
{
if(is_callable($children))
{
$interact = $children;
$children = null;
}
if($children instanceof ItemList)
{
$children = $children->getItems();
}
$itemList = new ItemList(null, $name, $children);
if(isset($interact))
{
$interact($itemList);
}
return $itemList;
}
public static function __callStatic($method, $parameters = array())
{
return call_user_func_array(array(static::handler(), $method), $parameters);
}
}
Menu::add('some', 'link', function($children)
{
$children->add('sub/1', 'linky1');
$children->add('sub/2', 'linky2', function($children)
{
$children->add('sub/2/1', 'linky2 - 1');
});
$children->add('sub/3', 'linky3');
$children->add('sub/4', 'linky4');
});
Menu::getItemListsAtDepthRange(2, 3)->map(function($itemList)
{
$itemList->setAttribute('style', 'color: orange; background: red');
});
echo Menu::render();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment