src/ConfigTrait.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

declare(strict_types=1);

namespace Atk4\Core;

use Symfony\Component\Yaml\Yaml;

/**
 * This trait makes it possible for you to read config files and various configurations
 * use:
 * 1. use Trait in your APP Class
 *    use \Atk4\Core\ConfigTrait;
 * 2. create config-default.php and/or config.php file and add config values like
 *    return ['key' => 'value'];
 * 3. call $this->readConfig();
 *    before using config.
 */
trait ConfigTrait
{
    /** @var array<string, mixed> This property stores config values. Use getConfig() method to access its values. */
    protected array $config = [];

    /**
     * Read config file or files and store it in $config property.
     *
     * Supported formats:
     *  php         - PHP file with return ['foo' => 'bar'] structure
     *  json        - JSON file with { 'foo': 'bar' } structure
     *  yaml        - YAML file with yaml structure
     *
     * @param string|list<string> $files  One or more filenames
     * @param string              $format Optional format for config files
     *
     * @return $this
     */
    public function readConfig($files = ['config.php'], string $format = 'php')
    {
        if (!is_array($files)) {
            $files = [$files];
        }

        $configs = [];
        foreach ($files as $file) {
            if (!is_readable($file)) {
                throw (new Exception('Cannot read config file'))
                    ->addMoreInfo('file', $file)
                    ->addMoreInfo('format', $format);
            }

            $tempConfig = [];

            switch (strtolower($format)) {
                case 'php':
                    $tempConfig = require $file;

                    break;
                case 'json':
                    $tempConfig = json_decode(file_get_contents($file), true);

                    break;
                case 'yaml':
                    $tempConfig = Yaml::parseFile($file);

                    break;
                default:
                    throw (new Exception('Unknown Format. Allowed formats: php, json, yml'))
                        ->addMoreInfo('file', $file)
                        ->addMoreInfo('format', $format);
            }

            if (!is_array($tempConfig)) {
                throw (new Exception('File was read but has a bad format'))
                    ->addMoreInfo('file', $file)
                    ->addMoreInfo('format', $format);
            }

            $configs[] = $tempConfig;
        }

        $this->config = array_replace_recursive($this->config, ...$configs);

        return $this;
    }

    /**
     * Manually set configuration option.
     *
     * @param string|array<string, mixed>       $paths Path to configuration element to set or array of [path => value]
     * @param ($paths is array ? never : mixed) $value Value to set
     *
     * @return $this
     */
    public function setConfig($paths = [], $value = null)
    {
        if (!is_array($paths)) {
            $paths = [$paths => $value];
        }
        unset($value);

        foreach ($paths as $path => $value) {
            $pos = &$this->_lookupConfigElement($path, true);

            if (is_array($pos) && count($pos) > 0 && is_array($value)) {
                // special treatment for arrays - merge them
                $pos = array_merge($pos, $value);
            } else {
                // otherwise just assign value
                $pos = $value;
            }
        }

        return $this;
    }

    /**
     * Get configuration element.
     *
     * @param string $path         path to configuration element
     * @param mixed  $defaultValue Default value returned if element don't exist
     *
     * @return mixed
     */
    public function getConfig(string $path, $defaultValue = null)
    {
        $pos = &$this->_lookupConfigElement($path, false);

        // path element don't exist - return default value
        if ($pos === false) {
            return $defaultValue;
        }

        return $pos;
    }

    /**
     * Internal method to lookup config element by given path.
     *
     * @param string $path           Path to navigate to
     * @param bool   $createElements Should we create elements it they don't exist
     *
     * @return mixed|false Pointer to element in $this->config or false is element don't exist and $createElements === false
     */
    private function &_lookupConfigElement(string $path, bool $createElements = false)
    {
        $path = explode('/', $path);
        $pos = &$this->config;
        foreach ($path as $el) {
            if (!is_array($pos) || !array_key_exists($el, $pos)) {
                if (!is_array($pos) || !$createElements) {
                    $res = false;

                    return $res;
                }

                $pos[$el] = [];
            }

            $pos = &$pos[$el];
        }

        return $pos;
    }
}