Vous êtes connecté en tant que anonymous Se Deconnecter
<?php declare(strict_types=1);
/**
 * Part of Windwalker project.
 *
 * @copyright  Copyright (C) 2019 LYRASOFT.
 * @license    LGPL-2.0-or-later
 */

namespace Windwalker\Structure;

/**
 * Class StructureHelper
 *
 * @since 2.0
 */
class StructureHelper
{
    /**
     * Property objectStorage.
     *
     * @var  \SplObjectStorage
     */
    private static $objectStorage;

    /**
     * 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  array  Return parsed array.
     *
     * @since   2.1
     */
    public static function loadFile($file, $format = Format::JSON, $options = [])
    {
        if (!is_file($file)) {
            throw new \InvalidArgumentException('No such file: ' . $file);
        }

        if (strtolower($format) == Format::PHP) {
            $data = include $file;
        } else {
            $data = file_get_contents($file);
        }

        return static::loadString($data, $format, $options);
    }

    /**
     * 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  array  Return parsed array.
     *
     * @since   2.1
     */
    public static function loadString($data, $format = Format::JSON, $options = [])
    {
        // Load a string into the given namespace [or default namespace if not given]
        $class = static::getFormatClass($format);

        return $class::stringToStruct($data, $options);
    }

    /**
     * Get a namespace in a given string format
     *
     * @param   array|object $data    The structure data to convert to markup string.
     * @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.1
     */
    public static function toString($data, $format = Format::JSON, $options = [])
    {
        $class = static::getFormatClass($format);

        return $class::structToString($data, $options);
    }

    /**
     * getFormatClass
     *
     * @param string $format
     *
     * @return  string|\Windwalker\Structure\Format\FormatInterface
     *
     * @throws  \DomainException
     *
     * @since   2.1
     */
    public static function getFormatClass($format)
    {
        // Return a namespace in a given format
        $class = sprintf('%s\Format\%sFormat', __NAMESPACE__, ucfirst(strtolower($format)));

        if (!class_exists($class)) {
            throw new \DomainException(
                sprintf(
                    'Structure format: %s not supported. Class: %s not found.',
                    $format,
                    $class
                )
            );
        }

        return $class;
    }

    /**
     * Method to determine if an array is an associative array.
     *
     * @param   array $array An array to test.
     *
     * @return  boolean  True if the array is an associative array.
     */
    public static function isAssociativeArray($array)
    {
        if (is_array($array)) {
            foreach (array_keys($array) as $k => $v) {
                if ($k !== $v) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * getValue
     *
     * @param array  $array
     * @param string $name
     * @param mixed  $default
     *
     * @return  mixed
     */
    public static function getValue(array $array, $name, $default = null)
    {
        return isset($array[$name]) ? $array[$name] : $default;
    }

    /**
     * Utility function to map an array to a stdClass object.
     *
     * @param   array  $array The array to map.
     * @param   string $class Name of the class to create
     *
     * @return  object   The object mapped from the given array
     *
     * @since   2.0
     */
    public static function toObject($array, $class = 'stdClass')
    {
        $object = new $class();

        foreach ($array as $k => $v) {
            if (is_array($v)) {
                $object->$k = static::toObject($v, $class);
            } else {
                $object->$k = $v;
            }
        }

        return $object;
    }

    /**
     * Get data from array or object by path.
     *
     * Example: `StructureHelper::getByPath($array, 'foo.bar.yoo')` equals to $array['foo']['bar']['yoo'].
     *
     * @param mixed  $data      An array or object to get value.
     * @param mixed  $path      The key path.
     * @param string $separator Separator of paths.
     *
     * @return  mixed Found value, null if not exists.
     *
     * @since   2.1
     */
    public static function getByPath(array $data, $path, $separator = '.')
    {
        $nodes = static::getPathNodes($path, $separator);

        if (empty($nodes)) {
            return null;
        }

        $dataTmp = $data;

        foreach ($nodes as $arg) {
            if (is_object($dataTmp) && isset($dataTmp->$arg)) {
                $dataTmp = $dataTmp->$arg;
            } elseif ($dataTmp instanceof \ArrayAccess && isset($dataTmp[$arg])) {
                $dataTmp = $dataTmp[$arg];
            } elseif (is_array($dataTmp) && isset($dataTmp[$arg])) {
                $dataTmp = $dataTmp[$arg];
            } else {
                return null;
            }
        }

        return $dataTmp;
    }

    /**
     * setByPath
     *
     * @param mixed  &$data
     * @param string $path
     * @param mixed  $value
     * @param string $separator
     *
     * @return  boolean
     *
     * @since   2.1
     */
    public static function setByPath(array &$data, $path, $value, $separator = '.')
    {
        $nodes = static::getPathNodes($path, $separator);

        if (empty($nodes)) {
            return false;
        }

        $dataTmp = &$data;

        foreach ($nodes as $node) {
            if (is_array($dataTmp)) {
                if (!isset($dataTmp[$node])) {
                    $dataTmp[$node] = [];
                }

                $dataTmp = &$dataTmp[$node];
            } else {
                // If a node is value but path is not go to the end, we replace this value as a new store.
                // Then next node can insert new value to this store.
                $dataTmp = [];
            }
        }

        // Now, path go to the end, means we get latest node, set value to this node.
        $dataTmp = $value;

        return true;
    }

    /**
     * removeByPath
     *
     * @param array  $data
     * @param string $path
     * @param string $separator
     *
     * @return  bool
     */
    public static function removeByPath(array &$data, $path, $separator = '.')
    {
        $nodes = static::getPathNodes($path, $separator);

        if (empty($nodes)) {
            return false;
        }

        $previous = null;
        $dataTmp = &$data;

        foreach ($nodes as $node) {
            if (is_array($dataTmp)) {
                if (empty($dataTmp[$node])) {
                    return false;
                }

                $previous = &$dataTmp;
                $dataTmp = &$dataTmp[$node];
            } else {
                return false;
            }
        }

        // Now, path go to the end, means we get latest node, set value to this node.
        unset($previous[$node]);

        return true;
    }

    /**
     * Explode the structure path into an array and remove empty
     * nodes that occur as a result of a double dot. ex: windwalker..test
     * Finally, re-key the array so they are sequential.
     *
     * @param string $path
     * @param string $separator
     *
     * @return  array
     */
    public static function getPathNodes($path, $separator = '.')
    {
        return array_values(array_filter(explode($separator, $path), 'strlen'));
    }

    /**
     * Method to recursively convert data to one dimension array.
     *
     * @param   array|object $array     The array or object to convert.
     * @param   string       $separator The key separator.
     * @param   string       $prefix    Last level key prefix.
     *
     * @return  array
     */
    public static function flatten($array, $separator = '.', $prefix = '')
    {
        $return = [];

        if ($array instanceof \Traversable) {
            $array = iterator_to_array($array);
        } elseif (is_object($array)) {
            $array = get_object_vars($array);
        }

        foreach ($array as $k => $v) {
            $key = $prefix ? $prefix . $separator . $k : $k;

            if (is_object($v) || is_array($v)) {
                $return = array_merge($return, static::flatten($v, $separator, $key));
            } else {
                $return[$key] = $v;
            }
        }

        return $return;
    }

    /**
     * Utility function to convert all types to an array.
     *
     * @param   mixed $data      The data to convert.
     * @param   bool  $recursive Recursive if data is nested.
     *
     * @return  array  The converted array.
     */
    public static function toArray($data, $recursive = false)
    {
        if ($data instanceof ValueReference) {
            return $data;
        }

        // Ensure the input data is an array.
        if ($data instanceof \Traversable) {
            $data = iterator_to_array($data);
        } elseif (is_object($data)) {
            $data = get_object_vars($data);
        } else {
            $data = (array) $data;
        }

        if ($recursive) {
            foreach ($data as &$value) {
                if (is_array($value) || is_object($value)) {
                    $value = static::toArray($value, $recursive);
                }
            }
        }

        return $data;
    }

    /**
     * dumpObjectValues
     *
     * @param   mixed $object
     *
     * @return  array
     */
    public static function dumpObjectValues($object)
    {
        $data = [];

        static::$objectStorage = new \SplObjectStorage();

        static::doDump($data, $object);

        return $data;
    }

    /**
     * doDump
     *
     * @param   array $data
     * @param   mixed $object
     *
     * @return  void
     */
    private static function doDump(&$data, $object)
    {
        if (is_object($object) && static::$objectStorage->contains($object)) {
            $data = null;

            return;
        }

        if (is_object($object)) {
            static::$objectStorage->attach($object);
        }

        if (is_array($object) || $object instanceof \Traversable) {
            foreach ($object as $key => $value) {
                static::doDump($data[$key], $value);
            }
        } elseif (is_object($object)) {
            $ref = new \ReflectionObject($object);

            $properties = $ref->getProperties();

            foreach ($properties as $property) {
                $property->setAccessible(true);

                $value = $property->getValue($object);

                static::doDump($data[$property->getName()], $value);
            }
        } else {
            $data = $object;
        }
    }
}