Completed
Push — master ( 12beb0...b07b53 )
by Sergii
02:29
created

Worker::read()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 12
cts 12
cp 1
rs 9.0534
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
            // Using just "\FilesystemIterator" we cannot be sure that correct ordering will be gained. On
82
            // TravisCI, for instance, ordering always was correct for every PHP version, but on Scrutinizer
83
            // was the cases when this valuable thing has not been achieved.
84
            // https://scrutinizer-ci.com/g/BR0kEN-/deploy-revision/inspections/8b28f584-b923-4a47-96af-90f5b31f4a32
85 7
            $files = iterator_to_array(new \FilesystemIterator($path, \FilesystemIterator::SKIP_DOTS));
86
87
            // Guarantee alphabetical ordering on every file system.
88 7
            ksort($files);
89
90 7
            foreach ($files as $path => $file) {
91 7
                $this->processPlaybook($path);
92 7
            }
93 17
        } elseif (file_exists($path)) {
94 15
            $this->processPlaybook($path);
95 15
        } else {
96 2
            $this->logger->log(sprintf('Not file "%s" nor directory exists', $path));
97
        }
98 16
    }
99
100
    /**
101
     * {@inheritdoc}
102
     */
103 3
    public function getCurrentCodeVersion()
104
    {
105 3
        return $this->currentCodeVersion;
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111 2
    public function getNewCodeVersion()
112
    {
113 2
        return $this->newCodeVersion;
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119 11
    public function filter(callable $processor)
120
    {
121 11
        $commands = [];
122
123 11
        ksort($this->commands);
124
125 11
        foreach ($this->commands as $list) {
126 11
            foreach ($list as $command) {
127
                // Deployment cannot continue.
128 11
                if (!is_string($command)) {
129 1
                    throw new \RuntimeException(sprintf(
130 1
                        'Complex value cannot be a command: %s',
131 1
                        var_export($command, true)
132 1
                    ));
133
                }
134
135 10
                $commands[$command] = $command;
136
137
                unset($commands[$processor($command, $commands, function (
138
                    $return_previous,
139
                    array $current_commands,
140
                    array $existing_commands
141
                ) use (
142 2
                    $command,
143 2
                    $commands
144
                ) {
145
                    // Ensure that current command is candidate for filtering.
146 2
                    if (in_array($command, $current_commands)) {
147
                        // Iterate over commands in the list.
148 2
                        foreach ($commands as $existing_command) {
149
                            // Match the command in diapason.
150 2
                            if (in_array($existing_command, $existing_commands)) {
151
                                // Remove existing command or do not add currently processed.
152 2
                                return $return_previous ? $existing_command : $command;
153
                            }
154 2
                        }
155 2
                    }
156
157 2
                    return '';
158 10
                })]);
159 10
            }
160 10
        }
161
162 10
        $this->commands = array_values($commands);
163 10
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168 12
    public function deploy(callable $processor)
169
    {
170 12
        $command = reset($this->commands);
171
172
        // Filtering have not been performed and we dealing with array of arrays.
173 12
        if (is_array($command)) {
174 9
            $this->filter(function () {
175 8
                return '';
176 9
            });
177 8
        }
178
179 11
        array_map($processor, $this->commands);
180
181
        // Do not allow to deploy once again accidentally.
182 11
        $this->commands = [];
183 11
        $this->deployed = true;
184 11
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189 3
    public function commit()
190
    {
191 3
        if (!$this->deployed) {
192 1
            throw new \RuntimeException('Deployment has not been performed. Saving the revision will cause problems');
193
        }
194
195 2
        if (!@file_put_contents($this->versionFile, $this->newCodeVersion)) {
196 1
            throw new \RuntimeException(sprintf('Cannot save the version of code to "%s" file', $this->versionFile));
197
        }
198 1
    }
199
200 15
    protected function processPlaybook($path)
201
    {
202 15
        if (!in_array(pathinfo($path, PATHINFO_EXTENSION), ['yaml', 'yml'])) {
203 1
            return;
204
        }
205
206 15
        $contents = $this->yaml->parse(file_get_contents($path));
207
208 15
        if (empty($contents['commands'])) {
209 1
            return;
210
        }
211
212 15
        foreach ($contents['commands'] as $group => $commands_group) {
213
            // Get only actions for particular site or global ones.
214 15
            if (!in_array($group, ['global', $this->environment])) {
215 2
                continue;
216
            }
217
218 15
            foreach ($commands_group as $version => $commands) {
219
                // Skip code actions that were already run.
220 15
                if ($version <= $this->currentCodeVersion) {
221 1
                    continue;
222
                }
223
224
                // Group commands by version to guarantee exact order.
225 14
                $this->commands += [$version => []];
226 14
                $this->commands[$version] = array_merge($this->commands[$version], array_filter((array) $commands));
227 14
                $this->newCodeVersion = (int) max($this->newCodeVersion, $version);
228 15
            }
229 15
        }
230 15
    }
231
}
232