Skip to content

Instantly share code, notes, and snippets.

@carloscarucce
Last active November 23, 2018 12:46
Show Gist options
  • Save carloscarucce/1a26254d645a16fc223cc77655c3e863 to your computer and use it in GitHub Desktop.
Save carloscarucce/1a26254d645a16fc223cc77655c3e863 to your computer and use it in GitHub Desktop.
<?php
/**
* Created by PhpStorm.
* User: Carlos Alberto Bertholdo Carucce
* Date: 16/11/2018
* Time: 10:02
*/
class Collection implements \ArrayAccess, \JsonSerializable, \Countable, \Iterator
{
/**
* @var array
*/
private $items;
/**
* @var string
*/
private $lastErrorMessage = null;
/**
* Adds an item.
*
* @param $item
* @param string|int|null $index
*
* @return bool
*/
public function add($item, $index = null)
{
if (is_null($index)) {
$index = '*';
}
$indexes = explode('.', $index);
$currentIndex = array_shift($indexes);
if (
$currentIndex != '*'
&& isset($this->items[$currentIndex])
&& !($this->items[$currentIndex] instanceof Collection)
) {
$this->lastErrorMessage = $currentIndex.' is not a Collection';
return false;
}
if (!empty($indexes)) {
$subCollection = null;
if (
$currentIndex != '*'
&& isset($this->items[$currentIndex])
&& $this->items[$currentIndex] instanceof Collection
) {
$subCollection = $this->items[$currentIndex];
} else {
$subCollection = new static();
}
$subCollection->add($item, implode('.', $indexes));
$item = $subCollection;
}
if ($currentIndex == '*') {
$this->items[] = $item;
} else {
$this->items[$currentIndex] = $item;
}
if (is_array($item)) {
$this->arrayToCollection();
}
return true;
}
/**
* Empty collection.
*
* @return void
*/
public function clear()
{
$this->items = [];
}
/**
* Creates an exact copy from this collection.
*
* @return static
*/
public function copy()
{
return new static($this->items);
}
/**
* Cycles through items in the current collection.
* $function must receive two arguments:
* $value (mixed) - Will contain the current item in the iteration.
* $key - Will contain the current item key.
*
* @param callable $function
*/
public function each(callable $function)
{
foreach ($this->items as $key => &$value) {
$function($value, $key);
}
}
/**
* Returns the value stored in $index.
*
* @param string $index
*
* @return mixed
*/
public function get($index)
{
if (!$this->hasIndex($index)) {
$this->lastErrorMessage = 'Index '.$index.' is not set';
return null;
}
$indexes = explode('.', $index);
$current = array_shift($indexes);
if (empty($indexes)) {
return $this->items[$current];
} else {
return $this->items[$current]->get(implode('.', $indexes));
}
}
/**
* Gets the index from $item. Returns null when it doesn't exists.
*
* @param $item
*
* @return string|null
*/
public function getIndex($item)
{
//Search for the item in the current level
$index = array_search($item, $this->items, true);
if ($index !== false) {
return (string) $index;
}
//Attempt to find it in child collections
$found = null;
foreach ($this->items as $key => $value) {
if ($value instanceof Collection) {
$index = $value->getIndex($item);
if (!is_null($index)) {
$found = "$key.$index";
}
}
}
return $found;
}
/**
* @return string|null
*/
public function getLastErrorMessage()
{
$msg = $this->lastErrorMessage;
$this->lastErrorMessage = null;
return $msg;
}
/**
* Checks if $item item exists in the current collection.
*
* @param $item
*
* @return bool
*/
public function has($item)
{
return !is_null($this->getIndex($item));
}
/**
* Checks if $index was set.
*
* @param $index
*
* @return bool
*/
public function hasIndex($index)
{
$hasIndex = false;
$indexes = explode('.', $index);
$current = array_shift($indexes);
if (empty($indexes)) {
$hasIndex = isset($this->items[$current]);
} elseif (isset($this->items[$current]) && $this->items[$current] instanceof Collection) {
$hasIndex = $this->items[$current]->hasIndex(implode('.', $indexes));
}
return $hasIndex;
}
/**
* Removes an item from the Collection.
*
* @param mixed $item
*
* @return bool
*/
public function remove($item)
{
$index = $this->getIndex($item);
return $this->removeIndex($index);
}
/**
* Removes the an index from the Collection.
*
* @param $index
*
* @return bool
*/
public function removeIndex($index)
{
if (!$this->hasIndex($index)) {
$this->lastErrorMessage = "Could not remove item: index not found";
return false;
}
$indexes = explode('.', $index);
$current = array_shift($indexes);
if (empty($indexes)) {
unset($this->items[$current]);
return true;
} else {
return $this->items[$current]->removeIndex(implode('.', $indexes));
}
}
/**
* Return true when the collection is empty.
*
* @return bool
*/
public function isEmpty()
{
return empty($this->items);
}
/**
* @param array $items
*/
public function setItems(array $items)
{
$this->items = $items;
$this->arrayToCollection();
}
/**
* Sorts items in the current collection.
* Based on usort. Please check documentation: http://php.net/manual/en/function.usort.php
* $function callable notes: The comparison function must return an integer less than,
* equal to, or greater than zero if the first argument is considered to be respectively less than,
* equal to, or greater than the second.
*
* @param callable $function
*
* @return bool
*/
public function sort(callable $function)
{
return usort($this->items, $function);
}
/**
* Extracts the current collection into an array.
*
* @return array
*/
public function toArray()
{
$arr = $this->items;
array_walk($arr, function(&$value) {
if ($value instanceof Collection) {
$value = $value->toArray();
}
});
return $arr;
}
/**
* Whether a offset exists
* @link https://php.net/manual/en/arrayaccess.offsetexists.php
* @param mixed $offset <p>
* An offset to check for.
* </p>
* @return boolean true on success or false on failure.
* </p>
* <p>
* The return value will be casted to boolean if non-boolean was returned.
* @since 5.0.0
*/
public function offsetExists($offset)
{
return isset($this->items[$offset]);
}
/**
* Offset to retrieve
* @link https://php.net/manual/en/arrayaccess.offsetget.php
* @param mixed $offset <p>
* The offset to retrieve.
* </p>
* @return mixed Can return all value types.
* @since 5.0.0
*/
public function offsetGet($offset)
{
return $this->offsetExists($offset) ? $this->items[$offset] : null;
}
/**
* Offset to set
* @link https://php.net/manual/en/arrayaccess.offsetset.php
* @param mixed $offset <p>
* The offset to assign the value to.
* </p>
* @param mixed $value <p>
* The value to set.
* </p>
* @return void
* @since 5.0.0
*/
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->items[] = $value;
} else {
$this->items[$offset] = $value;
}
if (is_array($value)) {
$this->arrayToCollection();
}
}
/**
* Offset to unset
* @link https://php.net/manual/en/arrayaccess.offsetunset.php
* @param mixed $offset <p>
* The offset to unset.
* </p>
* @return void
* @since 5.0.0
*/
public function offsetUnset($offset)
{
if ($this->offsetExists($offset)) {
unset($this->items[$offset]);
}
}
/**
* Count elements of an object
* @link https://php.net/manual/en/countable.count.php
* @return int The custom count as an integer.
* </p>
* <p>
* The return value is cast to an integer.
* @since 5.1.0
*/
public function count()
{
return count($this->items);
}
/**
* Specify data which should be serialized to JSON.
*
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
*
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
*
* @since 5.4.0
*/
public function jsonSerialize()
{
return $this->toArray();
}
/**
* Collection constructor.
*
* @param array|null $items
*/
public function __construct(array $items = null)
{
$this->setItems(is_null($items) ? [] : $items);
}
/**
* @param $name
* @return mixed
*/
public function &__get($name)
{
$value = null;
if(isset($this->items[$name])) {
$value = $this->items[$name];
}
return $value;
}
/**
* @param $name
* @return mixed
*/
public function __isset($name)
{
return isset($this->items[$name]);
}
/**
* @param $name
* @param $value
* @return mixed
*/
public function __set($name, $value)
{
$this->items[$name] = $value;
if (is_array($value)) {
$this->arrayToCollection();
}
}
/**
* @param $name
*/
public function __unset($name)
{
unset($this->items[$name]);
}
/**
* Return the current element
* @link https://php.net/manual/en/iterator.current.php
* @return mixed Can return any type.
* @since 5.0.0
*/
public function current()
{
return current($this->items);
}
/**
* Move forward to next element
* @link https://php.net/manual/en/iterator.next.php
* @return void Any returned value is ignored.
* @since 5.0.0
*/
public function next()
{
next($this->items);
}
/**
* Return the key of the current element
* @link https://php.net/manual/en/iterator.key.php
* @return mixed scalar on success, or null on failure.
* @since 5.0.0
*/
public function key()
{
return key($this->items);
}
/**
* Checks if current position is valid
* @link https://php.net/manual/en/iterator.valid.php
* @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
* @since 5.0.0
*/
public function valid()
{
return key($this->items) !== null;
}
/**
* Rewind the Iterator to the first element
* @link https://php.net/manual/en/iterator.rewind.php
* @return void Any returned value is ignored.
* @since 5.0.0
*/
public function rewind()
{
reset($this->items);
}
/**
* Converts all array items to collections.
*/
private function arrayToCollection()
{
foreach ($this->items as &$value) {
if (is_array($value)) {
$value = new static($value);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment