Test Setup Failed
Pull Request — master (#15)
by Łukasz
02:08
created

ProvisionCommand::deployCandidate()   A

Complexity

Conditions 3
Paths 5

Size

Total Lines 20
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 0
cts 17
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 15
nc 5
nop 1
crap 12
1
<?php
2
3
namespace Tworzenieweb\SqlProvisioner\Command;
4
5
use RuntimeException;
6
use Symfony\Component\Console\Command\Command;
7
use Symfony\Component\Console\Input\InputArgument;
8
use Symfony\Component\Console\Input\InputInterface;
9
use Symfony\Component\Console\Input\InputOption;
10
use Symfony\Component\Console\Output\OutputInterface;
11
use Symfony\Component\Console\Style\SymfonyStyle;
12
use Symfony\Component\Finder\SplFileInfo;
13
use Tworzenieweb\SqlProvisioner\Controller\ProvisionDispatcher;
14
use Tworzenieweb\SqlProvisioner\Database\Connection;
15
use Tworzenieweb\SqlProvisioner\Filesystem\Exception;
16
use Tworzenieweb\SqlProvisioner\Filesystem\WorkingDirectory;
17
use Tworzenieweb\SqlProvisioner\Model\Candidate;
18
use Tworzenieweb\SqlProvisioner\Model\CandidateBuilder;
19
use Tworzenieweb\SqlProvisioner\Table\DataRowsBuilder;
20
21
/**
22
 * @author Luke Adamczewski
23
 * @package Tworzenieweb\SqlProvisioner\Command
24
 */
25
class ProvisionCommand extends Command
26
{
27
    const HELP_MESSAGE = <<<'EOF'
28
The <info>%command.name% [path-to-folder]</info> command will scan the content of [path-to-folder] directory.
29
 
30
The script will look for <info>.env</info> file containing connection information in format:
31
<comment>
32
DATABASE_USER=[user]
33
DATABASE_PASSWORD=[password]
34
DATABASE_HOST=[host]
35
DATABASE_PORT=[port]
36
DATABASE_NAME=[database]
37
PROVISIONING_TABLE=changelog_database_deployments
38
PROVISIONING_TABLE_CANDIDATE_NUMBER_COLUMN=deploy_script_number
39
</comment>
40
41
If you want to create initial .env use <info>--init</info>
42
43
<info>%command.name% --init [path-to-folder]</info>
44
45
The next step is searching for sql files and trying to queue them in numerical order.
46
First n-th digits of a filename will be treated as candidate number. 
47
This will be used then to check in database if a certain file was already deployed (PROVISIONING_TABLE_CANDIDATE_NUMBER_COLUMN).
48
Before the insert, it will print the formatted output of a file and result of internal syntax check.
49
Then you can either skip or execute each.
50
51
If you would like to skip already provisioned candidates use <info>--skip-provisioned</info>
52
If you would like to skip syntax checking (for speed purpose) of candidates use <info>--skip-syntax-check</info>
53
54
EOF;
55
56
    /** @var Candidate[] */
57
    private $workingDirectoryCandidates = [];
58
59
    /** @var WorkingDirectory */
60
    private $workingDirectory;
61
62
    /** @var SymfonyStyle */
63
    private $io;
64
65
    /** @var Connection */
66
    private $connection;
67
68
    /** @var boolean */
69
    private $skipProvisionedCandidates = false;
70
71
    /** @var CandidateBuilder */
72
    private $candidateBuilder;
73
74
    /** @var DataRowsBuilder */
75
    private $dataRowsBuilder;
76
77
    /** @var integer */
78
    private $queuedCandidatesCount = 0;
79
80
    /** @var array */
81
    private $errorMessages = [];
82
83
    /** @var ProvisionDispatcher */
84
    private $dispatcher;
85
86
87
88
    /**
89
     * @param string $name
90
     * @param WorkingDirectory $workingDirectory
91
     * @param Connection $connection
92
     * @param CandidateBuilder $candidateBuilder
93
     * @param DataRowsBuilder $dataRowsBuilder
94
     * @param ProvisionDispatcher $dispatcher
95
     */
96
    public function __construct(
97
        $name,
98
        WorkingDirectory $workingDirectory,
99
        Connection $connection,
100
        CandidateBuilder $candidateBuilder,
101
        DataRowsBuilder $dataRowsBuilder,
102
        ProvisionDispatcher $dispatcher
103
    )
104
    {
105
        $this->workingDirectory = $workingDirectory;
106
        $this->connection = $connection;
107
        $this->candidateBuilder = $candidateBuilder;
108
        $this->dataRowsBuilder = $dataRowsBuilder;
109
        $this->dispatcher = $dispatcher;
110
111
        parent::__construct($name);
112
    }
113 1
114
115
    protected function configure()
116
    {
117
        $this
118
            ->setDescription('Execute the content of *.sql files from given')
119
            ->setHelp(self::HELP_MESSAGE);
120
        $this->addOption('init', null, InputOption::VALUE_NONE, 'Initialize .env in given directory');
121
        $this->addOption(
122
            'skip-provisioned',
123
            null,
124 1
            InputOption::VALUE_NONE,
125 1
            'Skip provisioned candidates from printing'
126 1
        );
127 1
        $this->addOption(
128 1
            'skip-syntax-check',
129 1
            null,
130 1
            InputOption::VALUE_NONE,
131
            'Skip executing of sql syntax check for each entry'
132 1
        );
133 1
        $this->addArgument('path', InputArgument::REQUIRED, 'Path to dbdeploys folder');
134
    }
135
136 1
137
    /**
138 1
     * @param InputInterface $input
139 1
     * @param OutputInterface $output
140 1
     * @return int
141 1
     */
142 1
    protected function execute(InputInterface $input, OutputInterface $output)
143 1
    {
144 1
        $this->start($input, $output);
145 1
        $this->io->section('Working directory processing');
146
147 1
        if ($input->getOption('skip-provisioned')) {
148 1
            $this->skipProvisionedCandidates = true;
149 1
            $this->io->warning('Hiding of provisioned candidates ENABLED');
150
        }
151
152
        if ($input->getOption('skip-syntax-check')) {
153
           $this->dispatcher->skipSyntaxCheck();
154
        }
155
156
        $this->processWorkingDirectory($input);
157
        $this->processCandidates();
158
159
        return 0;
160
    }
161
162
163
    /**
164
     * @param InputInterface $input
165
     * @param OutputInterface $output
166
     */
167
    protected function start(InputInterface $input, OutputInterface $output)
168
    {
169
        $this->io = new SymfonyStyle($input, $output);
170
        $this->dispatcher->setInputOutput($this->io);
171
172
        $this->io->title('SQL Provisioner');
173
        $this->io->block(sprintf('Provisioning started at %s', date('Y-m-d H:i:s')));
174
    }
175
176
177
    protected function fetchCandidates()
178
    {
179
        $this->iterateOverWorkingDirectory();
180
181
        if (!empty($this->errorMessages)) {
182
            $this->showSyntaxErrors();
183
        }
184
185
        if (!$this->queuedCandidatesCount) {
186
            $this->io->block('All candidates scripts were executed already.');
187
            $this->dispatcher->finalizeAndExit();
188
        }
189
    }
190
191
192
    /**
193
     * @param SplFileInfo $candidateFile
194
     */
195
    protected function processCandidateFile($candidateFile)
196
    {
197
        $candidate = $this->candidateBuilder->build($candidateFile);
198
        array_push($this->workingDirectoryCandidates, $candidate);
199
200
        try {
201
            $this->dispatcher->validate($candidate);
202
203
            // can be also ignored but without error
204
            if ($candidate->isQueued()) {
205
                $this->queuedCandidatesCount++;
206
            }
207
        } catch (RuntimeException $validationError) {
208
            array_push($this->errorMessages, $validationError->getMessage());
209
        }
210
    }
211
212
213
    protected function iterateOverWorkingDirectory()
214
    {
215
        foreach ($this->workingDirectory->getCandidates() as $candidateFile) {
216
            $this->processCandidateFile($candidateFile);
217
        }
218
219
        $this->io->text(sprintf('<info>%d</info> files found', count($this->workingDirectoryCandidates)));
220
221
        if (count($this->workingDirectoryCandidates) === 0) {
222
            throw Exception::noFilesInDirectory($this->workingDirectory);
223
        }
224
    }
225
226
227
    protected function showSyntaxErrors()
228
    {
229
        $this->io->warning(sprintf('Detected %d syntax checking issues', count($this->errorMessages)));
230
        $this->printAllCandidates();
231
        $this->io->warning(implode("\n", $this->errorMessages));
232
        $this->dispatcher->finalizeAndExit();
233
    }
234
235
236
    /**
237
     * @param InputInterface $input
238
     */
239
    protected function processWorkingDirectory(InputInterface $input)
240
    {
241
        $this->workingDirectory = $this->workingDirectory->cd($input->getArgument('path'));
242
        $this->loadOrCreateEnvironment($input);
243
        $this->io->success('DONE');
244
    }
245
246
247
    /**
248
     * @param InputInterface $input
249
     */
250
    private function loadOrCreateEnvironment(InputInterface $input)
251
    {
252
        if ($input->getOption('init')) {
253
            $this->workingDirectory->createEnvironmentFile();
254
            $this->io->success(sprintf('Initial .env file created in %s', $this->workingDirectory));
255
            die(0);
256
        }
257
258
        $this->workingDirectory->loadEnvironment();
259
    }
260
261
262
    private function setConnectionParameters()
263
    {
264
        $this->connection->useMysql($_ENV['DATABASE_HOST'], $_ENV['DATABASE_PORT'], $_ENV['DATABASE_NAME'], $_ENV['DATABASE_USER'], $_ENV['DATABASE_PASSWORD']);
265
        $this->connection->setProvisioningTable($_ENV['PROVISIONING_TABLE']);
266
        $this->connection->setCriteriaColumn($_ENV['PROVISIONING_TABLE_CANDIDATE_NUMBER_COLUMN']);
267
268
        $this->io->success(sprintf('Connection with `%s` established', $_ENV['DATABASE_NAME']));
269
    }
270
271
272
    private function processCandidates()
273
    {
274
        $this->io->newLine(2);
275
        $this->io->section('Candidates processing');
276
277
        $this->setConnectionParameters();
278
        $this->fetchCandidates();
279
        $this->printAllCandidates();
280
        $this->dispatcher->deploy($this->workingDirectoryCandidates, $this->queuedCandidatesCount);
281
    }
282
283
284
    private function printAllCandidates()
285
    {
286
        $this->io->table(
287
            DataRowsBuilder::TABLE_HEADERS,
288
            $this->dataRowsBuilder->build(
289
                $this->workingDirectoryCandidates, $this->skipProvisionedCandidates)
290
        );
291
        $this->io->newLine(3);
292
    }
293
}
294