Completed
Push — master ( ff4db3...2c17a6 )
by Sergii
02:29
created

Worker::read()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 12
cts 12
cp 1
rs 8.6845
c 0
b 0
f 0
cc 4
eloc 10
nc 4
nop 1
crap 4
1
<?php
2
3
namespace DeployRevision;
4
5
class Worker implements WorkerInterface
6
{
7
    /**
8
     * Indicates whether deployment was executed.
9
     *
10
     * @var bool
11
     */
12
    private $deployed = false;
13
    /**
14
     * Environment ID.
15
     *
16
     * @var string
17
     */
18
    protected $environment = '';
19
    /**
20
     * List of collected commands from playbooks.
21
     *
22
     * @var array
23
     */
24
    protected $commands = [];
25
    /**
26
     * Path to file for storing code revision.
27
     *
28
     * @var string
29
     */
30
    protected $versionFile = '';
31
    /**
32
     * Current version of code.
33
     *
34
     * @var int
35
     */
36
    protected $currentCodeVersion = 0;
37
    /**
38
     * New version of code.
39
     *
40
     * @var int
41
     */
42
    protected $newCodeVersion = 0;
43
    /**
44
     * YAML parser.
45
     *
46
     * @var YamlInterface
47
     */
48
    protected $yaml;
49
    /**
50
     * Messages logger.
51
     *
52
     * @var LoggerInterface
53
     */
54
    protected $logger;
55
56
    /**
57
     * {@inheritdoc}
58
     */
59 20
    public function __construct(YamlInterface $yaml, LoggerInterface $logger, $environment, $versionFile)
60
    {
61 20
        if (!$yaml->isAvailable()) {
62 1
            throw new \RuntimeException(sprintf('YAML parser "%s" is not available', get_class($yaml)));
63
        }
64
65 19
        $this->yaml = $yaml;
66 19
        $this->logger = $logger;
67 19
        $this->environment = $environment;
68 19
        $this->versionFile = "$versionFile-$environment";
69
70 19
        if (file_exists($this->versionFile)) {
71 2
            $this->newCodeVersion = $this->currentCodeVersion = (int) file_get_contents($this->versionFile);
72 2
        }
73 19
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78 17
    public function read($path)
79
    {
80 17
        if (is_dir($path)) {
81
            /**
82
             * Using just "\FilesystemIterator" we cannot be sure that correct ordering will be gained. On
83
             * TravisCI, for instance, ordering always was correct for every PHP version, but on Scrutinizer
84
             * was the cases when this valuable thing has not been achieved.
85
             *
86
             * @link https://scrutinizer-ci.com/g/BR0kEN-/deploy-revision/inspections/8b28f584-b923-4a47-96af-90f5b31f4a32
87
             */
88 7
            $files = iterator_to_array(new \FilesystemIterator($path, \FilesystemIterator::SKIP_DOTS));
89
90
            // Guarantee alphabetical ordering on every file system.
91 7
            ksort($files);
92
93 7
            foreach ($files as $path => $file) {
94 7
                $this->processPlaybook($path);
95 7
            }
96 17
        } elseif (file_exists($path)) {
97 15
            $this->processPlaybook($path);
98 15
        } else {
99 2
            $this->logger->log(sprintf('Not file "%s" nor directory exists', $path));
100
        }
101 16
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106 3
    public function getCurrentCodeVersion()
107
    {
108 3
        return $this->currentCodeVersion;
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114 2
    public function getNewCodeVersion()
115
    {
116 2
        return $this->newCodeVersion;
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122 11
    public function filter(callable $processor)
123
    {
124 11
        $commands = [];
125
126 11
        ksort($this->commands);
127
128 11
        foreach ($this->commands as $list) {
129 11
            foreach ($list as $command) {
130
                // Deployment cannot continue.
131 11
                if (!is_string($command)) {
132 1
                    throw new \RuntimeException(sprintf(
133 1
                        'Complex value cannot be a command: %s',
134 1
                        var_export($command, true)
135 1
                    ));
136
                }
137
138 10
                $commands[$command] = $command;
139
140
                unset($commands[$processor($command, $commands, function (
141
                    $return_previous,
142
                    array $current_commands,
143
                    array $existing_commands
144
                ) use (
145 2
                    $command,
146 2
                    $commands
147
                ) {
148
                    // Ensure that current command is candidate for filtering.
149 2
                    if (in_array($command, $current_commands)) {
150
                        // Iterate over commands in the list.
151 2
                        foreach ($commands as $existing_command) {
152
                            // Match the command in diapason.
153 2
                            if (in_array($existing_command, $existing_commands)) {
154
                                // Remove existing command or do not add currently processed.
155 2
                                return $return_previous ? $existing_command : $command;
156
                            }
157 2
                        }
158 2
                    }
159
160 2
                    return '';
161 10
                })]);
162 10
            }
163 10
        }
164
165 10
        $this->commands = array_values($commands);
166 10
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171 12
    public function deploy(callable $processor)
172
    {
173 12
        $command = reset($this->commands);
174
175
        // Filtering have not been performed and we dealing with array of arrays.
176 12
        if (is_array($command)) {
177 9
            $this->filter(function () {
178 8
                return '';
179 9
            });
180 8
        }
181
182 11
        array_map($processor, $this->commands);
183
184
        // Do not allow to deploy once again accidentally.
185 11
        $this->commands = [];
186 11
        $this->deployed = true;
187 11
    }
188
189
    /**
190
     * {@inheritdoc}
191
     */
192 3
    public function commit()
193
    {
194 3
        if (!$this->deployed) {
195 1
            throw new \RuntimeException('Deployment has not been performed. Saving the revision will cause problems');
196
        }
197
198 2
        if (!@file_put_contents($this->versionFile, $this->newCodeVersion)) {
199 1
            throw new \RuntimeException(sprintf('Cannot save the version of code to "%s" file', $this->versionFile));
200
        }
201 1
    }
202
203 15
    protected function processPlaybook($path)
204
    {
205 15
        if (!in_array(pathinfo($path, PATHINFO_EXTENSION), ['yaml', 'yml'])) {
206 1
            return;
207
        }
208
209 15
        $contents = $this->yaml->parse(file_get_contents($path));
210
211 15
        if (empty($contents['commands'])) {
212 1
            return;
213
        }
214
215 15
        foreach ($contents['commands'] as $group => $commands_group) {
216
            // Get only actions for particular site or global ones.
217 15
            if (!in_array($group, ['global', $this->environment])) {
218 2
                continue;
219
            }
220
221 15
            foreach ($commands_group as $version => $commands) {
222
                // Skip code actions that were already run.
223 15
                if ($version <= $this->currentCodeVersion) {
224 1
                    continue;
225
                }
226
227
                // Group commands by version to guarantee exact order.
228 14
                $this->commands += [$version => []];
229 14
                $this->commands[$version] = array_merge($this->commands[$version], array_filter((array) $commands));
230 14
                $this->newCodeVersion = (int) max($this->newCodeVersion, $version);
231 15
            }
232 15
        }
233 15
    }
234
}
235