Skip to content

Instantly share code, notes, and snippets.

@Petah
Last active July 15, 2019 00:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Petah/d90d7258d10e7334dbff to your computer and use it in GitHub Desktop.
Save Petah/d90d7258d10e7334dbff to your computer and use it in GitHub Desktop.
<?php
use Ramsey\Uuid\Uuid;
/**
* Helper class for dealing with input data (e.g. data sent over HTTP, API responses,
* deserialized data from storage etc). When accessing items this class automatically
* checks that it exists and casts the value to the expected type. If it does not exist a default value is returned.
*/
class InputData implements \ArrayAccess, \Countable, \IteratorAggregate, \JsonSerializable
{
/**
* @var mixed[]|null|string|float|int
*/
private $_data;
/**
* @param mixed[]|null|string|float|int $_data Input data
*/
public function __construct($_data)
{
$this->_data = $_data;
}
public static function jsonDecode($value)
{
try {
return new static(Json::decode($value));
} catch (\Exception $exception) {
return new static([]);
}
}
/**
* Cast to boolean.
*
* @param string|null $name The name/key of input item
* @param bool|null $default The default value if the item doesn't exist
*/
public function bool(?string $name = null, ?bool $default = false): ?bool
{
if (!$name) {
if (is_array($this->_data)) {
return $default;
}
return (bool) $this->_data;
}
[$data, $name] = $this->extractDataKey($name, $this->_data);
$result = static::getValue($data, $name, $default);
if (is_scalar($result)) {
return (bool) $result;
}
return $default;
}
/**
* Cast to an integer.
*
* @param string|null $name The name/key of input item
* @param int|null $default The default value if the item doesn't exist
*/
public function int(?string $name = null, ?int $default = 0): ?int
{
if (!$name) {
if (is_array($this->_data)) {
return $default;
}
return (int) $this->_data;
}
[$data, $name] = $this->extractDataKey($name, $this->_data);
$result = static::getValue($data, $name, $default);
if (is_numeric($result)) {
return (int) $result;
}
return $default;
}
/**
* Cast to a float.
*
* @param string|null $name The name/key of input item
* @param float|null $default The default value if the item doesn't exist
*/
public function decimal(?string $name = null, ?float $default = 0): ?float
{
if (!$name) {
if (is_array($this->_data)) {
return $default;
}
return (float) $this->_data;
}
[$data, $name] = $this->extractDataKey($name, $this->_data);
$result = static::getValue($data, $name, $default);
if (is_numeric($result)) {
return (float) $result;
}
return $default;
}
/**
* Cast to an string.
*
* @param string|null $name The name/key of input item
* @param string|null $default The default value if the item doesn't exist
*/
public function string(?string $name = null, ?string $default = ''): ?string
{
if (!$name) {
if (is_array($this->_data)) {
return $default;
}
return (string) $this->_data;
}
[$data, $name] = $this->extractDataKey($name, $this->_data);
$result = static::getValue($data, $name, $default);
if (is_scalar($result)) {
return (string) $result;
}
return $default;
}
public function html(?string $name = null, ?string $default = ''): ?string
{
$value = $this->string($name, $default);
if ($value === $default) {
return $default;
}
return cleanHtml($value);
}
public function uuid(?string $name = null, $default = null): ?string
{
$string = $this->string($name, $default);
if ($string === $default || !$string || !uuidValid($string)) {
return $default;
}
return uuidBytes($string);
}
public function uuidString(?string $name = null, $default = null): ?string
{
$string = $this->string($name, $default);
if ($string === $default || !$string || !uuidValid($string)) {
return $default;
}
return $string;
}
public function token(?string $name = null, $default = null): ?InputData
{
$string = $this->string($name, $default);
if ($string === $default || !$string) {
return $default;
}
return Token::decode($string);
}
/**
* Parse a DateTime.
*
* @param string|null $name The name/key of input item
* @param string|null $timezone The timezone to use for the result, if null the default or input is used
* @param string|null $default The default value if the item doesn't exist or is invalid
*/
public function dateTime(?string $name = null, ?string $timezone = null, ?string $default = 'now'): ?\DateTimeImmutable
{
[$data, $name] = $this->extractDataKey($name, $this->_data);
if ($default === null && !static::getValue($data, $name, $default)) {
return null;
}
try {
if ($timezone) {
return new \DateTimeImmutable(static::getValue($data, $name, $default) ?: $default, new \DateTimeZone($timezone));
} else {
return new \DateTimeImmutable(static::getValue($data, $name, $default) ?: $default, new \DateTimeZone('UTC'));
}
} catch (\Exception $exception) {
if ($default === null) {
return null;
}
if ($timezone) {
return new \DateTimeImmutable($default, new \DateTimeZone($timezone));
} else {
return new \DateTimeImmutable($default, new \DateTimeZone('UTC'));
}
}
}
public function date(?string $name = null, ?string $timezone = null, ?string $default = 'now'): ?\DateTimeImmutable
{
return $this->dateTime($name, $timezone, $default);
}
/**
* Returns a subarray of input data.
*
* @param string|null $name The name/key of input subarray
* @param array $default The default value if the item doesn't exist or is not an array
*/
public function arr(?string $name = null, array $default = []): InputData
{
if (!$name) {
if (!is_array($this->_data)) {
return new static($default);
}
return new static($this->_data);
}
[$data, $name] = $this->extractDataKey($name, $this->_data);
return new static(static::getValue($data, $name, $default));
}
/**
* JSON decode a value from the input data.
*
* @param string|null $name The name/key of input item
* @param mixed $default The default value if the item doesn't exist
*/
public function json(?string $name = null, $default = []): InputData
{
$string = $this->string($name);
if (!$string) {
return new static($default);
}
$value = json_decode($string);
if (json_last_error() !== JSON_ERROR_NONE) {
$value = $default;
}
return new static($value);
}
/**
* Returns a sub-object of input data.
*
* @param string|null $name The name/key of input item
* @param mixed[] $default The default value if the item doesn't exist
*/
public function object($name, $default = null): InputData
{
if (!$name) {
if (!is_array($this->_data)) {
return new static($default);
}
return new static($this->_data);
}
[$data, $name] = $this->extractDataKey($name, $this->_data);
return new static(static::getValue($data, $name, $default));
}
/**
* Gets the raw value (unwraps the class) of data.
*
* @param string|null $name The name/key of input item
* @param mixed $default The default value if the item doesn't exist
*
* @return mixed
*/
public function raw($name, $default = null)
{
[$data, $name] = $this->extractDataKey($name, $this->_data);
return static::getValue($data, $name, $default);
}
/**
* Converts dot delimited notations to access sub items.
*
* @param string|null $name The name/key of input item
* @param mixed[]|null|string|float|int $data The default value if the item doesn't exist
*
* @return mixed[]|null|string|float|int
*/
public static function extractDataKey(?string $name, $data)
{
$parts = explode('.', $name ?: '');
while (count($parts) > 1) {
$part = array_shift($parts);
if (is_array($data)) {
$data = isset($data[$part]) ? $data[$part] : [];
} elseif (is_object($data)) {
$data = isset($data->$part) ? $data->$part : [];
} else {
$data = [];
}
$name = $parts[0];
}
return [
$data,
$name,
];
}
/**
* @return InputData
*/
public function extract($data)
{
$newData = [];
foreach ($data as $key => $type) {
$newData[$key] = $this->$type($key);
}
return new static($newData);
}
/**
* @return InputData
*/
public function extend(array $data)
{
$newData = [];
foreach ($this->_data as $key => $type) {
$newData[$key] = $this->_data[$key];
}
$newData = array_replace_recursive($newData, $data);
return new static($newData);
}
/**
* @return bool True if the input data is empty
*/
public function isEmpty()
{
return empty($this->_data);
}
/**
* @return bool True if the input data is an array
*/
public function isArray(): bool
{
return is_array($this->_data);
}
/**
* @return mixed[]|null|string|float|int
*/
public function getData()
{
if (is_scalar($this->_data) || $this->_data === null) {
return $this->_data;
}
array_walk_recursive($this->_data, function (&$value) {
if ($value instanceof static ) {
$value = $value->_data;
}
});
return $this->_data;
}
public function map($callback): InputData
{
$result = [];
foreach ($this as $key => $value) {
$result[$key->_data] = $callback($value, $key);
}
return new static($result);
}
public static function getValue($data, $name, $default)
{
if (is_array($data)) {
if (!array_key_exists($name, $data)) {
return $default;
}
return $data[$name];
}
if (is_object($data)) {
if (!isset($data->$name)) {
return $default;
}
return $data->$name;
}
return $default;
}
public function exists($name)
{
[$data, $name] = $this->extractDataKey($name, $this->_data);
if (is_array($data)) {
return array_key_exists($name, $data);
}
if (is_object($data)) {
return property_exists($data, $name);
}
return false;
}
public function get($name): InputData
{
if (is_array($this->_data)) {
return isset($this->_data[$name]) ? new static($this->_data[$name]) : new static(null);
}
return isset($this->_data->$name) ? new static($this->_data->$name) : new static(null);
}
public function set($name, $value): InputData
{
if ($value instanceof static ) {
$value = $value->_data;
}
if (!isset($this->_data[$name]) || !$this->_data[$name]) {
$this->_data[$name] = [];
}
if (is_array($this->_data)) {
$this->_data[$name] = $value;
} else {
$this->_data->$name = $value;
}
return $this;
}
function unset($name): InputData {
if (is_array($this->_data)) {
unset($this->_data[$name]);
} else {
unset($this->_data->$name);
}
return $this;
}
function isset($name): bool {
if (is_array($this->_data)) {
return isset($this->_data[$name]);
}
return isset($this->_data->$name);
}
public function __toString()
{
return $this->string();
}
public function getIterator()
{
if (is_array($this->_data) || is_object($this->_data)) {
foreach ($this->_data as $key => $value) {
yield new static($key) => new static($value);
}
}
}
public function offsetExists($offset)
{
return $this->isset($offset);
}
public function offsetGet($offset)
{
return $this->get($offset);
}
public function offsetSet($offset, $value)
{
$this->$offset = $value;
}
public function offsetUnset($offset)
{
return isset($this->$offset);
}
public function count()
{
if (!$this->_data) {
return 0;
}
if (is_array($this->_data) || $this->_data instanceof \Countable) {
return count($this->_data);
}
return 0;
}
public function jsonSerialize()
{
return $this->getData();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment