Completed
Pull Request — master (#4433)
by Craig
04:46
created

Configurator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 2
c 1
b 0
f 1
nc 1
nop 1
dl 0
loc 4
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Zikula package.
7
 *
8
 * Copyright Zikula - https://ziku.la/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Zikula\Bundle\CoreBundle;
15
16
use InvalidArgumentException;
17
use Symfony\Component\Config\Definition\ConfigurationInterface;
18
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
19
use Symfony\Component\Config\Definition\Processor;
20
use Symfony\Component\Filesystem\Exception\IOException;
21
use Symfony\Component\Filesystem\Filesystem;
22
use Symfony\Component\Yaml\Yaml;
23
24
/**
25
 * This class manages configuration, validation and write-to-file for a select
26
 * subgroup of bundle configs (see $configurablePackages).
27
 *
28
 * To use, one must `loadPackages` by name. `Set` configuration as needed.
29
 * Then `write` the package config to the desired location. When the config
30
 * is loaded, the existing (if any) config settings are loaded in addition
31
 * to any default settings.
32
 *
33
 * The class defaults to write only the minimal required config (removing default
34
 * values) into the main `config/packages` (no env) directory.
35
 */
36
class Configurator
37
{
38
    /** @var string */
39
    private $configDir;
40
    /** @var Filesystem */
41
    private $fs;
42
    /** @var array */
43
    private $processedConfigurations = [];
44
    /** @var array */
45
    private $defaultConfigurations = [];
46
    /** @var string[] */
47
    private $configurablePackages = [
48
        'core' => 'Zikula\Bundle\CoreBundle\DependencyInjection\Configuration',
49
        'zikula_security_center' => 'Zikula\SecurityCenterModule\DependencyInjection\Configuration',
50
        'zikula_theme' => 'Zikula\ThemeModule\DependencyInjection\Configuration',
51
        'zikula_routes' => 'Zikula\RoutesModule\DependencyInjection\Configuration',
52
        'zikula_settings' => 'Zikula\SettingsModule\DependencyInjection\Configuration'
53
    ];
54
55
    public function __construct(string $projectDir)
56
    {
57
        $this->configDir = $projectDir . '/config';
58
        $this->fs = new Filesystem();
59
    }
60
61
    public function loadPackages($packages, string $env = 'prod'): void
62
    {
63
        if (!is_array($packages)) {
64
            $packages = (array) $packages;
65
        }
66
        foreach ($packages as $package) {
67
            if (!isset($this->configurablePackages[$package])) {
68
                throw new \InvalidArgumentException(sprintf('Package %s is not available for configuration. Please configure manually.', $package));
69
            }
70
            $configs = [];
71
            foreach ($this->getPaths($env) as $path) {
72
                if (file_exists($fullPath = $path . $package . '.yaml') && false !== $contents = file_get_contents($fullPath)) {
73
                    $configs[] = Yaml::parse($contents)[$package];
74
                }
75
            }
76
            $configuration = new $this->configurablePackages[$package]();
77
            $this->processedConfigurations[$package] = $this->process(
78
                $configuration,
79
                $configs
80
            );
81
            $this->defaultConfigurations[$package] = $this->process($configuration);
82
        }
83
    }
84
85
    /**
86
     * @throws InvalidConfigurationException
87
     */
88
    public function validatePackage(string $package): array
89
    {
90
        $config = $this->getAll($package);
91
        /** @var ConfigurationInterface $configuration */
92
        return $this->process(
93
            $configuration = new $this->configurablePackages[$package](),
94
            [$package => $config]
95
        );
96
    }
97
98
    /**
99
     * @throws InvalidConfigurationException
100
     */
101
    public function process(ConfigurationInterface $configuration, array $config = []): array
102
    {
103
        $processor = new Processor();
104
105
        return $processor->processConfiguration(
106
            $configuration,
107
            $config
108
        );
109
    }
110
111
    /**
112
     * @throws IOException if the file(s) cannot be written to
113
     */
114
    public function write(bool $min = true, string $env = ''): void
115
    {
116
        foreach (array_keys($this->processedConfigurations) as $package) {
117
            $this->validatePackage($package);
118
            $this->writePackage($package, $min, $env);
119
        }
120
    }
121
122
    /**
123
     * @throws IOException if the file cannot be written to
124
     */
125
    public function writePackage(string $package, bool $min = true, string $env = ''): void
126
    {
127
        $config = $min ? $this->arrayDiffAssocRecursive($this->processedConfigurations[$package], $this->getDefaults($package)) : $this->processedConfigurations[$package];
128
        $basePath = $this->configDir . '/packages/' . (!empty($env) ? $env . '/' : '');
129
        $path = $basePath . $package . '.yaml';
130
        if (!empty($config)) {
131
            $flags = Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE;
132
            $input = [$package => $config];
133
            $yaml = Yaml::dump($input, 4, 4, $flags);
134
            $this->fs->dumpFile($path, $yaml);
135
        } elseif ($this->fs->exists($path)) {
136
            $this->fs->remove($path);
137
        }
138
    }
139
140
    public function set(string $package, string $key, $value): void
141
    {
142
        if (!isset($this->processedConfigurations[$package][$key])) {
143
            throw new InvalidArgumentException(sprintf('Cannot set %s in the %s package because it is not configurable', $key, $package));
144
        }
145
146
        $this->processedConfigurations[$package][$key] = $value;
147
    }
148
149
    public function get(string $package, string $key)
150
    {
151
        if (!isset($this->processedConfigurations[$package][$key])) {
152
            throw new InvalidArgumentException(sprintf('The %s is not set in the %s package', $key, $package));
153
        }
154
155
        return $this->processedConfigurations[$package][$key];
156
    }
157
158
    public function getAll(string $package): array
159
    {
160
        if (!isset($this->processedConfigurations[$package])) {
161
            throw new InvalidArgumentException(sprintf('The %s package is not set', $package));
162
        }
163
164
        return $this->processedConfigurations[$package];
165
    }
166
167
    public function getDefaults(string $package): array
168
    {
169
        if (!isset($this->defaultConfigurations[$package])) {
170
            throw new InvalidArgumentException(sprintf('The %s package is not set', $package));
171
        }
172
173
        return $this->defaultConfigurations[$package];
174
    }
175
176
    private function getPaths(string $env = 'prod'): array
177
    {
178
        return [
179
            0 => $this->configDir . '/packages/',
180
            1 => $this->configDir . '/packages/' . $env,
181
        ];
182
    }
183
184
    /**
185
     * @author Giosh 2013-03-15
186
     * @see https://www.php.net/manual/en/function.array-diff-assoc.php#111675
187
     */
188
    public function arrayDiffAssocRecursive(array $array1, array $array2): array
189
    {
190
        $difference=[];
191
        foreach ($array1 as $key => $value) {
192
            if (is_array($value)) {
193
                if (!isset($array2[$key]) || !is_array($array2[$key])) {
194
                    $difference[$key] = $value;
195
                } else {
196
                    $newDiff = $this->arrayDiffAssocRecursive($value, $array2[$key]);
197
                    if (!empty($newDiff)) {
198
                        $difference[$key] = $newDiff;
199
                    }
200
                }
201
            } elseif (!array_key_exists($key, $array2) || $array2[$key] !== $value) {
202
                $difference[$key] = $value;
203
            }
204
        }
205
206
        return $difference;
207
    }
208
}
209