Passed
Push — feature-add-checkstyle ( f83c9d )
by Sébastien
18:53
created

FileMigrationProvider   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 237
Duplicated Lines 0 %

Test Coverage

Coverage 93.55%

Importance

Changes 0
Metric Value
wmc 24
eloc 67
c 0
b 0
f 0
dl 0
loc 237
ccs 58
cts 62
cp 0.9355
rs 10

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A initRepository() 0 7 2
A migration() 0 7 2
A import() 0 19 3
A all() 0 3 1
A path() 0 3 1
A has() 0 3 1
A create() 0 36 6
A normalizeName() 0 6 1
A createFilename() 0 3 1
A parseFilename() 0 9 2
A assertUnique() 0 23 3
1
<?php
2
3
namespace Bdf\Prime\Migration\Provider;
4
5
use Bdf\Prime\Migration\MigrationFactoryInterface;
6
use Bdf\Prime\Migration\MigrationInterface;
7
use Bdf\Prime\Migration\MigrationProviderInterface;
8
use InvalidArgumentException;
9
use RuntimeException;
10
use Symfony\Component\Filesystem\Filesystem;
11
12
/**
13
 * Migration file provider
14
 */
15
class FileMigrationProvider implements MigrationProviderInterface
16
{
17
    /**
18
     * The migration factory
19
     *
20
     * @var MigrationFactoryInterface
21
     */
22
    private $factory;
23
24
    /**
25
     * The path to migration files
26
     *
27
     * @var string
28
     */
29
    private $path;
30
31
    /**
32
     * The collection of migration name by version
33
     *
34
     * @var MigrationInterface[]
35
     */
36
    private $migrations = [];
37
38
    /**
39
     * Locator constructor
40
     *
41
     * @param MigrationFactoryInterface $factory
42
     * @param string $path
43
     */
44 53
    public function __construct(MigrationFactoryInterface $factory, string $path)
45
    {
46 53
        $this->factory = $factory;
47 53
        $this->path = $path;
48
    }
49
50
    /**
51
     *{@inheritDoc}
52
     */
53 1
    public function initRepository(): void
54
    {
55 1
        if (is_dir($this->path)) {
56
            return;
57
        }
58
59 1
        (new Filesystem())->mkdir($this->path);
60
    }
61
62
    /**
63
     *{@inheritDoc}
64
     *
65
     * @throws InvalidArgumentException  If path is not writable
66
     * @throws RuntimeException
67
     */
68 7
    public function create(string $version, string $name, string $stage = MigrationInterface::STAGE_DEFAULT): string
69
    {
70 7
        $name = $this->normalizeName($name);
71
72
        // Check if the path is writable
73 7
        if (!$this->path || !is_writable($this->path)) {
74 1
            throw new InvalidArgumentException(sprintf(
75
                'The path "%s" is not writable, please consider running the init command first',
76 1
                $this->path
77
            ));
78
        }
79
80 6
        $this->assertUnique($version, $name);
81 6
        $path = $this->createFilename($version, $name);
82
83 6
        if (is_file($path)) {
84
            throw new InvalidArgumentException(sprintf(
85
                'The file "%s" already exists',
86
                $path
87
            ));
88
        }
89
90 6
        $content = $stage === MigrationInterface::STAGE_DEFAULT
91 4
            ? file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'migration.stub')
92 6
            : file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'migration-staged.stub')
93
        ;
94
95
        // Try to write the migration file
96 6
        if (!file_put_contents($path, str_replace(['{className}', '{version}', '{stage}'], [$name, $version, $stage], $content))) {
97
            throw new RuntimeException(sprintf(
98
                'The file "%s" could not be written to',
99
                $path
100
            ));
101
        }
102
103 6
        return realpath($path);
104
    }
105
106
    /**
107
     *{@inheritDoc}
108
     */
109 1
    public function path(): string
110
    {
111 1
        return $this->path;
112
    }
113
114
    /**
115
     *{@inheritDoc}
116
     */
117 25
    public function all(): array
118
    {
119 25
        return $this->migrations;
120
    }
121
122
    /**
123
     *{@inheritDoc}
124
     *
125
     * @throws InvalidArgumentException
126
     */
127 21
    public function migration(string $version): MigrationInterface
128
    {
129 21
        if (!$this->has($version)) {
130
            throw new InvalidArgumentException("Unable to provide Migration for version $version");
131
        }
132
133 21
        return $this->migrations[$version];
134
    }
135
136
    /**
137
     *{@inheritDoc}
138
     */
139 27
    public function has(string $version): bool
140
    {
141 27
        return isset($this->migrations[$version]);
142
    }
143
144
    /**
145
     *{@inheritDoc}
146
     */
147 51
    public function import(): void
148
    {
149 51
        $this->migrations = [];
150
151 51
        $paths = glob(realpath($this->path) . DIRECTORY_SEPARATOR . '*.php');
152
153 51
        foreach ($paths as $path) {
154 49
            list($version, $className) = $this->parseFilename($path);
155
156 47
            $this->assertUnique($version, $className);
157
158 47
            if (!class_exists($className)) {
159 6
                require_once $path;
160
            }
161
162 47
            $this->migrations[$version] = $this->factory->create($className, $version);
163
        }
164
165 47
        ksort($this->migrations, SORT_NUMERIC);
166
    }
167
168
    /**
169
     * Assert that the version and classname are unique
170
     *
171
     * @param string $version
172
     * @param string $className
173
     *
174
     * @throws InvalidArgumentException
175
     *
176
     * @return void
177
     */
178 49
    private function assertUnique($version, $className): void
179
    {
180
        // Check if version already exists
181 49
        if (isset($this->migrations[$version])) {
182 1
            throw new InvalidArgumentException(sprintf(
183
                'Duplicate migration version %s, "%s" has the same version as "%s"',
184
                $version,
185
                $className,
186 1
                $this->migrations[$version]->name()
187
            ));
188
        }
189
190
        // Check if migration name already exists
191 49
        $definedMigration = array_search($className, array_map(function (MigrationInterface $migration) {
192 42
            return $migration->name();
193 49
        }, $this->migrations));
194
195 49
        if (false !== $definedMigration) {
196 1
            throw new InvalidArgumentException(sprintf(
197
                'Duplicate migration name "%s", version "%s" has the same name as version "%s"',
198
                $className,
199
                $version,
200
                $definedMigration
201
            ));
202
        }
203
    }
204
205
    /**
206
     * Get version and migration name from a file path
207
     *
208
     * @param string $filename
209
     *
210
     * @return string[]
211
     *
212
     * @throws InvalidArgumentException
213
     */
214 49
    private function parseFilename($filename)
215
    {
216 49
        if (!preg_match('/^([0-9]+)_(.+)\.php/', basename($filename), $matches)) {
217 2
            throw new InvalidArgumentException(sprintf('The file "%s" does not have a valid migration filename', $filename));
218
        }
219
220
        return [
221 47
            $matches[1],
222 47
            $matches[2],
223
        ];
224
    }
225
226
    /**
227
     * Get file name from version and migration class name
228
     *
229
     * @param string $version
230
     * @param string $className
231
     *
232
     * @return string  The file name
233
     */
234 6
    private function createFilename($version, $className)
235
    {
236 6
        return $this->path . DIRECTORY_SEPARATOR . $version . '_' . $className . '.php';
237
    }
238
239
    /**
240
     * Normalize the name of the migration
241
     *
242
     * @param string $name
243
     *
244
     * @return string
245
     */
246 7
    private function normalizeName($name)
247
    {
248 7
        $name = str_replace(['_', '.'], ' ', $name);
249 7
        $name = ucwords($name);
250
251 7
        return str_replace(' ', '', $name);
252
    }
253
}
254