mirror of
https://gh.wpcy.net/https://github.com/rilustrisimo/freescout-support.git
synced 2026-04-26 14:02:20 +08:00
812 lines
20 KiB
PHP
812 lines
20 KiB
PHP
<?php declare(strict_types=1);
|
|
/**
|
|
* Part of Windwalker project.
|
|
*
|
|
* @copyright Copyright (C) 2019 LYRASOFT.
|
|
* @license LGPL-2.0-or-later
|
|
*/
|
|
|
|
namespace Windwalker\Structure;
|
|
|
|
/**
|
|
* Structure class
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
class Structure implements \JsonSerializable, \ArrayAccess, \IteratorAggregate, \Countable
|
|
{
|
|
/**
|
|
* Property separator.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $separator = '.';
|
|
|
|
/**
|
|
* Structure data store.
|
|
*
|
|
* @var array
|
|
* @since 2.0
|
|
*/
|
|
protected $data = [];
|
|
|
|
/**
|
|
* Property ignoreValues.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $ignoreValues = [null];
|
|
|
|
/**
|
|
* Create Value Reference.
|
|
*
|
|
* @param string $path
|
|
* @param string|null $separator
|
|
*
|
|
* @return ValueReference
|
|
*
|
|
* @since 3.5.1
|
|
*/
|
|
public static function ref(string $path, string $separator = null): ValueReference
|
|
{
|
|
return new ValueReference($path, $separator);
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param mixed $data The data to bind to the new Structure object.
|
|
* @param string $format The format of input, only work when first argument is string.
|
|
* @param array $options The load options.
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function __construct($data = null, $format = Format::JSON, array $options = [])
|
|
{
|
|
$raw = $options['load_raw'] ?? false;
|
|
|
|
// Optionally load supplied data.
|
|
if (\is_array($data) || \is_object($data)) {
|
|
$this->bindData($this->data, $data, $raw, $options);
|
|
} elseif (!empty($data) && \is_string($data)) {
|
|
if (\strlen($data) < PHP_MAXPATHLEN && is_file($data)) {
|
|
$this->loadFile($data, $format, $options);
|
|
} else {
|
|
$this->loadString($data, $format, $options);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Magic function to clone the structure object.
|
|
*
|
|
* @return Structure
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function __clone()
|
|
{
|
|
$this->data = unserialize(serialize($this->data));
|
|
}
|
|
|
|
/**
|
|
* Magic function to render this object as a string using default args of toString method.
|
|
*
|
|
* @return string
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function __toString()
|
|
{
|
|
try {
|
|
return $this->toString();
|
|
} catch (\Exception $e) {
|
|
trigger_error((string) $e, E_USER_ERROR);
|
|
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implementation for the JsonSerializable interface.
|
|
* Allows us to pass Structure objects to json_encode.
|
|
*
|
|
* @return array
|
|
*
|
|
* @since 2.0
|
|
* : mixed
|
|
*/
|
|
#[\ReturnTypeWillChange]
|
|
public function jsonSerialize()
|
|
{
|
|
return $this->data;
|
|
}
|
|
|
|
/**
|
|
* Sets a default value if not already assigned.
|
|
*
|
|
* @param string $path The name of the parameter.
|
|
* @param mixed $value An optional value for the parameter.
|
|
*
|
|
* @return static Return self to support chaining.
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function def($path, $value = '')
|
|
{
|
|
$value = $this->get($path, $value);
|
|
$this->set($path, $value);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Check if a structure path exists.
|
|
*
|
|
* @param string $path Structure path (e.g. foo.content.showauthor)
|
|
*
|
|
* @return boolean
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function exists($path)
|
|
{
|
|
return null !== $this->get($path);
|
|
}
|
|
|
|
/**
|
|
* Get a structure value.
|
|
*
|
|
* @param string $path Structure path (e.g. foo.content.showauthor)
|
|
* @param mixed $default Optional default value, returned if the internal value is null.
|
|
* @param string $separator Force separate character.
|
|
*
|
|
* @return mixed Value of entry or null
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function get($path, $default = null, string $separator = null)
|
|
{
|
|
$result = StructureHelper::getByPath($this->data, $path, $separator ?: $this->separator);
|
|
|
|
return $result ?? $default;
|
|
}
|
|
|
|
/**
|
|
* remove
|
|
*
|
|
* @param string $path
|
|
*
|
|
* @return static
|
|
*/
|
|
public function remove($path)
|
|
{
|
|
StructureHelper::removeByPath($this->data, $path, $this->separator);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Reset all data.
|
|
*
|
|
* @return static
|
|
*/
|
|
public function reset()
|
|
{
|
|
$this->data = [];
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Load an array or object of values into the default namespace
|
|
*
|
|
* @param array|object $data The value to load into structure.
|
|
* @param boolean $raw Set to false that we will convert all object to array.
|
|
* @param array $options The options to bind data.
|
|
*
|
|
* @return static Return this object to support chaining.
|
|
*/
|
|
public function load($data, $raw = false, array $options = [])
|
|
{
|
|
$this->bindData($this->data, $data, $raw, $options);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Load the contents of a file into the structure
|
|
*
|
|
* @param string $file Path to file to load
|
|
* @param string $format Format of the file [optional: defaults to JSON]
|
|
* @param array $options Options used by the formatter
|
|
*
|
|
* @return static Return this object to support chaining.
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function loadFile($file, $format = Format::JSON, $options = [])
|
|
{
|
|
$raw = isset($options['load_raw']) ? $options['load_raw'] : false;
|
|
|
|
$this->load(StructureHelper::loadFile($file, $format, $options), $raw, $options);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Load a string into the structure
|
|
*
|
|
* @param string $data String to load into the structure
|
|
* @param string $format Format of the string
|
|
* @param array $options Options used by the formatter
|
|
*
|
|
* @return static Return this object to support chaining.
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function loadString($data, $format = Format::JSON, $options = [])
|
|
{
|
|
$raw = isset($options['load_raw']) ? $options['load_raw'] : false;
|
|
|
|
$this->load(StructureHelper::loadString($data, $format, $options), $raw, $options);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Merge a structure data into this object.
|
|
*
|
|
* @param Structure|mixed $source Source structure data to merge.
|
|
* @param boolean $raw Set to false to convert all object to array.
|
|
* @param array $options Options to bind data.
|
|
*
|
|
* @return static Return this object to support chaining.
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function merge($source, $raw = false, array $options = [])
|
|
{
|
|
if ($source instanceof self) {
|
|
$source = $source->getRaw();
|
|
}
|
|
|
|
$this->bindData($this->data, $source, $raw, $options);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Merge a structure data to a node.
|
|
*
|
|
* @param string $path The path to merge as root.
|
|
* @param Structure $source Source structure data to merge.
|
|
* @param boolean $raw Set to false to convert all object to array.
|
|
* @param array $options Options to bind data.
|
|
*
|
|
* @return static
|
|
*/
|
|
public function mergeTo($path, $source, $raw = false, array $options = [])
|
|
{
|
|
$nodes = StructureHelper::getPathNodes($path);
|
|
|
|
$data = [];
|
|
|
|
$tmp =& $data;
|
|
|
|
foreach ($nodes as $node) {
|
|
$tmp[$node] = [];
|
|
|
|
$tmp =& $tmp[$node];
|
|
}
|
|
|
|
if ($source instanceof self) {
|
|
$source = $source->getRaw();
|
|
}
|
|
|
|
$tmp = $source;
|
|
|
|
$this->bindData($this->data, $data, $raw, $options);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* extract
|
|
*
|
|
* @param string $path
|
|
*
|
|
* @return static
|
|
*/
|
|
public function extract($path)
|
|
{
|
|
return (new static())->load((array) $this->get($path), true);
|
|
}
|
|
|
|
/**
|
|
* getRaw
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getRaw()
|
|
{
|
|
return $this->data;
|
|
}
|
|
|
|
/**
|
|
* Checks whether an offset exists in the iterator.
|
|
*
|
|
* @param mixed $offset The array offset.
|
|
*
|
|
* @return boolean True if the offset exists, false otherwise.
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function offsetExists($offset): bool
|
|
{
|
|
return $this->get($offset) !== null;
|
|
}
|
|
|
|
/**
|
|
* Gets an offset in the iterator.
|
|
*
|
|
* @param mixed $offset The array offset.
|
|
*
|
|
* @return mixed The array value if it exists, null otherwise.
|
|
*
|
|
* @since 2.0
|
|
* : mixed
|
|
*/
|
|
#[\ReturnTypeWillChange]
|
|
public function offsetGet($offset)
|
|
{
|
|
return $this->get($offset);
|
|
}
|
|
|
|
/**
|
|
* Sets an offset in the iterator.
|
|
*
|
|
* @param mixed $offset The array offset.
|
|
* @param mixed $value The array value.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function offsetSet($offset, $value): void
|
|
{
|
|
$this->set($offset, $value);
|
|
}
|
|
|
|
/**
|
|
* Unsets an offset in the iterator.
|
|
*
|
|
* @param mixed $offset The array offset.
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function offsetUnset($offset): void
|
|
{
|
|
$this->set($offset, null);
|
|
}
|
|
|
|
/**
|
|
* Set a structure value and convert object to array.
|
|
*
|
|
* @param string $path Structure Path (e.g. foo.content.showauthor)
|
|
* @param mixed $value Value of entry.
|
|
*
|
|
* @return static Return self to support chaining.
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function set($path, $value)
|
|
{
|
|
if ($value instanceof ValueReference) {
|
|
$value = $value->get($this);
|
|
}
|
|
|
|
if (\is_array($value) || \is_object($value)) {
|
|
$value = StructureHelper::toArray($value, true);
|
|
}
|
|
|
|
StructureHelper::setByPath($this->data, $path, $value, $this->separator);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set a structure value.
|
|
*
|
|
* @param string $path Structure Path (e.g. foo.content.showauthor)
|
|
* @param mixed $value Value of entry.
|
|
*
|
|
* @return static Return self to support chaining.
|
|
*
|
|
* @since 2.1
|
|
*/
|
|
public function setRaw($path, $value)
|
|
{
|
|
StructureHelper::setByPath($this->data, $path, $value, $this->separator);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Transforms a namespace to an array
|
|
*
|
|
* @return array An associative array holding the namespace data
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function toArray()
|
|
{
|
|
return (array) $this->asArray($this->data);
|
|
}
|
|
|
|
/**
|
|
* Transforms a namespace to an object
|
|
*
|
|
* @param string $class The class of object.
|
|
*
|
|
* @return object An an object holding the namespace data
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function toObject($class = 'stdClass')
|
|
{
|
|
return StructureHelper::toObject($this->data, $class);
|
|
}
|
|
|
|
/**
|
|
* Get a namespace in a given string format
|
|
*
|
|
* @param string $format Format to return the string in
|
|
* @param mixed $options Parameters used by the formatter, see formatters for more info
|
|
*
|
|
* @return string Namespace in string format
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
public function toString($format = Format::JSON, $options = [])
|
|
{
|
|
return StructureHelper::toString($this->data, $format, $options);
|
|
}
|
|
|
|
/**
|
|
* Method to recursively bind data to a parent object.
|
|
*
|
|
* @param array $parent The parent object on which to attach the data values.
|
|
* @param mixed $data An array or object of data to bind to the parent object.
|
|
* @param boolean $raw Set to false to convert all object to array.
|
|
* @param array $options The options to bind data.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function bindData(&$parent, $data, $raw = false, array $options = [])
|
|
{
|
|
// Ensure the input data is an array.
|
|
if (!$raw) {
|
|
$data = StructureHelper::toArray($data, true);
|
|
}
|
|
|
|
$onlyExists = !empty($options['only_exists']);
|
|
|
|
foreach ($data as $key => $value) {
|
|
if (\in_array($value, $this->ignoreValues, true)) {
|
|
continue;
|
|
}
|
|
|
|
if ($onlyExists && !isset($parent[$key])) {
|
|
continue;
|
|
}
|
|
|
|
if (\is_array($value)) {
|
|
if (!isset($parent[$key]) || !\is_array($parent[$key])) {
|
|
$parent[$key] = [];
|
|
}
|
|
|
|
$this->bindData($parent[$key], $value, $raw);
|
|
} else {
|
|
$parent[$key] = $this->resolveValue($value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to recursively convert an object of data to an array.
|
|
*
|
|
* @param mixed $data An object of data to return as an array.
|
|
*
|
|
* @return array Array representation of the input object.
|
|
*
|
|
* @since 2.0
|
|
*/
|
|
protected function asArray($data)
|
|
{
|
|
$array = [];
|
|
|
|
if (\is_object($data)) {
|
|
$data = get_object_vars($data);
|
|
}
|
|
|
|
foreach ($data as $k => $v) {
|
|
if (\is_object($v) || \is_array($v)) {
|
|
$array[$k] = $this->asArray($v);
|
|
} else {
|
|
$array[$k] = $v;
|
|
}
|
|
}
|
|
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* Dump to on dimension array.
|
|
*
|
|
* @param string $separator The key separator.
|
|
*
|
|
* @return string[] Dumped array.
|
|
*/
|
|
public function flatten($separator = '.')
|
|
{
|
|
return StructureHelper::flatten($this->data, $separator);
|
|
}
|
|
|
|
/**
|
|
* Method to get property Separator
|
|
*
|
|
* @return string
|
|
*
|
|
* @since 2.1
|
|
*/
|
|
public function getSeparator()
|
|
{
|
|
return $this->separator;
|
|
}
|
|
|
|
/**
|
|
* Method to set property separator
|
|
*
|
|
* @param string $separator
|
|
*
|
|
* @return static Return self to support chaining.
|
|
*
|
|
* @since 2.1
|
|
*/
|
|
public function setSeparator($separator)
|
|
{
|
|
$this->separator = $separator;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Push value to a path in structure
|
|
*
|
|
* @param string $path Parent structure Path (e.g. windwalker.content.showauthor)
|
|
* @param mixed $value Value of entry, one or more elements.
|
|
*
|
|
* @return integer the new number of elements in the array.
|
|
*
|
|
* @since 2.1
|
|
*/
|
|
public function push($path, $value)
|
|
{
|
|
$node = $this->get($path);
|
|
|
|
if (!$node) {
|
|
$node = [];
|
|
} elseif (\is_object($node)) {
|
|
$node = get_object_vars($node);
|
|
}
|
|
|
|
if (!\is_array($node)) {
|
|
throw new \UnexpectedValueException(
|
|
sprintf(
|
|
'The value at path: %s should be object or array but is %s.',
|
|
$path,
|
|
\gettype($node)
|
|
)
|
|
);
|
|
}
|
|
|
|
$args = \func_get_args();
|
|
|
|
if (count($args) <= 2) {
|
|
$num = array_push($node, $value);
|
|
} else {
|
|
$args[0] = &$node;
|
|
|
|
$num = call_user_func_array('array_push', $args);
|
|
}
|
|
|
|
$this->set($path, $node);
|
|
|
|
return $num;
|
|
}
|
|
|
|
/**
|
|
* Prepend value to a path in structure.
|
|
*
|
|
* @param string $path Parent structure Path (e.g. windwalker.content.showauthor)
|
|
* @param mixed $value Value of entry, one or more elements.
|
|
*
|
|
* @return integer the new number of elements in the array.
|
|
*
|
|
* @since 2.1
|
|
*/
|
|
public function unshift($path, $value)
|
|
{
|
|
$node = $this->get($path);
|
|
|
|
if (!$node) {
|
|
$node = [];
|
|
} elseif (\is_object($node)) {
|
|
$node = get_object_vars($node);
|
|
}
|
|
|
|
if (!\is_array($node)) {
|
|
throw new \UnexpectedValueException(
|
|
sprintf(
|
|
'The value at path: %s should be object or array but is %s.',
|
|
$path,
|
|
gettype($node)
|
|
)
|
|
);
|
|
}
|
|
|
|
$args = \func_get_args();
|
|
|
|
if (\count($args) <= 2) {
|
|
$key = array_unshift($node, $value);
|
|
} else {
|
|
$args[0] = &$node;
|
|
|
|
$key = call_user_func_array('array_unshift', $args);
|
|
}
|
|
|
|
$this->set($path, $node);
|
|
|
|
return $key;
|
|
}
|
|
|
|
/**
|
|
* To remove first element from the path of this structure.
|
|
*
|
|
* @param string $path The structure path.
|
|
*
|
|
* @return mixed The shifted value, or null if array is empty.
|
|
*/
|
|
public function shift($path)
|
|
{
|
|
$node = $this->get($path);
|
|
|
|
if (\is_object($node)) {
|
|
$node = get_object_vars($node);
|
|
}
|
|
|
|
if (!\is_array($node)) {
|
|
throw new \UnexpectedValueException(
|
|
sprintf(
|
|
'The value at path: %s should be object or array but is %s.',
|
|
$path,
|
|
\gettype($node)
|
|
)
|
|
);
|
|
}
|
|
|
|
$value = array_shift($node);
|
|
|
|
$this->set($path, $node);
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* To remove last element from the path of this structure.
|
|
*
|
|
* @param string $path The structure path.
|
|
*
|
|
* @return mixed The shifted value, or &null; if array is empty.
|
|
*/
|
|
public function pop($path)
|
|
{
|
|
$node = $this->get($path);
|
|
|
|
if (\is_object($node)) {
|
|
$node = get_object_vars($node);
|
|
}
|
|
|
|
if (!\is_array($node)) {
|
|
throw new \UnexpectedValueException(
|
|
sprintf(
|
|
'The value at path: %s should be object or array but is %s.',
|
|
$path,
|
|
\gettype($node)
|
|
)
|
|
);
|
|
}
|
|
|
|
$value = array_pop($node);
|
|
|
|
$this->set($path, $node);
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Gets this object represented as an RecursiveArrayIterator.
|
|
*
|
|
* This allows the data properties to be accessed via a foreach statement.
|
|
*
|
|
* You can wrap this iterator by RecursiveIteratorIterator that will support recursive foreach.
|
|
* Example: `foreach (new \RecursiveIteratorIterator($structure) as $value)`
|
|
*
|
|
* @return \RecursiveArrayIterator This object represented as an RecursiveArrayIterator.
|
|
*
|
|
* @see IteratorAggregate::getIterator()
|
|
* @since 2.1
|
|
*/
|
|
public function getIterator(): \Traversable
|
|
{
|
|
return new \RecursiveArrayIterator($this->data);
|
|
}
|
|
|
|
/**
|
|
* Count elements of the data object
|
|
*
|
|
* @return integer The custom count as an integer.
|
|
*
|
|
* @link http://php.net/manual/en/countable.count.php
|
|
* @since 2.1
|
|
*/
|
|
public function count(): int
|
|
{
|
|
return \count($this->data);
|
|
}
|
|
|
|
/**
|
|
* resolveValue
|
|
*
|
|
* @param mixed|ValueReference $value
|
|
*
|
|
* @return mixed
|
|
*
|
|
* @since 3.5.1
|
|
*/
|
|
protected function resolveValue($value)
|
|
{
|
|
if ($value instanceof ValueReference) {
|
|
$value = $value->get($this);
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Method to get property IgnoreValues
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getIgnoreValues()
|
|
{
|
|
return $this->ignoreValues;
|
|
}
|
|
|
|
/**
|
|
* Method to set property ignoreValues
|
|
*
|
|
* @param array $ignoreValues
|
|
*
|
|
* @return static Return self to support chaining.
|
|
*/
|
|
public function setIgnoreValues($ignoreValues)
|
|
{
|
|
$this->ignoreValues = (array) $ignoreValues;
|
|
|
|
return $this;
|
|
}
|
|
}
|