Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
ConfigContainer
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
4 / 4
10
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%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 get
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 isResolvedByContainer
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 * @link      https://github.com/php-fast-forward/config
12 * @copyright Copyright (c) 2025 Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
13 * @license   https://opensource.org/licenses/MIT MIT License
14 */
15
16namespace FastForward\Config\Container;
17
18use FastForward\Config\ConfigInterface;
19use FastForward\Config\Exception\ContainerNotFoundException;
20use Psr\Container\ContainerInterface;
21
22/**
23 * Class ConfigContainer.
24 *
25 * Provides a PSR-11 compatible container interface for accessing configuration values.
26 *
27 * This container implementation SHALL resolve configuration keys by prefix using the alias "config".
28 * For example, a request for "config.db.host" will attempt to fetch the key "db.host" from the underlying ConfigInterface.
29 *
30 * Identifiers such as 'config', ConfigInterface::class, and the concrete config class MUST return the configuration instance itself.
31 * Requests for unknown or invalid identifiers MUST result in a ContainerNotFoundException.
32 *
33 * @package FastForward\Config\Container
34 */
35final class ConfigContainer implements ContainerInterface
36{
37    /**
38     * @const string The standard identifier for retrieving the configuration object.
39     */
40    public const ALIAS = 'config';
41
42    /**
43     * Constructs a new ConfigContainer instance.
44     *
45     * This constructor SHALL wrap an existing ConfigInterface instance and expose it
46     * through PSR-11 `get()` and `has()` methods with namespace-style key resolution.
47     *
48     * @param ConfigInterface $config the configuration instance to expose as a container
49     */
50    public function __construct(
51        private ConfigInterface $config,
52    ) {}
53
54    /**
55     * Determines whether the container can return an entry for the given identifier.
56     *
57     * This method SHALL return true if:
58     * - The identifier matches known internal bindings (alias, interface, or class).
59     * - The identifier is prefixed with 'config' and corresponds to an existing key in the configuration.
60     *
61     * @param string $id identifier of the entry to look for
62     *
63     * @return bool true if the entry can be resolved; false otherwise
64     */
65    public function has(string $id): bool
66    {
67        if ($this->isResolvedByContainer($id)) {
68            return true;
69        }
70
71        if (!str_starts_with($id, self::ALIAS)) {
72            return false;
73        }
74
75        return $this->config->has(mb_substr($id, mb_strlen(self::ALIAS) + 1));
76    }
77
78    /**
79     * Retrieves an entry of the container by its identifier.
80     *
81     * This method SHALL resolve identifiers in the following order:
82     * - If the identifier matches 'config', ConfigInterface::class, or the concrete config class,
83     *   it SHALL return the ConfigInterface instance itself.
84     * - If the identifier is prefixed with 'config.', the suffix SHALL be used to query the configuration.
85     *   If the configuration key exists, its value SHALL be returned.
86     * - If the identifier cannot be resolved, a ContainerNotFoundException MUST be thrown.
87     *
88     * @param string $id identifier of the entry to retrieve
89     *
90     * @return mixed the value associated with the identifier
91     *
92     * @throws ContainerNotFoundException if the identifier cannot be resolved
93     */
94    public function get(string $id)
95    {
96        if (self::class === $id) {
97            return $this;
98        }
99
100        if ($this->isResolvedByContainer($id)) {
101            return $this->config;
102        }
103
104        if (str_starts_with($id, self::ALIAS)) {
105            $id = mb_substr($id, mb_strlen(self::ALIAS) + 1);
106
107            if ($this->config->has($id)) {
108                return $this->config->get($id);
109            }
110        }
111
112        throw ContainerNotFoundException::forKey($id);
113    }
114
115    /**
116     * Determines whether the given identifier is resolved internally by the container itself.
117     *
118     * This method SHALL match the identifier against:
119     * - the alias "config"
120     * - the ConfigInterface::class string
121     * - the concrete class of the injected configuration instance
122     *
123     * @param string $id the identifier to check
124     *
125     * @return bool true if the identifier is resolved internally; false otherwise
126     */
127    private function isResolvedByContainer(string $id): bool
128    {
129        return \in_array($id, [self::ALIAS, ConfigInterface::class, $this->config::class], true);
130    }
131}