Completed
Pull Request — master (#4433)
by Craig
06:19
created

Configurator::writePackage()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 3
eloc 7
c 1
b 0
f 1
nc 4
nop 3
dl 0
loc 10
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
//        'jms_i18n_routing' => 'JMS\I18nRoutingBundle\DependencyInjection\Configuration',
52
//        'bazinga_js_translation' => 'Bazinga\Bundle\JsTranslationBundle\DependencyInjection\Configuration',
53
//        'php_translation' => 'Translation\Bundle\DependencyInjection\Configuration',
54
//        'translation' => 'Symfony\Bundle\FrameworkBundle\DependencyInjection\Configuration',
55
    ];
56
57
    public function __construct(string $projectDir)
58
    {
59
        $this->configDir = $projectDir . '/config';
60
        $this->fs = new Filesystem();
61
    }
62
63
    public function loadPackages($packages, string $env = 'prod'): void
64
    {
65
        if (!is_array($packages)) {
66
            $packages = (array) $packages;
67
        }
68
        foreach ($packages as $package) {
69
            if (!isset($this->configurablePackages[$package])) {
70
                throw new \InvalidArgumentException(sprintf('Package %s is not available for configuration. Please configure manually.', $package));
71
            }
72
            $configs = [];
73
            foreach ($this->getPaths($env) as $path) {
74
                if (file_exists($fullPath = $path . $package . '.yaml') && false !== $contents = file_get_contents($fullPath)) {
75
                    $configs[] = Yaml::parse($contents)[$package];
76
                }
77
            }
78
            $configuration = new $this->configurablePackages[$package]();
79
            $this->processedConfigurations[$package] = $this->process(
80
                $configuration,
81
                $configs
82
            );
83
            $this->defaultConfigurations[$package] = $this->process($configuration);
84
        }
85
    }
86
87
    /**
88
     * @throws InvalidConfigurationException
89
     */
90
    public function validatePackage(string $package): array
91
    {
92
        $config = $this->getAll($package);
93
        /** @var ConfigurationInterface $configuration */
94
        return $this->process(
95
            $configuration = new $this->configurablePackages[$package](),
96
            // $package could be wrong key here - maybe need to get configuration root
97
            [$package => $config]
98
        );
99
    }
100
101
    /**
102
     * @throws InvalidConfigurationException
103
     */
104
    public function process(ConfigurationInterface $configuration, array $config = []): array
105
    {
106
        $processor = new Processor();
107
        return $processor->processConfiguration(
108
            $configuration,
109
            $config
110
        );
111
    }
112
113
    /**
114
     * @throws IOException if the file(s) cannot be written to
115
     */
116
    public function write(bool $min = true, string $env = ''): void
117
    {
118
        foreach (array_keys($this->processedConfigurations) as $package) {
119
            $this->validatePackage($package);
120
            $this->writePackage($package, $min, $env);
121
        }
122
    }
123
124
    /**
125
     * @throws IOException if the file cannot be written to
126
     */
127
    public function writePackage(string $package, bool $min = true, string $env = ''): void
128
    {
129
        $config = $min ? $this->arrayDiffAssocRecursive($this->processedConfigurations[$package], $this->getDefaults($package)) : $this->processedConfigurations[$package];
130
        $flags = Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE;
131
        // $package could be wrong key here - maybe need to get configuration root
132
        $input = [$package => $config];
133
        $yaml = Yaml::dump($input, 4, 4, $flags);
134
        $basePath = $this->configDir . '/packages/' . (!empty($env) ? $env . '/' : '');
135
        $path = $basePath . $package . '.yaml';
136
        $this->fs->dumpFile($path, $yaml);
137
    }
138
139
    public function set(string $package, string $key, $value): void
140
    {
141
        if (!isset($this->processedConfigurations[$package][$key])) {
142
            throw new InvalidArgumentException(sprintf('Cannot set %s in the %s package because it is not configurable', $key, $package));
143
        }
144
145
        $this->processedConfigurations[$package][$key] = $value;
146
    }
147
148
    public function get(string $package, string $key)
149
    {
150
        if (!isset($this->processedConfigurations[$package][$key])) {
151
            throw new InvalidArgumentException(sprintf('The %s is not set in the %s package', $key, $package));
152
        }
153
154
        return $this->processedConfigurations[$package][$key];
155
    }
156
157
    public function getAll(string $package): array
158
    {
159
        if (!isset($this->processedConfigurations[$package])) {
160
            throw new InvalidArgumentException(sprintf('The %s package is not set', $package));
161
        }
162
163
        return $this->processedConfigurations[$package];
164
    }
165
166
    public function getDefaults(string $package): array
167
    {
168
        if (!isset($this->defaultConfigurations[$package])) {
169
            throw new InvalidArgumentException(sprintf('The %s package is not set', $package));
170
        }
171
172
        return $this->defaultConfigurations[$package];
173
    }
174
175
    private function getPaths(string $env = 'prod'): array
176
    {
177
        return [
178
            0 => $this->configDir . '/packages/',
179
            1 => $this->configDir . '/packages/' . $env,
180
        ];
181
    }
182
183
    /**
184
     * @author Giosh 2013-03-15
185
     * @see https://www.php.net/manual/en/function.array-diff-assoc.php#111675
186
     */
187
    public function arrayDiffAssocRecursive(array $array1, array $array2): array
188
    {
189
        $difference=[];
190
        foreach($array1 as $key => $value) {
191
            if (is_array($value)) {
192
                if (!isset($array2[$key]) || !is_array($array2[$key])) {
193
                    $difference[$key] = $value;
194
                } else {
195
                    $newDiff = $this->arrayDiffAssocRecursive($value, $array2[$key]);
196
                    if (!empty($newDiff)) {
197
                        $difference[$key] = $newDiff;
198
                    }
199
                }
200
            } else if (!array_key_exists($key,$array2) || $array2[$key] !== $value) {
201
                $difference[$key] = $value;
202
            }
203
        }
204
205
        return $difference;
206
    }
207
}
208