Passed
Push — master ( 85012e...9f5f8f )
by Sebastian
02:03
created

Factory::combineArgumentsAndSettingFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 8
ccs 8
cts 8
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 2
1
<?php
2
3
/**
4
 * This file is part of CaptainHook
5
 *
6
 * (c) Sebastian Feldmann <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace CaptainHook\App\Config;
13
14
use CaptainHook\App\CH;
15
use CaptainHook\App\Config;
16
use CaptainHook\App\Hook\Util as HookUtil;
17
use CaptainHook\App\Storage\File\Json;
18
use RuntimeException;
19
20
/**
21
 * Class Factory
22
 *
23
 * @package CaptainHook
24
 * @author  Sebastian Feldmann <[email protected]>
25
 * @link    https://github.com/captainhookphp/captainhook
26
 * @since   Class available since Release 0.9.0
27
 * @internal
28
 */
29
final class Factory
30
{
31
    /**
32
     * Maximal level in including config files
33
     *
34
     * @var int
35
     */
36
    private $maxIncludeLevel = 1;
37
38
    /**
39
     * Current level of inclusion
40
     *
41
     * @var int
42
     */
43
    private $includeLevel = 0;
44
45
    /**
46
     * Create a CaptainHook configuration
47
     *
48
     * @param  string $path
49
     * @param  array  $settings
50
     * @return \CaptainHook\App\Config
51 32
     * @throws \Exception
52
     */
53 32
    public function createConfig(string $path = '', array $settings = []): Config
54 32
    {
55
        $path = $path ?: getcwd() . DIRECTORY_SEPARATOR . CH::CONFIG;
56 32
        $file = new Json($path);
57
58
        return $this->setupConfig($file, $settings);
59
    }
60
61
    /**
62
     * Includes a external captainhook configuration
63
     *
64 6
     * @param  string $path
65
     * @return \CaptainHook\App\Config
66 6
     * @throws \Exception
67 6
     */
68 1
    private function includeConfig(string $path): Config
69
    {
70 5
        $file = new Json($path);
71
        if (!$file->exists()) {
72
            throw new RuntimeException('Config to include not found: ' . $path);
73
        }
74
        return $this->setupConfig($file);
75
    }
76
77
    /**
78
     * Return a configuration with data loaded from json file it it exists
79
     *
80
     * @param  \CaptainHook\App\Storage\File\Json $file
81
     * @param  array                              $settings
82 32
     * @return \CaptainHook\App\Config
83
     * @throws \Exception
84 32
     */
85 27
    private function setupConfig(Json $file, array $settings = []): Config
86 31
    {
87
        return $file->exists()
88
            ? $this->loadConfigFromFile($file, $settings)
89
            : new Config($file->getPath(), false, $settings);
90
    }
91
92
    /**
93
     * Loads a given file into given the configuration
94
     *
95
     * @param  \CaptainHook\App\Storage\File\Json $file
96
     * @param  array                              $settings
97 27
     * @return \CaptainHook\App\Config
98
     * @throws \Exception
99 27
     */
100 27
    private function loadConfigFromFile(Json $file, array $settings): Config
101
    {
102 27
        $json = $file->readAssoc();
103 27
        Util::validateJsonConfiguration($json);
104
105 27
        $settings = $this->combineArgumentsAndSettingFile($file, $settings);
106
        $settings = array_merge($this->extractSettings($json), $settings);
107 26
        $config   = new Config($file->getPath(), true, $settings);
108 26
109 26
        $this->appendIncludedConfigurations($config, $json);
110
111
        foreach (HookUtil::getValidHooks() as $hook => $class) {
112 26
            if (isset($json[$hook])) {
113
                $this->configureHook($config->getHookConfig($hook), $json[$hook]);
114
            }
115
        }
116
        return $config;
117
    }
118
119
    /**
120
     * Read settings from a local 'config' file
121 27
     *
122
     * If you prefer a different verbosity or use a different run mode locally then your team mates do.
123 27
     * You can create a 'captainhook.config.json' in the same directory as your captainhook
124
     * configuration file and use it to overwrite the 'config' settings of that configuration file.
125
     * Exclude the 'captainhook.config.json' from version control and you don't have to edit the
126
     * version controlled configuration for your local specifics anymore.
127
     *
128
     * Settings provided as arguments still overrule config file settings:
129
     *
130
     * ARGUMENTS > SETTINGS_FILE > CONFIGURATION
131
     *
132
     * @param \CaptainHook\App\Storage\File\Json $file
133
     * @param array                              $settings
134 25
     * @return array
135
     */
136 25
    private function combineArgumentsAndSettingFile(Json $file, array $settings)
137 25
    {
138 19
        $settingsFile = new Json(dirname($file->getPath()) . '/captainhook.config.json');
139 19
        if ($settingsFile->exists()) {
140 19
            $fileSettings = $settingsFile->readAssoc();
141 19
            $settings     = array_merge($fileSettings, $settings);
142 1
        }
143 19
        return $settings;
144 19
    }
145
146 25
    /**
147
     * Return `config` section of captainhook.json
148
     *
149
     * @param  array $json
150
     * @return array
151
     */
152
    private function extractSettings(array $json): array
153
    {
154
        return isset($json['config']) && is_array($json['config']) ? $json['config'] : [];
155 27
    }
156
157 27
    /**
158 27
     * Setup a hook configuration by json data
159 27
     *
160 26
     * @param  \CaptainHook\App\Config\Hook $config
161 26
     * @param  array                        $json
162
     * @return void
163
     * @throws \Exception
164 26
     */
165 26
    private function configureHook(Config\Hook $config, array $json): void
166
    {
167
        $config->setEnabled($json['enabled']);
168
        foreach ($json['actions'] as $actionJson) {
169
            $options    = isset($actionJson['options']) && is_array($actionJson['options'])
170
                        ? $actionJson['options']
171
                        : [];
172 27
            $conditions = isset($actionJson['conditions']) && is_array($actionJson['conditions'])
173
                        ? $actionJson['conditions']
174
                        : [];
175 27
            $config->addAction(new Config\Action($actionJson['action'], $options, $conditions));
176 1
        }
177
    }
178 27
179
    /**
180
     * Append all included configuration to the current configuration
181
     *
182
     * @param  \CaptainHook\App\Config $config
183
     * @param  array                   $json
184
     * @throws \Exception
185
     */
186
    private function appendIncludedConfigurations(Config $config, array $json)
187 26
    {
188
        $this->readMaxIncludeLevel($json);
189 26
        if ($this->includeLevel < $this->maxIncludeLevel) {
190 5
            $includes = $this->loadIncludedConfigs($json, $config->getPath());
191 5
            foreach (HookUtil::getValidHooks() as $hook => $class) {
192 5
                $this->mergeHookConfigFromIncludes($config->getHookConfig($hook), $includes);
193 5
            }
194
        }
195
        $this->includeLevel++;
196 26
    }
197
198
    /**
199
     * Check config section for 'includes-level' setting
200
     *
201
     * @param array $json
202
     */
203
    private function readMaxIncludeLevel(array $json): void
204
    {
205
        // read the include level setting only for the actual configuration
206 27
        if ($this->includeLevel === 0 && isset($json['config'][Config::SETTING_INCLUDES_LEVEL])) {
207
            $this->maxIncludeLevel = (int) $json['config'][Config::SETTING_INCLUDES_LEVEL];
208 27
        }
209 27
    }
210 27
211 27
    /**
212 6
     * Merge a given hook config with the corresponding hook configs from a list of included configurations
213 27
     *
214
     * @param  \CaptainHook\App\Config\Hook $hook
215 27
     * @param  \CaptainHook\App\Config[]    $includes
216 6
     * @return void
217
     */
218 26
    private function mergeHookConfigFromIncludes(Hook $hook, array $includes): void
219
    {
220
        foreach ($includes as $includedConfig) {
221
            $includedHook = $includedConfig->getHookConfig($hook->getName());
222
            if ($includedHook->isEnabled()) {
223
                $hook->setEnabled(true);
224
                $this->copyActionsFromTo($includedHook, $hook);
225
            }
226
        }
227 5
    }
228
229 5
    /**
230 5
     * Return list of included configurations to add them to the main configuration afterwards
231
     *
232 5
     * @param  array  $json
233
     * @param  string $path
234
     * @return \CaptainHook\App\Config[]
235
     * @throws \Exception
236
     */
237
    protected function loadIncludedConfigs(array $json, string $path): array
238
    {
239
        $includes  = [];
240
        $directory = dirname($path);
241
        $files     = isset($json['config'][Config::SETTING_INCLUDES])
242 32
                     && is_array($json['config'][Config::SETTING_INCLUDES])
243
                   ? $json['config'][Config::SETTING_INCLUDES]
244 32
                   : [];
245 32
246
        foreach ($files as $file) {
247
            $includes[] = $this->includeConfig($directory . DIRECTORY_SEPARATOR . $file);
248
        }
249
        return $includes;
250
    }
251
252
    /**
253
     * Copy action from a given configuration to the second given configuration
254
     *
255
     * @param \CaptainHook\App\Config\Hook $sourceConfig
256
     * @param \CaptainHook\App\Config\Hook $targetConfig
257
     */
258
    private function copyActionsFromTo(Hook $sourceConfig, Hook $targetConfig)
259
    {
260
        foreach ($sourceConfig->getActions() as $action) {
261
            $targetConfig->addAction($action);
262
        }
263
    }
264
265
    /**
266
     * Config factory method
267
     *
268
     * @param  string $path
269
     * @param  array  $settings
270
     * @return \CaptainHook\App\Config
271
     * @throws \Exception
272
     */
273
    public static function create(string $path = '', array $settings = []): Config
274
    {
275
        $factory = new static();
276
        return $factory->createConfig($path, $settings);
277
    }
278
}
279