803 lines
20 KiB
Plaintext
803 lines
20 KiB
Plaintext
/**
|
|
* Path separator
|
|
*
|
|
* @var string|null
|
|
* @since 3.2.0
|
|
*/
|
|
protected ?string $separator = '.';
|
|
|
|
/**
|
|
* The name of the registry.
|
|
*
|
|
* @var string|null
|
|
* @since 5.0.4
|
|
*/
|
|
protected ?string $name = null;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* Initializes the Registry object with optional data.
|
|
*
|
|
* @param mixed $data Optional data to load into the registry.
|
|
* Can be an array, string, or object.
|
|
* @param string|null $separator The path separator, and empty string will flatten the registry.
|
|
* @since 5.0.4
|
|
*/
|
|
public function __construct($data = null, ?string $separator = null)
|
|
{
|
|
// we don't allow null on initialization (default is a dot)
|
|
// so that all class inheritance can override the separator property
|
|
// use an empty string if you want to flatten the registry
|
|
if ($separator !== null)
|
|
{
|
|
$this->setSeparator($separator);
|
|
}
|
|
|
|
if ($data !== null)
|
|
{
|
|
if (is_array($data))
|
|
{
|
|
$this->loadArray($data);
|
|
}
|
|
elseif (is_string($data))
|
|
{
|
|
$this->loadString($data);
|
|
}
|
|
elseif (is_object($data))
|
|
{
|
|
$this->loadObject($data);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Magic method to get a value from the registry.
|
|
*
|
|
* Allows for accessing registry data using object property syntax.
|
|
*
|
|
* @param string $name The name of the property to get.
|
|
*
|
|
* @return mixed The value of the property, or null if not found.
|
|
* @since 5.0.4
|
|
*/
|
|
public function __get($name)
|
|
{
|
|
return $this->get($name);
|
|
}
|
|
|
|
/**
|
|
* Magic method to set a value in the registry.
|
|
*
|
|
* Allows for setting registry data using object property syntax.
|
|
*
|
|
* @param string $name The name of the property to set.
|
|
* @param mixed $value The value to set.
|
|
*
|
|
* @return void
|
|
* @since 5.0.4
|
|
*/
|
|
public function __set($name, $value)
|
|
{
|
|
$this->set($name, $value);
|
|
}
|
|
|
|
/**
|
|
* Magic method to check if a property is set in the registry.
|
|
*
|
|
* Allows for using isset() on registry properties.
|
|
*
|
|
* @param string $name The name of the property to check.
|
|
*
|
|
* @return bool True if the property is set, false otherwise.
|
|
* @since 5.0.4
|
|
*/
|
|
public function __isset($name)
|
|
{
|
|
return $this->exists($name);
|
|
}
|
|
|
|
/**
|
|
* Magic method to unset a property in the registry.
|
|
*
|
|
* Allows for using unset() on registry properties.
|
|
*
|
|
* @param string $name The name of the property to unset.
|
|
*
|
|
* @return void
|
|
* @since 5.0.4
|
|
*/
|
|
public function __unset($name)
|
|
{
|
|
$this->remove($name);
|
|
}
|
|
|
|
/**
|
|
* Magic method to clone the registry.
|
|
*
|
|
* Performs a deep copy of the registry data.
|
|
*
|
|
* @return void
|
|
* @since 5.0.4
|
|
*/
|
|
public function __clone()
|
|
{
|
|
$this->active = unserialize(serialize($this->active));
|
|
}
|
|
|
|
/**
|
|
* Magic method to convert the registry to a string.
|
|
*
|
|
* Returns the registry data in JSON format.
|
|
*
|
|
* @return string The registry data in JSON format.
|
|
* @since 5.0.4
|
|
*/
|
|
public function __toString()
|
|
{
|
|
return $this->toString();
|
|
}
|
|
|
|
/**
|
|
* Loads data into the registry from a string using Joomla's format classes.
|
|
*
|
|
* @param string $data The data string to load.
|
|
* @param string $format The format of the data string. Supported formats: 'json', 'ini', 'xml', 'php'.
|
|
* @param array $options Options used by the formatter
|
|
*
|
|
* @return self
|
|
* @throws \InvalidArgumentException If the format is not supported.
|
|
* @since 5.0.4
|
|
*/
|
|
public function loadString(string $data, string $format = 'JSON', array $options = []): self
|
|
{
|
|
// Load a string into the given namespace [or default namespace if not given]
|
|
$object = FormatFactory::getFormat($format, $options)->stringToObject($data, $options);
|
|
|
|
// Merge the object into the registry
|
|
$this->loadObject($object);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Loads data into the registry from an object.
|
|
*
|
|
* @param object $object The data object to load.
|
|
*
|
|
* @return self
|
|
* @since 5.0.4
|
|
*/
|
|
public function loadObject(object $object): self
|
|
{
|
|
// Convert the object to an array
|
|
$array = $this->objectToArray($object);
|
|
|
|
// Merge the array into the registry
|
|
$this->loadArray($array);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Loads data into the registry from an array.
|
|
*
|
|
* The loaded data will be merged into the registry's existing data.
|
|
*
|
|
* @param array $array The array of data to load into the registry.
|
|
*
|
|
* @return self
|
|
* @since 5.0.4
|
|
*/
|
|
public function loadArray(array $array): self
|
|
{
|
|
$this->active = $this->arrayMergeRecursive($this->active, $array);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Loads data into the registry from a file.
|
|
*
|
|
* @param string $path The path to the file to load.
|
|
* @param string $format The format of the file. Supported formats: 'json', 'ini', 'xml', 'php'.
|
|
*
|
|
* @return self
|
|
* @throws \InvalidArgumentException If the file does not exist or is not readable.
|
|
* @throws \RuntimeException If the file cannot be read.
|
|
* @since 5.0.4
|
|
*/
|
|
public function loadFile(string $path, string $format = 'json'): self
|
|
{
|
|
if (!file_exists($path) || !is_readable($path))
|
|
{
|
|
throw new \InvalidArgumentException("File does not exist or is not readable: {$path}");
|
|
}
|
|
|
|
$data = file_get_contents($path);
|
|
|
|
if ($data === false)
|
|
{
|
|
throw new \RuntimeException("Failed to read file: {$path}");
|
|
}
|
|
|
|
$this->loadString($data, $format);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Sets a value into the registry using multiple keys.
|
|
*
|
|
* @param string $path Registry path (e.g. vdm.content.builder)
|
|
* @param mixed $value Value of entry
|
|
*
|
|
* @throws \InvalidArgumentException If any of the path values are not a number or string.
|
|
* @return self
|
|
* @since 3.2.0
|
|
*/
|
|
public function set(string $path, $value): self
|
|
{
|
|
if (($keys = $this->getActiveKeys($path)) === null)
|
|
{
|
|
throw new \InvalidArgumentException("Path must only be strings or numbers to set any value.");
|
|
}
|
|
|
|
$this->setActive($value, ...$keys);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Adds content into the registry. If a key exists,
|
|
* it either appends or concatenates based on $asArray switch.
|
|
*
|
|
* @param string $path Registry path (e.g. vdm.content.builder)
|
|
* @param mixed $value Value of entry
|
|
* @param bool|null $asArray Determines if the new value should be treated as an array.
|
|
* Default is $addAsArray = false (if null) in base class.
|
|
* Override in child class allowed set class property $addAsArray = true.
|
|
*
|
|
* @throws \InvalidArgumentException If any of the path values are not a number or string.
|
|
* @return self
|
|
* @since 3.2.0
|
|
*/
|
|
public function add(string $path, $value, ?bool $asArray = null): self
|
|
{
|
|
if (($keys = $this->getActiveKeys($path)) === null)
|
|
{
|
|
throw new \InvalidArgumentException("Path must only be strings or numbers to add any value.");
|
|
}
|
|
|
|
$this->addActive($value, $asArray, ...$keys);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a value (or sub-array) from the registry using multiple keys.
|
|
*
|
|
* @param string $path Registry path (e.g. vdm.content.builder)
|
|
* @param mixed $default Optional default value, returned if the internal doesn't exist.
|
|
*
|
|
* @throws \InvalidArgumentException If any of the path values are not a number or string.
|
|
* @return mixed The value or sub-array from the storage. Null if the location doesn't exist.
|
|
* @since 3.2.0
|
|
*/
|
|
public function get(string $path, $default = null): mixed
|
|
{
|
|
if (($keys = $this->getActiveKeys($path)) === null)
|
|
{
|
|
throw new \InvalidArgumentException("Path must only be strings or numbers to get any value.");
|
|
}
|
|
|
|
return $this->getActive($default, ...$keys);
|
|
}
|
|
|
|
/**
|
|
* Removes a value (or sub-array) from the registry using multiple keys.
|
|
*
|
|
* @param string $path Registry path (e.g. vdm.content.builder)
|
|
*
|
|
* @throws \InvalidArgumentException If any of the path values are not a number or string.
|
|
* @return self
|
|
* @since 3.2.0
|
|
*/
|
|
public function remove(string $path): self
|
|
{
|
|
if (($keys = $this->getActiveKeys($path)) === null)
|
|
{
|
|
throw new \InvalidArgumentException("Path must only be strings or numbers to remove any value.");
|
|
}
|
|
|
|
$this->removeActive(...$keys);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Checks the existence of a particular location in the registry using multiple keys.
|
|
*
|
|
* @param string $path Registry path (e.g. vdm.content.builder)
|
|
*
|
|
* @throws \InvalidArgumentException If any of the path values are not a number or string.
|
|
* @return bool True if the location exists, false otherwise.
|
|
* @since 3.2.0
|
|
*/
|
|
public function exists(string $path): bool
|
|
{
|
|
if (($keys = $this->getActiveKeys($path)) === null)
|
|
{
|
|
throw new \InvalidArgumentException("Path must only be strings or numbers to check if any value exist.");
|
|
}
|
|
|
|
return $this->existsActive(...$keys);
|
|
}
|
|
|
|
/**
|
|
* Specify data which should be serialized to JSON.
|
|
*
|
|
* @return mixed Data which can be serialized by json_encode(),
|
|
* which is a value of any type other than a resource.
|
|
* @since 5.0.4
|
|
*/
|
|
public function jsonSerialize(): mixed
|
|
{
|
|
return $this->active;
|
|
}
|
|
|
|
/**
|
|
* Count elements of the registry.
|
|
*
|
|
* @return int The number of elements in the registry.
|
|
* @since 5.0.4
|
|
*/
|
|
public function count(): int
|
|
{
|
|
return count($this->active);
|
|
}
|
|
|
|
/**
|
|
* Whether a given offset exists in the registry.
|
|
*
|
|
* @param mixed $offset An offset to check for.
|
|
*
|
|
* @return bool True if the offset exists, false otherwise.
|
|
* @since 5.0.4
|
|
*/
|
|
public function offsetExists(mixed $offset): bool
|
|
{
|
|
if (!is_string($offset))
|
|
{
|
|
return false;
|
|
}
|
|
return $this->exists($offset);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the value at a given offset.
|
|
*
|
|
* @param mixed $offset The offset to retrieve.
|
|
*
|
|
* @return mixed The value at the specified offset.
|
|
* @since 5.0.4
|
|
*/
|
|
public function offsetGet(mixed $offset): mixed
|
|
{
|
|
if (!is_string($offset))
|
|
{
|
|
return null;
|
|
}
|
|
return $this->get($offset);
|
|
}
|
|
|
|
/**
|
|
* Set the value at a given offset.
|
|
*
|
|
* @param mixed $offset The offset to assign the value to.
|
|
* @param mixed $value The value to set.
|
|
*
|
|
* @return void
|
|
* @since 5.0.4
|
|
*/
|
|
public function offsetSet(mixed $offset, mixed $value): void
|
|
{
|
|
if (!is_string($offset))
|
|
{
|
|
return;
|
|
}
|
|
$this->set($offset, $value);
|
|
}
|
|
|
|
/**
|
|
* Unset the value at a given offset.
|
|
*
|
|
* @param mixed $offset The offset to unset.
|
|
*
|
|
* @return void
|
|
* @since 5.0.4
|
|
*/
|
|
public function offsetUnset(mixed $offset): void
|
|
{
|
|
if (!is_string($offset))
|
|
{
|
|
return;
|
|
}
|
|
$this->remove($offset);
|
|
}
|
|
|
|
/**
|
|
* Retrieve an external iterator for the registry.
|
|
*
|
|
* @return \Traversable An instance of an object implementing Iterator or Traversable.
|
|
* @since 5.0.4
|
|
*/
|
|
public function getIterator(): \Traversable
|
|
{
|
|
return new \ArrayIterator($this->active);
|
|
}
|
|
|
|
/**
|
|
* Get the registry data as an associative array.
|
|
*
|
|
* @return array The registry data.
|
|
* @since 5.0.4
|
|
*/
|
|
public function toArray(): array
|
|
{
|
|
return $this->active;
|
|
}
|
|
|
|
/**
|
|
* Get the registry data as an object.
|
|
*
|
|
* @return object The registry data converted to an object.
|
|
* @since 5.0.4
|
|
*/
|
|
public function toObject()
|
|
{
|
|
return $this->arrayToObject($this->active);
|
|
}
|
|
|
|
/**
|
|
* Converts the registry data to a string in the specified format.
|
|
*
|
|
* @param string $format The format to output the string in. Supported formats: 'json', 'ini', 'xml', 'php'.
|
|
* @param array $options Options used by the formatter.
|
|
*
|
|
* @return string The registry data in the specified format.
|
|
*
|
|
* @throws \InvalidArgumentException If the format is not supported.
|
|
* @since 5.0.4
|
|
*/
|
|
public function toString(string $format = 'JSON', array $options = []): string
|
|
{
|
|
// Convert the internal array to an object
|
|
$object = $this->arrayToObject($this->active);
|
|
|
|
return FormatFactory::getFormat($format, $options)->objectToString($object, $options);
|
|
}
|
|
|
|
/**
|
|
* Flattens the registry data into a one-dimensional array.
|
|
*
|
|
* @param string|null $separator The separator for the key names.
|
|
* @param bool $full True to include the full path as keys.
|
|
*
|
|
* @return array The flattened data array.
|
|
* @since 5.0.4
|
|
*/
|
|
public function flatten(?string $separator = null, bool $full = false): array
|
|
{
|
|
// we use default separator
|
|
if ($separator === null)
|
|
{
|
|
$separator = $this->separator;
|
|
}
|
|
|
|
return $this->flattenArray($this->active, $separator, $full);
|
|
}
|
|
|
|
/**
|
|
* Sets a default value if not already set.
|
|
*
|
|
* @param string $path The registry path (e.g., 'vdm.content.builder').
|
|
* @param mixed $default The default value to set if the path does not exist.
|
|
*
|
|
* @return mixed The value of the path after the method call.
|
|
* @since 5.0.4
|
|
*/
|
|
public function def(string $path, $default)
|
|
{
|
|
if (!$this->exists($path))
|
|
{
|
|
$this->set($path, $default);
|
|
return $default;
|
|
}
|
|
return $this->get($path);
|
|
}
|
|
|
|
/**
|
|
* Merges another registry into this one.
|
|
*
|
|
* The data from the source registry will be merged into this registry,
|
|
* overwriting any existing values with the same keys.
|
|
*
|
|
* @param Registryinterface $source The registry to merge with this one.
|
|
*
|
|
* @return self
|
|
* @since 5.0.4
|
|
*/
|
|
public function merge(Registryinterface $source): self
|
|
{
|
|
$this->active = $this->arrayMergeRecursive($this->active, $source->toArray());
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Clears all data from the registry.
|
|
*
|
|
* @return self
|
|
* @since 5.0.4
|
|
*/
|
|
public function clear(): self
|
|
{
|
|
$this->active = [];
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Extracts a subset of the registry data based on a given path.
|
|
*
|
|
* @param string $path The registry path to extract.
|
|
* @param mixed $default Optional default value, returned if the path does not exist.
|
|
* @param string|null $separator The path separator.
|
|
*
|
|
* @return self A new Registry instance with the extracted data.
|
|
* @since 5.0.4
|
|
*/
|
|
public function extract(string $path, $default = null, ?string $separator = null): self
|
|
{
|
|
$originalSeparator = $this->getSeparator();
|
|
if ($separator !== null)
|
|
{
|
|
$this->setSeparator($separator);
|
|
}
|
|
|
|
$data = $this->get($path, $default);
|
|
|
|
if ($separator !== null)
|
|
{
|
|
$this->setSeparator($originalSeparator);
|
|
}
|
|
|
|
$newRegistry = new static();
|
|
|
|
if ($data !== $default)
|
|
{
|
|
if (is_array($data))
|
|
{
|
|
$newRegistry->loadArray($data);
|
|
}
|
|
else
|
|
{
|
|
$newRegistry->set('value', $data);
|
|
}
|
|
}
|
|
|
|
return $newRegistry;
|
|
}
|
|
|
|
/**
|
|
* Appends content into the registry.
|
|
*
|
|
* If a key exists, the value will be appended to the existing value.
|
|
*
|
|
* @param string $path The registry path (e.g., 'vdm.content.builder').
|
|
* @param mixed $value The value to append.
|
|
*
|
|
* @return self
|
|
* @since 5.0.4
|
|
*/
|
|
public function append(string $path, $value): self
|
|
{
|
|
return $this->add($path, $value, false);
|
|
}
|
|
|
|
/**
|
|
* Gets the name of the registry.
|
|
*
|
|
* @return string|null The name of the registry.
|
|
* @since 5.0.4
|
|
*/
|
|
public function getName(): ?string
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
/**
|
|
* Sets the name of the registry.
|
|
*
|
|
* @param string|null $name The name to set.
|
|
*
|
|
* @return self
|
|
* @since 5.0.4
|
|
*/
|
|
public function setName(?string $name): self
|
|
{
|
|
$this->name = $name;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Sets a separator value
|
|
*
|
|
* @param string|null $value The value to set.
|
|
*
|
|
* @return self
|
|
* @since 3.2.0
|
|
*/
|
|
public function setSeparator(?string $value): self
|
|
{
|
|
$this->separator = $value;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Gets the current path separator used in registry paths.
|
|
*
|
|
* @return string|null The path separator.
|
|
* @since 5.0.4
|
|
*/
|
|
public function getSeparator(): ?string
|
|
{
|
|
return $this->separator;
|
|
}
|
|
|
|
/**
|
|
* Recursively converts an array to an object.
|
|
*
|
|
* This method is used to convert the internal array data into an object
|
|
* structure suitable for serialization or other operations that require objects.
|
|
*
|
|
* @param mixed $data The data to convert.
|
|
*
|
|
* @return mixed The converted object, or the original data if not an array.
|
|
* @since 5.0.4
|
|
*/
|
|
protected function arrayToObject($data)
|
|
{
|
|
if (is_array($data))
|
|
{
|
|
$object = new \stdClass();
|
|
foreach ($data as $key => $value)
|
|
{
|
|
// Handle numeric keys for object properties
|
|
if (is_numeric($key))
|
|
{
|
|
$key = 'item' . $key;
|
|
}
|
|
$object->{$key} = $this->arrayToObject($value);
|
|
}
|
|
return $object;
|
|
}
|
|
else
|
|
{
|
|
return $data;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively converts an object to an array.
|
|
*
|
|
* This method is used to convert data loaded from formats that produce objects
|
|
* (e.g., JSON, XML) into an array structure for internal storage.
|
|
*
|
|
* @param mixed $data The data to convert.
|
|
*
|
|
* @return mixed The converted array, or the original data if not an object.
|
|
* @since 5.0.4
|
|
*/
|
|
protected function objectToArray($data)
|
|
{
|
|
return json_decode(json_encode($data), true);
|
|
}
|
|
|
|
/**
|
|
* Recursively merges two arrays.
|
|
*
|
|
* This method merges the elements of two arrays together so that the values of one
|
|
* are appended to the end of the previous one. It preserves numeric keys.
|
|
*
|
|
* @param array $array1 The array to merge into.
|
|
* @param array $array2 The array to merge from.
|
|
*
|
|
* @return array The merged array.
|
|
* @since 5.0.4
|
|
*/
|
|
protected function arrayMergeRecursive(array $array1, array $array2): array
|
|
{
|
|
foreach ($array2 as $key => $value)
|
|
{
|
|
// If the value is an array and the key exists in both arrays, merge recursively
|
|
if (is_array($value) && isset($array1[$key]) && is_array($array1[$key]))
|
|
{
|
|
$array1[$key] = $this->arrayMergeRecursive($array1[$key], $value);
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, replace or set the value
|
|
$array1[$key] = $value;
|
|
}
|
|
}
|
|
return $array1;
|
|
}
|
|
|
|
/**
|
|
* Helper function to recursively flatten the array.
|
|
*
|
|
* @param array $array The array to flatten.
|
|
* @param string $separator The separator for the key names.
|
|
* @param bool $full True to include the full path as keys.
|
|
* @param array $flattened The flattened array (used internally for recursion).
|
|
* @param string $path The current path (used internally for recursion).
|
|
*
|
|
* @return array The flattened array.
|
|
* @since 5.0.4
|
|
*/
|
|
protected function flattenArray(array $array, string $separator, bool $full, array $flattened = [], string $path = ''): array
|
|
{
|
|
foreach ($array as $key => $value)
|
|
{
|
|
if ($full)
|
|
{
|
|
$newPath = $path === '' ? $key : $path . $separator . $key;
|
|
}
|
|
else
|
|
{
|
|
$newPath = $key;
|
|
}
|
|
|
|
if (is_array($value))
|
|
{
|
|
$flattened = $this->flattenArray($value, $separator, $full, $flattened, $newPath);
|
|
}
|
|
else
|
|
{
|
|
$flattened[$newPath] = $value;
|
|
}
|
|
}
|
|
return $flattened;
|
|
}
|
|
|
|
/**
|
|
* Get that the active keys from a path
|
|
*
|
|
* @param string $path The path to determine the location registry.
|
|
*
|
|
* @return array|null The valid array of keys
|
|
* @since 3.2.0
|
|
*/
|
|
protected function getActiveKeys(string $path): ?array
|
|
{
|
|
// empty path no allowed
|
|
if ($path === '')
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Flatten the path
|
|
if ($this->separator === null || $this->separator === '')
|
|
{
|
|
return [$path];
|
|
}
|
|
|
|
$keys = array_values(array_filter(explode($this->separator, $path), 'strlen'));
|
|
|
|
if (empty($keys))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return $keys;
|
|
} |