Completed
Push — master ( f60e47...74fc93 )
by Sandro
02:28
created

src/ConfigurationTrait.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Sandro Keil (https://sandro-keil.de)
4
 *
5
 * @link      http://github.com/sandrokeil/interop-config for the canonical source repository
6
 * @copyright Copyright (c) 2015-2016 Sandro Keil
7
 * @license   http://github.com/sandrokeil/interop-config/blob/master/LICENSE.md New BSD License
8
 */
9
10
declare(strict_types = 1);
11
12
namespace Interop\Config;
13
14
use ArrayAccess;
15
use Interop\Config\Exception;
16
use Iterator;
17
18
/**
19
 * ConfigurationTrait which retrieves options from configuration, see interface \Interop\Config\RequiresConfig
20
 *
21
 * This trait is a implementation of \Interop\Config\RequiresConfig. Retrieves options from a configuration and optional
22
 * to perform a mandatory option check. Default options are merged and overridden of the provided options.
23
 */
24
trait ConfigurationTrait
25
{
26
    /**
27
     * @inheritdoc \Interop\Config\RequiresConfig::dimensions
28
     */
29
    abstract public function dimensions(): iterable;
30
31
    /**
32
     * Checks if options are available depending on implemented interfaces and checks that the retrieved options from
33
     * the dimensions path are an array or have implemented \ArrayAccess. The RequiresConfigId interface is supported.
34
     *
35
     * `canRetrieveOptions()` returning true does not mean that `options($config)` will not throw an exception.
36
     * It does however mean that `options()` will not throw an `OptionNotFoundException`. Mandatory options are
37
     * not checked.
38
     *
39
     * @param array|ArrayAccess $config Configuration
40
     * @param string|null $configId Config name, must be provided if factory uses RequiresConfigId interface
41
     * @return bool True if options depending on dimensions are available, otherwise false
42
     */
43 65
    public function canRetrieveOptions($config, string $configId = null): bool
44
    {
45 65
        $dimensions = $this->dimensions();
46 65
        $dimensions = $dimensions instanceof Iterator ? iterator_to_array($dimensions) : $dimensions;
47
48 65
        if ($this instanceof RequiresConfigId) {
49 46
            $dimensions[] = $configId;
50
        }
51
52 65
        foreach ($dimensions as $dimension) {
53 62
            if (((array)$config !== $config && !$config instanceof ArrayAccess)
54 62
                || (!isset($config[$dimension]) && $this instanceof RequiresMandatoryOptions)
55 62
                || (!isset($config[$dimension]) && !$this instanceof ProvidesDefaultOptions)
56
            ) {
57 14
                return false;
58
            }
59 61
            if ($this instanceof ProvidesDefaultOptions && !isset($config[$dimension])) {
60 1
                return true;
61
            }
62
63 60
            $config = $config[$dimension];
64
        }
65 55
        return (array)$config === $config || $config instanceof ArrayAccess;
66
    }
67
68
    /**
69
     * Returns options based on dimensions() like [vendor][package] and can perform mandatory option checks if
70
     * class implements RequiresMandatoryOptions. If the ProvidesDefaultOptions interface is implemented, these options
71
     * must be overridden by the provided config. If you want to allow configurations for more then one instance use
72
     * RequiresConfigId interface.
73
     *
74
     * The RequiresConfigId interface is supported.
75
     *
76
     * @param array|ArrayAccess $config Configuration
77
     * @param string $configId Config name, must be provided if factory uses RequiresConfigId interface
78
     * @return array|ArrayAccess
79
     * @throws Exception\InvalidArgumentException If the $configId parameter is provided but factory does not support it
80
     * @throws Exception\UnexpectedValueException If the $config parameter has the wrong type
81
     * @throws Exception\OptionNotFoundException If no options are available
82
     * @throws Exception\MandatoryOptionNotFoundException If a mandatory option is missing
83
     */
84 61
    public function options($config, string $configId = null)
85
    {
86 61
        $dimensions = $this->dimensions();
87 61
        $dimensions = $dimensions instanceof Iterator ? iterator_to_array($dimensions) : $dimensions;
88
89 61
        if ($this instanceof RequiresConfigId) {
90 41
            $dimensions[] = $configId;
91 20
        } elseif ($configId !== null) {
92 1
            throw new Exception\InvalidArgumentException(
93 1
                sprintf('The factory "%s" does not support multiple instances.', __CLASS__)
94
            );
95
        }
96
97
        // get configuration for provided dimensions
98 60
        foreach ($dimensions as $dimension) {
99 57
            if ((array)$config !== $config && !$config instanceof ArrayAccess) {
100 1
                throw Exception\UnexpectedValueException::invalidOptions($dimensions, $dimension);
0 ignored issues
show
It seems like $dimensions defined by $dimensions instanceof \...mensions) : $dimensions on line 87 can also be of type array; however, Interop\Config\Exception...ption::invalidOptions() does only seem to accept object<Interop\Config\Exception\iterable>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
101
            }
102
103 57
            if (!isset($config[$dimension])) {
104 10
                if (!$this instanceof RequiresMandatoryOptions && $this instanceof ProvidesDefaultOptions) {
105 2
                    break;
106
                }
107 8
                throw Exception\OptionNotFoundException::missingOptions($this, $dimension, $configId);
108
            }
109 54
            $config = $config[$dimension];
110
        }
111
112 51
        if ((array)$config !== $config && !$config instanceof ArrayAccess) {
113 1
            throw Exception\UnexpectedValueException::invalidOptions($this->dimensions());
114
        }
115
116 50
        if ($this instanceof RequiresMandatoryOptions) {
117 36
            $this->checkMandatoryOptions($this->mandatoryOptions(), $config);
118
        }
119
120 44
        if ($this instanceof ProvidesDefaultOptions) {
121 20
            $options = $this->defaultOptions();
122
123 20
            $config = array_replace_recursive(
124 20
                $options instanceof Iterator ? iterator_to_array($options) : (array)$options,
125 20
                (array)$config
126
            );
127
        }
128 44
        return $config;
129
    }
130
131
    /**
132
     * Checks if options can be retrieved from config and if not, default options (ProvidesDefaultOptions interface) or
133
     * an empty array will be returned.
134
     *
135
     * @param array|ArrayAccess $config Configuration
136
     * @param string $configId Config name, must be provided if factory uses RequiresConfigId interface
137
     * @return array|ArrayAccess options Default options or an empty array
138
     */
139 3
    public function optionsWithFallback($config, string $configId = null)
140
    {
141 3
        $options = [];
142
143 3
        if ($this->canRetrieveOptions($config, $configId)) {
144 3
            $options = $this->options($config, $configId);
145
        }
146 3
        if (empty($options) && $this instanceof ProvidesDefaultOptions) {
147 3
            $options = $this->defaultOptions();
148
        }
149 3
        return $options;
150
    }
151
152
    /**
153
     * Checks if a mandatory param is missing, supports recursion
154
     *
155
     * @param iterable $mandatoryOptions
156
     * @param array|ArrayAccess $config
157
     * @throws Exception\MandatoryOptionNotFoundException
158
     */
159 19
    private function checkMandatoryOptions(iterable $mandatoryOptions, $config): void
160
    {
161 19
        foreach ($mandatoryOptions as $key => $mandatoryOption) {
162 19
            $useRecursion = !is_scalar($mandatoryOption);
163
164 19
            if (!$useRecursion && isset($config[$mandatoryOption])) {
165 18
                continue;
166
            }
167
168 13
            if ($useRecursion && isset($config[$key])) {
169 9
                $this->checkMandatoryOptions($mandatoryOption, $config[$key]);
170 6
                return;
171
            }
172
173 7
            throw Exception\MandatoryOptionNotFoundException::missingOption(
174 7
                $this->dimensions(),
175 7
                $useRecursion ? $key : $mandatoryOption
176
            );
177
        }
178 12
    }
179
}
180