Completed
Push — fm-matches ( f867e2...7fc64f )
by Vladimir
14:12
created

ConfigHandler::writeNode()   C

Complexity

Conditions 8
Paths 13

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 26
ccs 0
cts 14
cp 0
rs 5.3846
cc 8
eloc 14
nc 13
nop 3
crap 72
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
     * @param $event Event Composer's event
47
     */
48
    public function build()
49
    {
50
        $file = $this->getConfigurationPath();
51
        $exists = is_file($file);
52
53
        $configuration = new Configuration();
54
        $tree = $configuration->getConfigTreeBuilder()->buildTree();
55
56
        $action = $exists ? 'Updating' : 'Creating';
57
        $this->io->write(" <info>$action the \"$file\" file</info>\n");
58
59
        // Load the configuration file if it exists
60
        $config = $exists ? Yaml::parse($file) : array();
61
62
        if (!is_array($config)) {
63
            $config = array();
64
        }
65
66
        $this->writeNode($tree, $config);
67
68
        file_put_contents($file, Yaml::dump($config, 4));
69
70
        $this->io->write(<<<SUCCESS
71
<bg=green;options=bold>                                            </>
72
<bg=green;options=bold> [OK] The configuration file is up to date  </>
73
<bg=green;options=bold>                                            </>
74
SUCCESS
75
        );
76
    }
77
78
    /**
79
     * Write the node in the configuration array
80
     *
81
     * @param  NodeInterface $node   The node to write
82
     * @param  array         $config The parsed configuration
83
     * @param  string        $parent The name of the parent nodes
84
     * @return void
85
     */
86
    private function writeNode(NodeInterface $node, array &$config = array(), $parent = null)
87
    {
88
        if ($node->getAttribute('manual')) {
89
            return;
90
        }
91
92
        $name = $node->getName();
93
94
        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...
95
            $name = $parent . '.' . $name;
96
        }
97
98
        if (!$node instanceof ArrayNode || $node instanceof PrototypedArrayNode) {
99
            if (!array_key_exists($node->getName(), $config)) {
100
                $config[$node->getName()] = $this->writeNodeQuestion($node, $name);
101
            }
102
        } else {
103
            if (!isset($config[$node->getName()])) {
104
                $config[$node->getName()] = array();
105
            }
106
107
            foreach ($node->getChildren() as $childNode) {
108
                $this->writeNode($childNode, $config[$node->getName()], $name);
109
            }
110
        }
111
    }
112
113
    /**
114
     * Present a node question to the user
115
     *
116
     * @param  NodeInterface $node The node in question
117
     * @param  string $name The name of the node
118
     * @return mixed The new value of the node
119
     */
120
    private function writeNodeQuestion($node, $name)
121
    {
122
        if (!$this->questionInfoShown) {
123
            $this->io->write(array(
124
                "<comment> ! [NOTE] You can change all the configuration options later",
125
                " ! on the config.yml file in the app folder</>\n"
126
            ));
127
128
            $this->questionInfoShown = true;
129
        }
130
131
        if (!$node->getAttribute('asked')) {
132
            return $node->getDefaultValue();
133
        }
134
135
        $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...
136
137
        if ($info = $node->getInfo()) {
138
            $this->io->write(" $info");
139
        }
140
141
        if ($example = $node->getExample()) {
142
            // We use Inline::dump() to convert the value to the YAML
143
            // format so that it is readable by the user (as an example,
144
            // false values are converted to 'false' instead of an empty
145
            // string)
146
            $example = Inline::dump($example);
147
            $this->io->write(" Example: <comment>$example</comment>");
148
        }
149
150
        $question = " <fg=green>$name";
151
152
        if ($node instanceof EnumNode) {
153
            // Create list of possible values for enum configuration parameters
154
            $values = $node->getValues();
155
156
            foreach ($values as &$value) {
157
                $value = Inline::dump($value);
158
            }
159
160
            $question .= ' (' . implode(', ', $values) . ')';
161
        }
162
163
        $question .= "</fg=green>";
164
165
        if ($node->hasDefaultValue()) {
166
            // Show the default value of the parameter
167
            $default = Inline::dump($node->getDefaultValue());
168
            $question .= " [<comment>$default</comment>]";
169
        } else {
170
            $default = null;
171
        }
172
173
        // Show a user-friendly prompt
174
        $question .= ":\n > ";
175
176
        $value = $this->io->askAndValidate($question, function ($value) use ($node) {
177
            $value = Inline::parse($value);
178
179
            // Make sure that there are no errors
180
            $node->finalize($value);
181
182
            return $value;
183
        }, null, $default);
184
185
        $this->io->write("");
186
187
        return $value;
188
    }
189
190
    /**
191
     * Write a warning if necessary
192
     *
193
     * @param VariableNode $node The node with the warning
194
     */
195
    private function writeWarning($node)
196
    {
197
        if (!$node->hasAttribute('warning')) {
198
            return;
199
        }
200
201
        // Split warning into words so that we can apply wrapping
202
        $words = preg_split('/\s+/', $node->getAttribute('warning'));
203
204
        $caution = ' ! [CAUTION]';
205
        $currentLength = 0;
206
207
        foreach ($words as $word) {
208
            if ($currentLength > self::CAUTION_LINE_LENGTH) {
209
                $caution .= "\n !";
210
                $currentLength = 0;
211
            }
212
213
            $caution .= ' ' . $word;
214
            $currentLength += strlen($word) + 1;
215
        }
216
217
        $this->io->write("<warning>\n\n$caution\n</>");
218
    }
219
220
    /**
221
     * Returns the path to the configuration file
222
     *
223
     * @return string
224
     */
225 1
    public static function getConfigurationPath()
226
    {
227 1
        return realpath(__DIR__ . '/../../app') . '/config.yml';
228
    }
229
}
230