Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
ArrayConfig
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
7 / 7
12
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 has
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 set
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 remove
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getIterator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toArray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5/**
6 * This file is part of php-fast-forward/config.
7 *
8 * This source file is subject to the license bundled
9 * with this source code in the file LICENSE.
10 *
11 * @copyright Copyright (c) 2025-2026 Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
12 * @license   https://opensource.org/licenses/MIT MIT License
13 *
14 * @see       https://github.com/php-fast-forward/config
15 * @see       https://github.com/php-fast-forward
16 * @see       https://datatracker.ietf.org/doc/html/rfc2119
17 */
18
19namespace FastForward\Config;
20
21use Traversable;
22use Dflydev\DotAccessData\Data;
23use FastForward\Config\Exception\InvalidArgumentException;
24use FastForward\Config\Helper\ConfigHelper;
25
26/**
27 * Class ArrayConfig.
28 *
29 * Provides a configuration management system with dot notation access.
30 * This class SHALL encapsulate configuration data using the DotAccessData library.
31 * It MUST support nested keys and provide export, iteration, and dynamic update capabilities.
32 */
33 class ArrayConfig implements ConfigInterface
34{
35    use ArrayAccessConfigTrait;
36
37    /**
38     * @var Data internal configuration storage instance
39     */
40    private Data $data;
41
42    /**
43     * Constructs the ArrayConfig instance.
44     *
45     * This constructor SHALL initialize the internal configuration store using normalized keys.
46     *
47     * @param array $config an optional initial configuration array
48     */
49    public function __construct(array $config = [])
50    {
51        $this->data = new Data(data: ConfigHelper::normalize($config));
52    }
53
54    /**
55     * Determines whether the given configuration key exists.
56     *
57     * @param string $key the dot-notation configuration key to check
58     *
59     * @return bool TRUE if the key exists, FALSE otherwise
60     */
61    public function has(string $key): bool
62    {
63        return $this->data->has($key);
64    }
65
66    /**
67     * Retrieves a value from the configuration.
68     *
69     * If the value is a nested associative array, a new instance of ArrayConfig SHALL be returned.
70     * If the key is not found and no default is provided, a MissingPathException SHOULD be thrown.
71     *
72     * @param string $key the configuration key to retrieve
73     * @param mixed $default the default value to return if the key does not exist
74     *
75     * @return mixed|self the configuration value, or a nested ArrayConfig instance
76     */
77    public function get(string $key, mixed $default = null): mixed
78    {
79        $value = $this->data->get($key, $default);
80
81        if (ConfigHelper::isAssoc($value)) {
82            return new self($value);
83        }
84
85        return $value;
86    }
87
88    /**
89     * Sets configuration values into the internal data store.
90     *
91     * If a value is provided, the key MUST be a string. If the input is an associative array
92     * or another ConfigInterface instance, it SHALL be normalized before insertion.
93     *
94     * @param array|ConfigInterface|string $key the configuration key(s) or configuration object
95     * @param mixed|null $value the value to assign to the specified key, if applicable
96     *
97     * @return void
98     *
99     * @throws InvalidArgumentException if the key is not a string when a value is provided
100     */
101    public function set(array|ConfigInterface|string $key, mixed $value = null): void
102    {
103        if (! empty($value)) {
104            if (! \is_string($key)) {
105                throw InvalidArgumentException::forNonStringKeyWithValue();
106            }
107
108            $key = [
109                $key => $value,
110            ];
111        }
112
113        if ($key instanceof ConfigInterface) {
114            $key = $key->toArray();
115        }
116
117        $this->data->import(ConfigHelper::normalize($key));
118    }
119
120    /**
121     * Removes a configuration key and its associated value.
122     *
123     * If the key does not exist, this method SHALL do nothing.
124     *
125     * @param string $key the configuration key to remove
126     *
127     * @return void
128     */
129    public function remove(string $key): void
130    {
131        if ($this->has($key)) {
132            $this->data->remove($key);
133        }
134    }
135
136    /**
137     * Retrieves a traversable set of flattened configuration data.
138     *
139     * This method SHALL return an iterator where each key represents
140     * the nested path in dot notation, and each value is the corresponding value.
141     * For example ['database' => ['host' => 'localhost']] becomes ['database.host' => 'localhost'].
142     *
143     * @return Traversable<string, mixed> an iterator of flattened key-value pairs
144     */
145    public function getIterator(): Traversable
146    {
147        return ConfigHelper::flatten($this->toArray());
148    }
149
150    /**
151     * Converts the entire configuration to an associative array.
152     *
153     * This method MUST export the configuration in its current state.
154     *
155     * @return array the exported configuration array
156     */
157    public function toArray(): array
158    {
159        return $this->data->export();
160    }
161}