Completed
Push — dev ( 348067...b33ea3 )
by Zach
02:21
created

FileDateMigrator::getMigrationFiles()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 0
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Yarak\Migrations\FileDate;
4
5
use Yarak\Helpers\Str;
6
use Yarak\Config\Config;
7
use Yarak\Helpers\Loggable;
8
use Yarak\Helpers\Filesystem;
9
use Yarak\Migrations\Migrator;
10
use Yarak\DB\ConnectionResolver;
11
use Yarak\Migrations\Repositories\MigrationRepository;
12
13
class FileDateMigrator implements Migrator
14
{
15
    use Filesystem, Loggable;
16
17
    /**
18
     * Yarak config.
19
     *
20
     * @var Config
21
     */
22
    protected $config;
23
24
    /**
25
     * Database connection resolver.
26
     *
27
     * @var ConnectionResolver
28
     */
29
    protected $resolver;
30
31
    /**
32
     * Repository for logging migration activity.
33
     *
34
     * @var MigrationRepository
35
     */
36
    protected $repository;
37
38
    /**
39
     * The active database connection.
40
     *
41
     * @var Phalcon\Db\Adapter\Pdo
42
     */
43
    protected $connection = null;
44
45
    /**
46
     * Construct.
47
     *
48
     * @param Config                       $config
49
     * @param ConnectionResolver           $resolver
50
     * @param MigrationRepositoryInterface $repository
51
     */
52
    public function __construct(
53
        Config $config,
54
        ConnectionResolver $resolver,
55
        MigrationRepository $repository
56
    ) {
57
        $this->config = $config;
58
        $this->resolver = $resolver;
59
        $this->repository = $repository;
60
    }
61
62
    /**
63
     * Run migrations.
64
     *
65
     * @return array
66
     */
67
    public function run()
68
    {
69
        $this->setUp();
70
71
        $pendingMigrations = $this->getPendingMigrations();
72
73
        return $this->runPending($pendingMigrations);
74
    }
75
76
    /**
77
     * Get all migration filenames that have not been run.
78
     *
79
     * @return array
80
     */
81
    protected function getPendingMigrations()
82
    {
83
        return array_diff(
84
            $this->getMigrationFiles(),
85
            $this->repository->getRanMigrations()
86
        );
87
    }
88
89
    /**
90
     * Get array of migration file names from directory listed in config.
91
     *
92
     * @return array
93
     */
94
    protected function getMigrationFiles()
95
    {
96
        $files = scandir($this->config->getMigrationDirectory());
97
98
        $files = array_filter($files, function ($file) {
99
            return strpos($file, '.php') !== false;
100
        });
101
102
        $files = array_map(function ($file) {
103
            return str_replace('.php', '', $file);
104
        }, $files);
105
106
        return array_values($files);
107
    }
108
109
    /**
110
     * Run pending migrations.
111
     *
112
     * @param array $migrations
113
     *
114
     * @return array
115
     */
116
    protected function runPending(array $migrations)
117
    {
118
        if (count($migrations) === 0) {
119
            $this->log('<info>No pending migrations to run.</info>');
120
121
            return [];
122
        }
123
124
        $batch = $this->repository->getNextBatchNumber();
125
126
        $this->connection->begin();
127
128
        foreach ($migrations as $migration) {
129
            $this->runUp($migration, $batch);
130
        }
131
132
        $this->connection->commit();
133
134
        return $migrations;
135
    }
136
137
    /**
138
     * Run the migration.
139
     *
140
     * @param string $migration
141
     * @param int    $batch
142
     */
143 View Code Duplication
    protected function runUp($migration, $batch)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
144
    {
145
        $migrationClass = $this->resolveMigrationClass($migration);
146
147
        try {
148
            $migrationClass->up($this->connection);
149
        } catch (\Exception $e) {
150
            return $this->log("<error>{$e->getMessage()}</error>");
151
        }
152
153
        $this->log("<info>Migrated {$migration}.</info>");
154
155
        $this->repository->insertRecord($migration, $batch);
156
    }
157
158
    /**
159
     * Resolve the migration class from the file name.
160
     *
161
     * @param string $migration
162
     *
163
     * @return Yarak\Migrations\Migration
164
     */
165
    protected function resolveMigrationClass($migration)
166
    {
167
        require_once $this->config->getMigrationDirectory().$migration.'.php';
168
169
        $class = Str::studly(implode('_', array_slice(explode('_', $migration), 4)));
170
171
        return new $class();
172
    }
173
174
    /**
175
     * Rollback migrations.
176
     *
177
     * @param int $steps
178
     *
179
     * @return array
180
     */
181
    public function rollback($steps = 1)
182
    {
183
        $this->setUp();
184
185
        $toRollback = $this->repository->getRanMigrations(null, $steps);
186
187
        return $this->runRollback($toRollback);
188
    }
189
190
    /**
191
     * Rollback given migrations.
192
     *
193
     * @param array $migrations
194
     *
195
     * @return array
196
     */
197
    protected function runRollback(array $migrations)
198
    {
199
        if (count($migrations) === 0) {
200
            $this->log('<info>Nothing to rollback.</info>');
201
202
            return [];
203
        }
204
205
        $this->connection->begin();
206
207
        foreach (array_reverse($migrations) as $migration) {
208
            $this->runDown($migration);
209
        }
210
211
        $this->connection->commit();
212
213
        return $migrations;
214
    }
215
216
    /**
217
     * Rollback the migration.
218
     *
219
     * @param string $migration
220
     */
221 View Code Duplication
    protected function runDown($migration)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
222
    {
223
        $migrationClass = $this->resolveMigrationClass($migration);
224
225
        try {
226
            $migrationClass->down($this->connection);
227
        } catch (\Exception $e) {
228
            return $this->log("<error>{$e->getMessage()}</error>");
229
        }
230
231
        $this->log("<info>Rolled back {$migration}.</info>");
232
233
        $this->repository->deleteRecord($migration);
234
    }
235
236
    /**
237
     * Reset the database by rolling back all migrations.
238
     *
239
     * @return array
240
     */
241
    public function reset()
242
    {
243
        $this->setUp();
244
245
        $toRollback = $this->repository->getRanMigrations();
246
247
        return $this->runRollback($toRollback);
248
    }
249
250
    /**
251
     * Reset the database and run all migrations.
252
     *
253
     * @return array
254
     */
255
    public function refresh()
256
    {
257
        $this->setUp();
258
259
        $toRollback = $this->repository->getRanMigrations();
260
261
        $this->runRollback($toRollback);
262
263
        $pendingMigrations = $this->getPendingMigrations();
264
265
        return $this->runPending($pendingMigrations);
266
    }
267
268
    /**
269
     * Perform setup procedures for migrations.
270
     */
271
    protected function setUp()
272
    {
273
        if (!$this->connection) {
274
            $this->setConnection();
275
        }
276
277
        $this->createMigrationsRepository();
278
279
        $this->makeDirectoryStructure($this->config->getAllDatabaseDirectories());
280
    }
281
282
    /**
283
     * Set connection to database on object.
284
     *
285
     * @return Pdo
286
     */
287
    public function setConnection()
288
    {
289
        $dbConfig = $this->config->get('database');
290
291
        $this->connection = $this->resolver->getConnection($dbConfig);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->resolver->getConnection($dbConfig) of type object<Yarak\DB\Phalcon\Db\Adapter\Pdo> is incompatible with the declared type object<Yarak\Migrations\...Phalcon\Db\Adapter\Pdo> of property $connection.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
292
293
        $this->repository->setConnection($this->connection);
294
295
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Yarak\Migrations\FileDate\FileDateMigrator) is incompatible with the return type declared by the interface Yarak\Migrations\Migrator::setConnection of type Yarak\Migrations\Pdo.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
296
    }
297
298
    /**
299
     * Return the connection.
300
     *
301
     * @return Phalcon\Db\Adapter\Pdo
302
     */
303
    public function getConnection()
304
    {
305
        return $this->connection;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->connection; (Yarak\Migrations\FileDate\Phalcon\Db\Adapter\Pdo) is incompatible with the return type declared by the interface Yarak\Migrations\Migrator::getConnection of type Yarak\Migrations\Phalcon\Db\Adapter\Pdo.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
306
    }
307
308
    /**
309
     * Create the migrations table if it doesn't exist.
310
     */
311
    protected function createMigrationsRepository()
312
    {
313
        if (!$this->repository->exists()) {
314
            $this->repository->create();
315
        }
316
317
        return $this;
318
    }
319
}
320