fhteam/laravel-validator

View on GitHub
src/Utility/ArrayDataStorage.php

Summary

Maintainability
B
4 hrs
Test Coverage
<?php

namespace FHTeam\LaravelValidator\Utility;

use ArrayAccess;
use ArrayIterator;
use Exception;
use Illuminate\Support\Str;
use IteratorAggregate;

/**
 * Class, incapsulating access to array-like data.
 * It provides convenient access to the underlying array.
 *
 * @package FHTeam\LaravelValidator\Utility
 */
class ArrayDataStorage implements ArrayAccess, IteratorAggregate
{
    const KEY_CASE_NO_CHANGE = 0;

    const KEY_CASE_SNAKE = 1;

    const KEY_CASE_STUDLY = 2;

    const KEY_CASE_CAMEL = 3;

    /**
     * @var array
     */
    protected $data = null;

    /**
     * @var callable
     */
    protected $keyCaseNormalizer;

    /**
     * @param int|callable $keyCase How to normalize keys when executing setXXXX methods. Either callable returning a
     *                              new key or one of the KEY_CASE_XXXXX constants
     *
     * @throws Exception
     */
    public function __construct($keyCase = self::KEY_CASE_NO_CHANGE)
    {
        $this->setKeyNormalizer($keyCase);
    }

    /**
     * Returns a single item
     *
     * @param $key
     *
     * @throws Exception
     * @return mixed
     */
    public function getItem($key)
    {
        $this->assertArrayDataExists();
        $this->assertKeyExists($key);

        return $this->data[$key];
    }

    /**
     * Checks if we have an item with the specified name
     *
     * @param $key
     *
     * @throws Exception
     * @return mixed
     */
    public function hasItem($key)
    {
        $this->assertArrayDataExists();

        return array_key_exists($key, $this->data);
    }

    /**
     * Returns a single item, if it is set or a default value, if it is not
     *
     * @param string     $key
     * @param null|mixed $default
     *
     * @return mixed
     * @throws Exception
     */
    public function getItemOrDefault($key, $default = null)
    {
        $this->assertArrayDataExists();

        return array_key_exists($key, $this->data) ? $this->data[$key] : $default;
    }

    /**
     * Returns all items
     *
     * @return array
     */
    public function getItems()
    {
        $this->assertArrayDataExists();

        return $this->data;
    }

    /**
     * @param $key
     * @param $value
     *
     * @return void
     */
    public function setItem($key, $value)
    {
        $key = $this->normalizeKey($key);
        $this->data[$key] = $value;
    }

    /**
     * @param $key
     *
     * @return void
     */
    public function unsetItem($key)
    {
        unset($this->data[$key]);
    }

    /**
     * @param array $data
     *
     * @return void
     */
    public function setItems(array $data)
    {
        $this->data = [];
        foreach ($data as $key => $value) {
            $this->setItem($key, $value);
        }
    }

    /**
     * Returns only specified items
     *
     * @param array $keys            Keys which to return from array
     * @param bool  $respectKeyOrder If you want the resulting array to be ordered
     *                               precisely as $keys. Especially useful in list() construct
     * @param bool  $requireAllKeys  Require all keys from $keys to be present in the result. Throw exception otherwise
     *
     * @return array
     * @throws Exception
     */
    public function getOnly(array $keys, $respectKeyOrder = false, $requireAllKeys = true)
    {
        $this->assertArrayDataExists();

        $result = $respectKeyOrder ? Arr::onlyRespectOrder($this->data, $keys) : Arr::only($this->data, $keys);
        if ($requireAllKeys) {
            $this->assertAllDataPresent($keys, $result);
        }

        return $result;
    }

    /**
     * Returns values of only specified items
     *
     * @param array $keys            Keys which values to return from array
     * @param bool  $respectKeyOrder If you want the resulting array to be ordered
     *                               precisely as $keys. Especially useful in list() construct
     * @param bool  $requireAllKeys  Require all keys from $keys to be present in the result. Throw exception otherwise
     *
     * @return array
     * @throws Exception
     */
    public function getOnlyValues(array $keys, $respectKeyOrder = false, $requireAllKeys = true)
    {
        return array_values($this->getOnly($keys, $respectKeyOrder, $requireAllKeys));
    }

    /**
     * Returns all items except for the specified
     *
     * @param array $keys
     *
     * @return mixed
     */
    public function getExcept(array $keys)
    {
        $this->assertArrayDataExists();
        $result = Arr::except($this->data, $keys);

        return $result;
    }

    /**
     * IteratorAggregate implementation
     *
     * @return ArrayIterator
     */
    public function getIterator()
    {
        return new ArrayIterator($this->data);
    }

    /**
     * ArrayAccess implementation
     *
     * @param mixed $offset
     *
     * @return bool
     */
    public function offsetExists($offset)
    {
        return $this->hasItem($offset);
    }

    /**
     * ArrayAccess implementation
     *
     * @param mixed $offset
     *
     * @return mixed
     */
    public function offsetGet($offset)
    {
        return $this->getItem($offset);
    }

    /**
     * ArrayAccess implementation
     *
     * @param mixed $offset
     * @param mixed $value
     *
     * @throws Exception
     * @return void
     */
    public function offsetSet($offset, $value)
    {
        $this->setItem($offset, $value);
    }

    /**
     * ArrayAccess implementation
     *
     * @param mixed $offset
     *
     * @throws Exception
     * @return void
     */
    public function offsetUnset($offset)
    {
        $this->unsetItem($offset);
    }

    /**
     * For testing if validated properties are set
     *
     * @param string $name
     *
     * @return bool
     */
    public function __isset($name)
    {
        $this->assertArrayDataExists();

        return isset($this->data[$name]);
    }

    /**
     * For unsetting properties
     *
     * @param string $name
     *
     * @return void
     */
    public function __unset($name)
    {
        $this->unsetItem($name);
    }

    /**
     * @param string $name
     *
     * @return bool
     */
    public function __get($name)
    {
        return $this->getItem($name);
    }

    /**
     * @param string $name
     * @param mixed  $value
     */
    public function __set($name, $value)
    {
        $this->setItem($name, $value);
    }

    /**
     * Tests if all the given keys are present in the result
     *
     * @param array $keys
     * @param       $result
     *
     * @throws Exception
     */
    protected function assertAllDataPresent(array $keys, array $result)
    {
        if (count($result) !== count($keys)) {
            throw new Exception(
                "Cannot find the following data required: ".
                json_encode(array_diff_key($keys, $this->data))
            );
        }
    }

    /**
     * Asserts data exists at all
     *
     * @throws Exception
     */
    protected function assertArrayDataExists()
    {
        if (null === $this->data) {
            throw new Exception("Attempt to read non-existent data");
        }
    }

    /**
     * Asserts data exists at all
     *
     * @param string $key
     *
     * @throws Exception
     */
    protected function assertKeyExists($key)
    {
        if (!array_key_exists($key, $this->data)) {
            throw new Exception("Key '$key' does not exist. I have only ".json_encode($this->data));
        }
    }

    /**
     * Normalizes key name to access the data
     *
     * @param $key
     *
     * @return string
     */
    protected function normalizeKey($key)
    {
        $keyNormalizer = $this->keyCaseNormalizer;

        return $keyNormalizer($key);
    }

    /**
     * Key normalizer option for normalizeKey()
     *
     * @param string $key
     *
     * @return mixed
     */
    protected function keyCaseNoChange($key)
    {
        return $key;
    }

    /**
     * Key normalizer option for normalizeKey()
     *
     * @param string $key
     *
     * @return string
     */
    protected function keyCaseSnake($key)
    {
        return Str::snake($key);
    }

    /**
     * Key normalizer option for normalizeKey()
     *
     * @param string $key
     *
     * @return string
     */
    protected function keyCaseStudly($key)
    {
        return Str::studly($key);
    }

    /**
     * Key normalizer option for normalizeKey()
     *
     * @param string $key
     *
     * @return string
     */
    protected function keyCaseCamel($key)
    {
        return Str::camel($key);
    }

    /**
     * @param $keyCase
     *
     * @throws Exception
     */
    public function setKeyNormalizer($keyCase)
    {
        if (null !== $this->data) {
            throw new Exception("Cannot change key normalizer if data was already filled in");
        }

        if (is_callable($keyCase)) {
            $this->keyCaseNormalizer = $keyCase;
        } else {
            switch ($keyCase) {
                case self::KEY_CASE_NO_CHANGE:
                    $this->keyCaseNormalizer = [$this, 'keyCaseNoChange'];
                    break;
                case self::KEY_CASE_SNAKE:
                    $this->keyCaseNormalizer = [$this, 'keyCaseSnake'];
                    break;
                case self::KEY_CASE_STUDLY:
                    $this->keyCaseNormalizer = [$this, 'keyCaseStudly'];
                    break;
                case self::KEY_CASE_CAMEL:
                    $this->keyCaseNormalizer = [$this, 'keyCaseCamel'];
                    break;
                default:
                    throw new Exception("Unsupported key case option '$keyCase'");
            }
        }
    }
}