ConfigHandler::build()   B
last analyzed

Complexity

Conditions 4
Paths 8

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 29
ccs 0
cts 15
cp 0
rs 8.5806
cc 4
eloc 15
nc 8
nop 0
crap 20
1
<?php
2
/**
3
 * This file allows easily updating the configuration file when new code has
4
 * been pulled
5
 *
6
 * @license    https://github.com/allejo/bzion/blob/master/LICENSE.md GNU General Public License Version 3
7
 */
8
9
namespace BZIon\Composer;
10
11
use BZIon\Config\Configuration;
12
use Composer\Script\Event;
13
use Symfony\Component\Config\Definition\ArrayNode;
14
use Symfony\Component\Config\Definition\EnumNode;
15
use Symfony\Component\Config\Definition\NodeInterface;
16
use Symfony\Component\Config\Definition\PrototypedArrayNode;
17
use Symfony\Component\Yaml\Inline;
18
use Symfony\Component\Yaml\Yaml;
19
20
/**
21
 * A handler for the ignored config.yml file
22
 */
23
class ConfigHandler
24
{
25
    private $event;
26
    private $io;
27
28
    /**
29
     * Whether the user has received the short notice about configuration values
30
     * being able to get edited later on app/config.yml
31
     * @var bool
32
     */
33
    private $questionInfoShown;
34
35
    const CAUTION_LINE_LENGTH = 60;
36
37
    public function __construct($event)
38
    {
39
        $this->event = $event;
40
        $this->io = $event->getIO();
41
    }
42
43
    /**
44
     * Migrate the config.yml file
45
     */
46
    public function build()
47
    {
48
        $file = $this->getConfigurationPath();
49
        $exists = is_file($file);
50
51
        $configuration = new Configuration();
52
        $tree = $configuration->getConfigTreeBuilder()->buildTree();
53
54
        $action = $exists ? 'Updating' : 'Creating';
55
        $this->io->write(" <info>$action the \"$file\" file</info>\n");
56
57
        // Load the configuration file if it exists
58
        $config = $exists ? Yaml::parse($file) : array();
59
60
        if (!is_array($config)) {
61
            $config = array();
62
        }
63
64
        $this->writeNode($tree, $config);
65
66
        file_put_contents($file, Yaml::dump($config, 4));
67
68
        $this->io->write(<<<SUCCESS
69
<bg=green;options=bold>                                            </>
70
<bg=green;options=bold> [OK] The configuration file is up to date  </>
71
<bg=green;options=bold>                                            </>
72
SUCCESS
73
        );
74
    }
75
76
    /**
77
     * Write the node in the configuration array
78
     *
79
     * @param  NodeInterface $node   The node to write
80
     * @param  array         $config The parsed configuration
81
     * @param  string        $parent The name of the parent nodes
82
     * @return void
83
     */
84
    private function writeNode(NodeInterface $node, array &$config = array(), $parent = null)
85
    {
86
        if ($node->getAttribute('manual')) {
87
            return;
88
        }
89
90
        $name = $node->getName();
91
92
        if ($parent) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parent of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
93
            $name = $parent . '.' . $name;
94
        }
95
96
        if (!$node instanceof ArrayNode || $node instanceof PrototypedArrayNode) {
97
            if (!array_key_exists($node->getName(), $config)) {
98
                $config[$node->getName()] = $this->writeNodeQuestion($node, $name);
99
            }
100
        } else {
101
            if (!isset($config[$node->getName()]) || !is_array($config[$node->getName()])) {
102
                $config[$node->getName()] = array();
103
            }
104
105
            foreach ($node->getChildren() as $childNode) {
106
                $this->writeNode($childNode, $config[$node->getName()], $name);
107
            }
108
        }
109
    }
110
111
    /**
112
     * Present a node question to the user
113
     *
114
     * @param  NodeInterface $node The node in question
115
     * @param  string $name The name of the node
116
     * @return mixed The new value of the node
117
     */
118
    private function writeNodeQuestion($node, $name)
119
    {
120
        if (!$this->questionInfoShown) {
121
            $this->io->write(array(
122
                "<comment> ! [NOTE] You can change all the configuration options later",
123
                " ! on the config.yml file in the app folder</>\n"
124
            ));
125
126
            $this->questionInfoShown = true;
127
        }
128
129
        if (!$node->getAttribute('asked')) {
130
            return $node->getDefaultValue();
131
        }
132
133
        $this->writeWarning($node);
0 ignored issues
show
Documentation introduced by
$node is of type object<Symfony\Component...finition\NodeInterface>, but the function expects a object<BZIon\Composer\VariableNode>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
134
135
        if ($info = $node->getInfo()) {
136
            $this->io->write(" $info");
137
        }
138
139
        if ($example = $node->getExample()) {
140
            // We use Inline::dump() to convert the value to the YAML
141
            // format so that it is readable by the user (as an example,
142
            // false values are converted to 'false' instead of an empty
143
            // string)
144
            $example = Inline::dump($example);
145
            $this->io->write(" Example: <comment>$example</comment>");
146
        }
147
148
        $question = " <fg=green>$name";
149
150
        if ($node instanceof EnumNode) {
151
            // Create list of possible values for enum configuration parameters
152
            $values = $node->getValues();
153
154
            foreach ($values as &$value) {
155
                $value = Inline::dump($value);
156
            }
157
158
            $question .= ' (' . implode(', ', $values) . ')';
159
        }
160
161
        $question .= "</fg=green>";
162
163
        if ($node->hasDefaultValue()) {
164
            // Show the default value of the parameter
165
            $default = Inline::dump($node->getDefaultValue());
166
            $question .= " [<comment>$default</comment>]";
167
        } else {
168
            $default = null;
169
        }
170
171
        // Show a user-friendly prompt
172
        $question .= ":\n > ";
173
174
        $value = $this->io->askAndValidate($question, function ($value) use ($node) {
175
            $value = Inline::parse($value);
176
177
            // Make sure that there are no errors
178
            $node->finalize($value);
179
180
            return $value;
181
        }, null, $default);
182
183
        $this->io->write("");
184
185
        return $value;
186
    }
187
188
    /**
189
     * Write a warning if necessary
190
     *
191
     * @param VariableNode $node The node with the warning
192
     */
193
    private function writeWarning($node)
194
    {
195
        if (!$node->hasAttribute('warning')) {
196
            return;
197
        }
198
199
        // Split warning into words so that we can apply wrapping
200
        $words = preg_split('/\s+/', $node->getAttribute('warning'));
201
202
        $caution = ' ! [CAUTION]';
203
        $currentLength = 0;
204
205
        foreach ($words as $word) {
206
            if ($currentLength > self::CAUTION_LINE_LENGTH) {
207
                $caution .= "\n !";
208
                $currentLength = 0;
209
            }
210
211
            $caution .= ' ' . $word;
212
            $currentLength += strlen($word) + 1;
213
        }
214
215
        $this->io->write("<warning>\n\n$caution\n</>");
216
    }
217
218
    /**
219
     * Returns the path to the configuration file
220
     *
221
     * @return string
222
     */
223 1
    public static function getConfigurationPath()
224
    {
225 1
        return realpath(__DIR__ . '/../../app') . '/config.yml';
226
    }
227
}
228