Completed
Branch feature/pre-split (67216b)
by Anton
03:22
created

FileRepository::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 4
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
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
                //Attempting to load migration class (we can not relay on autoloading here)
92
                require_once($definition['filename']);
93
            }
94
95
            if (!$definition['created'] instanceof \DateTime) {
96
                throw new RepositoryException(
97
                    "Invalid migration filename '{$definition['filename']}'"
98
                );
99
            }
100
101
            /**
102
             * @var MigrationInterface $migration
103
             */
104
            $migration = $this->factory->make($definition['class']);
105
106
            $migrations[$definition['filename']] = $migration->withState(
107
                new State($definition['name'], $definition['created'])
108
            );
109
        }
110
111
        return $migrations;
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function registerMigration(string $name, string $class, string $body = null): string
118
    {
119
        if (empty($body) && !class_exists($class)) {
120
            throw new RepositoryException(
121
                "Unable to register migration '{$class}', representing class does not exists"
122
            );
123
        }
124
125
        foreach ($this->getMigrations() as $migration) {
126
            if (get_class($migration) == $class) {
127
                throw new RepositoryException(
128
                    "Unable to register migration '{$class}', migration already exists"
129
                );
130
            }
131
132
            if ($migration->getState()->getName() == $name) {
133
                throw new RepositoryException(
134
                    "Unable to register migration '{$name}', migration under same name already exists"
135
                );
136
            }
137
        }
138
139
        if (empty($body)) {
140
            //Let's read body from a given class filename
141
            $body = $this->files->read((new \ReflectionClass($class))->getFileName());
142
        }
143
144
        //Copying
145
        $this->files->write(
146
            $filename = $this->createFilename($name),
147
            $body,
148
            FilesInterface::READONLY
149
        );
150
151
        return basename($filename);
152
    }
153
154
    /**
155
     * Internal method to fetch all migration filenames.
156
     */
157
    private function getFiles(): \Generator
158
    {
159
        foreach ($this->files->getFiles($this->config['directory'], '*.php') as $filename) {
160
            $reflection = $this->tokenizer->fileReflection($filename);
161
162
            $definition = explode('_', basename($filename));
163
164
            yield [
165
                'filename' => $filename,
166
                'class'    => $reflection->getClasses()[0],
167
                'created'  => \DateTime::createFromFormat(self::TIMESTAMP_FORMAT, $definition[0]),
168
                'name'     => str_replace('.php', '', join('_', array_slice($definition, 2)))
169
            ];
170
        }
171
    }
172
173
    /**
174
     * Request new migration filename based on user input and current timestamp.
175
     *
176
     * @param string $name
177
     *
178
     * @return string
179
     */
180
    private function createFilename(string $name): string
181
    {
182
        $name = Inflector::tableize($name);
183
184
        $filename = \Spiral\interpolate(self::FILENAME_FORMAT, [
185
            'timestamp' => date(self::TIMESTAMP_FORMAT),
186
            'chunk'     => $this->chunkID++,
187
            'name'      => $name
188
        ]);
189
190
        return $this->files->normalizePath(
191
            $this->config->getDirectory() . FilesInterface::SEPARATOR . $filename
192
        );
193
    }
194
}