HippoPHP/Hippo

View on GitHub
src/Config/YAMLConfigReader.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php

/*
 * This file is part of Hippo.
 *
 * (c) James Brooks <james@alt-three.com>
 * (c) Marcin Kurczewski <rr-@sakuya.pl>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace HippoPHP\Hippo\Config;

use HippoPHP\Hippo\FileSystem;
use Symfony\Component\Yaml\Parser as YamlParser;

class YAMLConfigReader implements ConfigReaderInterface
{
    protected $parser;
    protected $fileSystem;

    /**
     * @param FileSystem $fileSystem
     */
    public function __construct(FileSystem $fileSystem)
    {
        $this->parser = new YamlParser();
        $this->fileSystem = $fileSystem;
    }

    /**
     * @param string $filename
     *
     * @return Config
     */
    public function loadFromFile($filename)
    {
        $config = $this->parseFile($filename);

        return $this->load($config, $filename);
    }

    /**
     * @param string $string
     *
     * @return Config
     */
    public function loadFromString($string)
    {
        $config = $this->parseString($string);

        return $this->load($config);
    }

    /**
     * @param array $config
     * @param mixed $filename
     *
     * @return Config
     */
    private function load($config, $filename = false)
    {
        if ($filename) {
            $included = [$this->normalizeConfigName($filename)];
        } else {
            $included = [];
        }

        while (isset($config['extends'])) {
            $baseConfigName = $config['extends'];
            $baseConfigDir = ($filename ? dirname($filename) : '.');
            $baseConfigPath = $baseConfigDir.DIRECTORY_SEPARATOR.$baseConfigName.'.yml';
            $baseConfig = $this->parseFile($baseConfigPath);
            unset($config['extends']);

            $config = $this->mergeRecursive($baseConfig, $config);

            if (isset($config['extends'])) {
                if (in_array($this->normalizeConfigName($config['extends']), $included)) {
                    // Avoid circular dependencies
                    unset($config['extends']);
                } else {
                    $included[] = $this->normalizeConfigName($config['extends']);
                }
            }
        }

        return new Config($config);
    }

    /**
     * @param string $filePath
     *
     * @return array<*,*>
     */
    private function parseFile($filePath)
    {
        return $this->parseString($this->fileSystem->getContent($filePath));
    }

    /**
     * @param string $string
     *
     * @return array<*,*>
     */
    private function parseString($string)
    {
        $result = $this->parser->parse($string);
        if (is_string($result)) {
            throw new \Exception('Config must be an array');
        }

        return $result;
    }

    /**
     * Normalizes a configuration filename.
     *
     * @param string $name
     *
     * @return string
     */
    private function normalizeConfigName($name)
    {
        return trim(basename(strtolower($name), '.yml'));
    }

    /**
     * @param array<*,*> $array1
     * @param array<*,*> $array2
     *
     * @return array<*,*>
     */
    private function mergeRecursive($array1, $array2)
    {
        $result = [];
        foreach (array_merge(array_keys($array1), array_keys($array2)) as $key) {
            if (!isset($array1[$key])) {
                $result[$key] = $array2[$key];
            } elseif (!isset($array2[$key])) {
                $result[$key] = $array1[$key];
            } elseif (is_array($array1[$key]) || is_array($array2[$key])) {
                if (!is_array($array1[$key]) || !is_array($array2[$key])) {
                    throw new \Exception('Cannot merge a scalar with an array');
                }
                $result[$key] = $this->mergeRecursive($array1[$key], $array2[$key]);
            } else {
                $result[$key] = $array2[$key];
            }
        }

        return $result;
    }
}