Last active
November 23, 2018 12:46
-
-
Save carloscarucce/1a26254d645a16fc223cc77655c3e863 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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