src/StrongTypes/Collection.php
<?php
/**
* Copyright (c) 2014-2016 Ryan Parman.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* http://opensource.org/licenses/MIT
*/
namespace Skyzyx\StrongTypes;
use \ArrayAccess;
use \ArrayObject;
use \Countable;
use \DomainException;
use \IteratorAggregate;
use \InvalidArgumentException;
use \UnexpectedValueException;
class Collection extends AbstractShape implements CollectionInterface, IteratorAggregate, ArrayAccess, Countable, MultiValueInterface
{
use Collection\AliasTrait;
use Collection\ArrayAccessTrait;
use Collection\CountableTrait;
use Collection\IteratorAggregateTrait;
use Collection\SeekableInterfaceTrait;
/** @var \ArrayObject */
private $collection;
/** @var array */
private $required_keys = [];
/**************************************************************************/
// CONSTRUCTOR
/**
* Constructs a new instance of this class.
*
* @param array $value The standard array to convert into a collection. The default value is an empty string.
*/
public function __construct($value = [])
{
$this->value = $value;
$this->validate();
$this->collection = new ArrayObject($value, ArrayObject::ARRAY_AS_PROPS);
$this->iterator = $this->collection->getIterator();
}
/**************************************************************************/
// ShapeInterface
/**
* {@inheritdoc}
*/
public function validate()
{
if ($this->value !== null && !is_array($this->value)) {
throw new UnexpectedValueException(
sprintf(self::TYPE_EXCEPTION_MESSAGE, get_called_class(), 'array', gettype($this->value))
);
}
$map = $this->validateValue();
$this->storeRequiredKeys($this->requiredKeys());
if (is_array($this->value)) {
foreach ($this->value as $k => $v) {
// If the key is defined...
if (isset($map[$k])) {
// ...validate its shape.
if (!($v instanceof $map[$k] || gettype($v) === $this->getNativeType($map[$k]))) {
throw new InvalidArgumentException(
sprintf('The %s shape expects the %s key to be of type %s.',
get_called_class(), $k, get_class($map[$k]))
);
}
}
if ($this->isRequiredKey($k)) {
$this->pluckFromRequiredKeys($k);
}
}
}
if (count($this->required_keys) !== 0) {
throw new DomainException(
sprintf('The %s collection is missing one or more required keys: %s.',
get_called_class(),
implode(', ', $this->getRemainingRequiredKeys())
)
);
}
}
/**
* {@inheritdoc}
*/
public function getValue()
{
return $this->value;
}
/**
* The expected keys and data types of this shape.
*
* @return array The expected keys and data types of this shape.
*/
public function validateValue()
{
/** @var array */
return [];
}
/**
* {@inheritdoc}
*/
public function requiredKeys()
{
return [];
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return json_encode($this->value);
}
/**
* Checks to see whether or not this is an indexed array (e.g., list).
*
* @return boolean Whether or not this Collection is a List. A value of `true` means that the Collection is an
* indexed array. A value of `false` means that the Collection is an associative array.
*/
public function isList()
{
foreach ($this->value as $a => $b) {
if ($a !== (integer) $a) {
/** @var boolean */
return false;
}
}
/** @var boolean */
return true;
}
/**
* Checks to see whether or not this is an associative array (e.g., hash, dictionary).
*
* @return boolean Whether or not this Collection is a Hash. A value of `true` means that the Collection is an
* associative array. A value of `false` means that the Collection is an indexed array.
*/
public function isMap()
{
foreach ($this->value as $a => $b) {
if ($a !== (string) $a) {
/** @var boolean */
return false;
}
}
/** @var boolean */
return true;
}
/**************************************************************************/
// Helper Methods
/**
* Stores the list of required keys for processing.
*
* @param array $keys The response from a call to `requiredKeys()`.
* @return void
*/
protected function storeRequiredKeys(array $keys)
{
$this->required_keys = array_flip($keys);
}
/**
* Checks to see if a key is required.
*
* @param string $key The key to remove from the list of required keys.
* @return boolean Whether or not a key is required. A value of `true` means that the key is required.
* A value of `false` means that the key is NOT required.
*/
protected function isRequiredKey($key)
{
return isset($this->required_keys[$key]);
}
/**
* Plucks a key from the list of required keys, and returns the list of remaining keys.
*
* @param string $key The key to remove from the list of required keys.
* @return void
*/
protected function pluckFromRequiredKeys($key)
{
if (isset($this->required_keys[$key]) === true) {
unset($this->required_keys[$key]);
}
}
/**
* Gets the required keys that have not yet been set.
*
* @return array<string> The required keys that have not yet been set.
*/
protected function getRemainingRequiredKeys()
{
return array_flip($this->required_keys);
}
}