StatusHandler   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 4
Bugs 2 Features 1
Metric Value
wmc 21
c 4
b 2
f 1
lcom 1
cbo 7
dl 0
loc 176
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
B handle() 0 53 5
A printPendingVersion() 0 9 2
C getRelativePath() 0 30 7
A printDiff() 0 15 3
A splitDiff() 0 14 4
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Baleen\Cli\CommandBus\Config;
21
22
use Baleen\Migrations\Version;
23
use Symfony\Component\Console\Output\OutputInterface;
24
25
/**
26
 * Handles the config:status command.
27
 *
28
 * @author Gabriel Somoza <[email protected]>
29
 */
30
class StatusHandler
31
{
32
    const STYLE_INFO = 'info';
33
    const STYLE_COMMENT = 'comment';
34
35
    /** @var OutputInterface */
36
    protected $output;
37
38
    /** @var StatusMessage */
39
    protected $message;
40
41
    /**
42
     * Handles a StatusMessage, which prints the status of the migrations system in a developer-friendly format
43
     * (inspired by "git status").
44
     *
45
     * @param StatusMessage $message
46
     */
47
    public function handle(StatusMessage $message)
48
    {
49
        $this->message = $message;
50
        $this->output = $message->getOutput();
51
        $output = $this->output;
52
53
        $repository = $message->getRepository();
54
        $storage = $message->getStorage();
55
56
        $availableMigrations = $repository->fetchAll();
57
        $migratedVersions = $storage->fetchAll();
58
59
        $migratedVersions->sortWith($message->getComparator());
60
        $headVersion = $migratedVersions->last();
61
62
        $currentMessage = $headVersion ?
63
            "Current version: <comment>{$headVersion->getId()}</comment>" :
64
            'Nothing has been migrated yet.';
65
        $output->writeln($currentMessage);
66
67
        $diff = array_diff($availableMigrations->toArray(), $migratedVersions->toArray());
68
69
        $pendingCount = count($diff);
70
71
        if ($pendingCount > 0) {
72
            $executable = defined('MIGRATIONS_EXECUTABLE') ? MIGRATIONS_EXECUTABLE . ' ' : '';
73
            $output->writeln([
74
                "Your database is out-of-date by $pendingCount versions, and can be migrated.",
75
                sprintf(
76
                    '  (use "<comment>%smigrate</comment>" to execute all migrations)',
77
                    $executable
78
                ),
79
                ''
80
            ]);
81
            if ($headVersion) {
82
                list($beforeHead, $afterHead) = $this->splitDiff($diff, $message->getComparator(), $headVersion);
83
            } else {
84
                $beforeHead = [];
85
                $afterHead = $diff;
86
            }
87
            $this->printDiff(
88
                $beforeHead,
89
                [
90
                    'Old migrations still pending:',
91
                    sprintf("  (use \"<comment>{$executable}migrate HEAD</comment>\" to migrate them)", $executable),
92
                ],
93
                self::STYLE_COMMENT
94
            );
95
            $this->printDiff($afterHead, ['New migrations:'], self::STYLE_INFO);
96
        } else {
97
            $output->writeln('Your database is up-to-date.');
98
        }
99
    }
100
101
    /**
102
     * Formats and prints a pending version with the given style.
103
     *
104
     * @param Version $version The Version to print.
105
     * @param string $style One of the STYLE_* constants.
106
     */
107
    protected function printPendingVersion(Version $version, $style)
108
    {
109
        /** @var Version $version */
110
        $id = $version->getId();
111
        $reflectionClass = new \ReflectionClass($version->getMigration());
112
        $absolutePath = $reflectionClass->getFileName();
113
        $fileName = $absolutePath ? $this->getRelativePath(getcwd(), $absolutePath) : '';
114
        $this->output->writeln("\t<$style>[$id] $fileName</$style>");
115
    }
116
117
    /**
118
     * Returns the relative path between two known paths.
119
     *
120
     * @param $from
121
     * @param $to
122
     *
123
     * @return string
124
     *
125
     * @link http://stackoverflow.com/questions/2637945/getting-relative-path-from-absolute-path-in-php
126
     */
127
    protected function getRelativePath($from, $to)
128
    {
129
        // some compatibility fixes for Windows paths
130
        $from = is_dir($from) ? rtrim($from, '\/') . '/' : $from;
131
        $to = is_dir($to) ? rtrim($to, '\/') . '/' : $to;
132
        $from = str_replace('\\', '/', $from);
133
        $to = str_replace('\\', '/', $to);
134
135
        $from = explode('/', $from);
136
        $to = explode('/', $to);
137
        $relPath = $to;
138
139
        foreach ($from as $depth => $dir) {
140
            // find first non-matching dir
141
            if (isset($to[$depth]) && $dir === $to[$depth]) {
142
                // ignore this directory
143
                array_shift($relPath);
144
            } else {
145
                // get number of remaining dirs to $from
146
                $remaining = count($from) - $depth;
147
                if ($remaining > 1) {
148
                    // add traversals up to first matching dir
149
                    $padLength = (count($relPath) + $remaining - 1) * -1;
150
                    $relPath = array_pad($relPath, $padLength, '..');
151
                    break;
152
                }
153
            }
154
        }
155
        return implode('/', $relPath);
156
    }
157
158
    /**
159
     * Prints an array (group) of Versions all with the given style. If the array is empty then it prints nothing.
160
     *
161
     * @param array $versions
162
     * @param string|string[] $message Message(s) to print before the group of versions.
163
     * @param string $style One of the STYLE_* constants.
164
     */
165
    protected function printDiff(array $versions, $message, $style = self::STYLE_INFO)
166
    {
167
        if (empty($versions)) {
168
            return;
169
        }
170
        $this->output->writeln($message);
171
        $this->output->writeln('');
172
173
        foreach ($versions as $version) {
174
            $this->printPendingVersion($version, $style);
175
        }
176
177
        // if there was at least one version in the array
178
        $this->output->writeln('');
179
    }
180
181
    /**
182
     * Splits an array of Versions into two arrays and returns them. The first one contains all versions before the HEAD
183
     * (latest migrated) Version, and the second one contains all Versions after HEAD. Head is never included in either
184
     * of the arrays.
185
     *
186
     * @param Version[] $diff The array of Versions that should be split.
187
     * @param callable $comparator The comparator used to sort Versions.
188
     * @param Version $head The HEAD version.
189
     * @return array
190
     */
191
    protected function splitDiff(array $diff, callable $comparator, Version $head)
192
    {
193
        $beforeHead = [];
194
        $afterHead = [];
195
        foreach ($diff as $v) {
196
            $result = $comparator($v, $head);
197
            if ($result < 0) {
198
                $beforeHead[] = $v;
199
            } elseif ($result > 0) {
200
                $afterHead[] = $v;
201
            }
202
        }
203
        return [$beforeHead, $afterHead];
204
    }
205
}
206