Completed
Branch feature/pre-split (e801ec)
by Anton
03:11
created

FileRepository   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 173
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 0
Metric Value
dl 0
loc 173
c 0
b 0
f 0
rs 10
wmc 15
lcom 1
cbo 9

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
A createFilename() 0 14 1
B getMigrations() 0 27 4
C registerMigration() 0 36 7
A getFiles() 0 15 2
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
namespace Spiral\Migrations;
9
10
use Doctrine\Common\Inflector\Inflector;
11
use Spiral\Core\FactoryInterface;
12
use Spiral\Files\FilesInterface;
13
use Spiral\Migrations\Configs\MigrationsConfig;
14
use Spiral\Migrations\Exceptions\RepositoryException;
15
use Spiral\Migrations\Migration\State;
16
use Spiral\Tokenizer\TokenizerInterface;
17
18
/**
19
 * Stores migrations as files.
20
 */
21
class FileRepository implements RepositoryInterface
22
{
23
    /**
24
     * Migrations file name format. This format will be used when requesting new migration filename.
25
     */
26
    const FILENAME_FORMAT = '{timestamp}_{chunk}_{name}.php';
27
28
    /**
29
     * Timestamp format for files.
30
     */
31
    const TIMESTAMP_FORMAT = 'Ymd.His';
32
33
    /**
34
     * @var MigrationsConfig
35
     */
36
    private $config = null;
37
38
    /**
39
     * Required when multiple migrations added at once.
40
     *
41
     * @var int
42
     */
43
    private $chunkID = 0;
44
45
    /**
46
     * @invisible
47
     * @var TokenizerInterface
48
     */
49
    protected $tokenizer = null;
50
51
    /**
52
     * @invisible
53
     * @var FactoryInterface
54
     */
55
    protected $factory = null;
56
57
    /**
58
     * @invisible
59
     * @var FilesInterface
60
     */
61
    protected $files = null;
62
63
    /**
64
     * @param MigrationsConfig   $config
65
     * @param TokenizerInterface $tokenizer
66
     * @param FilesInterface     $files
67
     * @param FactoryInterface   $factory
68
     */
69
    public function __construct(
70
        MigrationsConfig $config,
71
        TokenizerInterface $tokenizer,
72
        FilesInterface $files,
73
        FactoryInterface $factory
74
    ) {
75
        $this->config = $config;
76
77
        $this->tokenizer = $tokenizer;
78
        $this->files = $files;
79
        $this->factory = $factory;
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function getMigrations(): array
86
    {
87
        $migrations = [];
88
89
        foreach ($this->getFiles() as $definition) {
90
            if (!class_exists($definition['class'], false)) {
91
                require_once($definition['filename']);
92
            }
93
94
            if (!$definition['created'] instanceof \DateTime) {
95
                throw new RepositoryException(
96
                    "Invalid migration filename '{$definition['filename']}'"
97
                );
98
            }
99
100
            /**
101
             * @var MigrationInterface $migration
102
             */
103
            $migration = $this->factory->make($definition['class']);
104
105
            $migrations[$definition['filename']] = $migration->withState(
106
                new State($definition['name'], $definition['created'])
107
            );
108
        }
109
110
        return $migrations;
111
    }
112
113
    /**
114
     * {@inheritdoc}
115
     */
116
    public function registerMigration(string $name, string $class, string $body = null): string
117
    {
118
        if (empty($body) && !class_exists($class)) {
119
            throw new RepositoryException(
120
                "Unable to register migration '{$class}', representing class does not exists"
121
            );
122
        }
123
124
        foreach ($this->getMigrations() as $migration) {
125
            if (get_class($migration) == $class) {
126
                throw new RepositoryException(
127
                    "Unable to register migration '{$class}', migration already exists"
128
                );
129
            }
130
131
            if ($migration->getMeta()->getName() == $name) {
0 ignored issues
show
Bug introduced by
The method getMeta() does not seem to exist on object<Spiral\Migrations\MigrationInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
132
                throw new RepositoryException(
133
                    "Unable to register migration '{$name}', migration under same name already exists"
134
                );
135
            }
136
        }
137
138
        if (empty($body)) {
139
            //Let's read body from a given class filename
140
            $body = $this->files->read((new \ReflectionClass($class))->getFileName());
141
        }
142
143
        //Copying
144
        $this->files->write(
145
            $filename = $this->createFilename($name),
146
            $body,
147
            FilesInterface::READONLY
148
        );
149
150
        return basename($filename);
151
    }
152
153
    /**
154
     * Internal method to fetch all migration filenames.
155
     */
156
    private function getFiles(): \Generator
157
    {
158
        foreach ($this->files->getFiles($this->config['directory'], '*.php') as $filename) {
159
            $reflection = $this->tokenizer->fileReflection($filename);
160
161
            $definition = explode('_', basename($filename));
162
163
            yield [
164
                'filename' => $filename,
165
                'class'    => $reflection->getClasses()[0],
166
                'created'  => \DateTime::createFromFormat(self::TIMESTAMP_FORMAT, $definition[0]),
167
                'name'     => str_replace('.php', '', join('_', array_slice($definition, 2)))
168
            ];
169
        }
170
    }
171
172
    /**
173
     * Request new migration filename based on user input and current timestamp.
174
     *
175
     * @param string $name
176
     *
177
     * @return string
178
     */
179
    private function createFilename(string $name): string
180
    {
181
        $name = Inflector::tableize($name);
182
183
        $filename = \Spiral\interpolate(self::FILENAME_FORMAT, [
184
            'timestamp' => date(self::TIMESTAMP_FORMAT),
185
            'chunk'     => $this->chunkID++,
186
            'name'      => $name
187
        ]);
188
189
        return $this->files->normalizePath(
190
            $this->config->getDirectory() . FilesInterface::SEPARATOR . $filename
191
        );
192
    }
193
}