Completed
Push — master ( d6a52e...9b4e23 )
by Michael
02:57
created

Migrations::checkStatus()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 62

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 62
ccs 0
cts 28
cp 0
rs 8.2068
c 0
b 0
f 0
cc 6
nc 8
nop 0
crap 42

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Joomla! Statistics Server
4
 *
5
 * @copyright  Copyright (C) 2013 - 2017 Open Source Matters, Inc. All rights reserved.
6
 * @license    http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License Version 2 or Later
7
 */
8
9
namespace Joomla\StatsServer\Database;
10
11
use Joomla\Database\DatabaseDriver;
12
use Joomla\Database\Exception\ExecutionFailureException;
13
use Joomla\Database\Exception\PrepareStatementFailureException;
14
use Joomla\StatsServer\Database\Exception\CannotInitializeMigrationsException;
15
use Joomla\StatsServer\Database\Exception\UnknownMigrationException;
16
use League\Flysystem\Filesystem;
17
use League\Flysystem\UnreadableFileException;
18
19
/**
20
 * Class for managing database migrations
21
 */
22
class Migrations
23
{
24
	/**
25
	 * Database connector
26
	 *
27
	 * @var  DatabaseDriver
28
	 */
29
	private $database;
30
31
	/**
32
	 * Filesystem adapter
33
	 *
34
	 * @var  Filesystem
35
	 */
36
	private $filesystem;
37
38
	/**
39
	 * Constructor
40
	 *
41
	 * @param   DatabaseDriver  $database    Database connector
42
	 * @param   Filesystem      $filesystem  Filesystem adapter
43
	 */
44
	public function __construct(DatabaseDriver $database, Filesystem $filesystem)
45
	{
46
		$this->database   = $database;
47
		$this->filesystem = $filesystem;
48
	}
49
50
	/**
51
	 * Checks the migration status of the current installation
52
	 *
53
	 * @return  MigrationsStatus
54
	 */
55
	public function checkStatus(): MigrationsStatus
56
	{
57
		$response = new MigrationsStatus;
58
59
		try
60
		{
61
			// First get the list of applied migrations
62
			$appliedMigrations = $this->database->setQuery(
63
				$this->database->getQuery(true)
64
					->select('version')
65
					->from('#__migrations')
66
			)->loadColumn();
67
		}
68
		catch (ExecutionFailureException | PrepareStatementFailureException $exception)
69
		{
70
			// On PDO we're checking "42S02, 1146, Table 'XXX.#__migrations' doesn't exist"
71
			if (strpos($exception->getMessage(), "migrations' doesn't exist") === false)
72
			{
73
				throw $exception;
74
			}
75
76
			$response->tableExists = false;
77
78
			return $response;
79
		}
80
81
		// Now get the list of all known migrations
82
		$knownMigrations = [];
83
84
		foreach ($this->filesystem->listContents() as $migrationFiles)
85
		{
86
			$knownMigrations[] = $migrationFiles['filename'];
87
		}
88
89
		// Don't rely on file system ordering.
90
		sort($knownMigrations);
91
92
		// Validate all migrations are applied; the count and latest versions should match
93
		if (\count($appliedMigrations) === \count($knownMigrations))
94
		{
95
			$appliedValues = array_values($appliedMigrations);
96
			$knownValues   = array_values($knownMigrations);
97
98
			$latestApplied = (int) end($appliedValues);
99
			$latestKnown   = (int) end($knownValues);
100
101
			// Versions match, good to go
102
			if ($latestApplied === $latestKnown)
103
			{
104
				$response->latest = true;
105
106
				return $response;
107
			}
108
		}
109
110
		// The system is not on the latest version, get the relevant data
111
		$response->missingMigrations = \count($knownMigrations) - \count($appliedMigrations);
112
		$response->currentVersion    = array_pop($appliedMigrations);
113
		$response->latestVersion     = array_pop($knownMigrations);
114
115
		return $response;
116
	}
117
118
	/**
119
	 * Migrate the database
120
	 *
121
	 * @param   string|null  $version  Optional migration version to run
122
	 *
123
	 * @return  void
124
	 */
125
	public function migrateDatabase(?string $version = null): void
126
	{
127
		try
128
		{
129
			// Determine the migrations to apply
130
			$appliedMigrations = $this->database->setQuery(
131
				$this->database->getQuery(true)
132
					->select('version')
133
					->from('#__migrations')
134
			)->loadColumn();
135
		}
136
		catch (ExecutionFailureException | PrepareStatementFailureException $exception)
137
		{
138
			// If the table does not exist, we can still try to run migrations
139
			if (strpos($exception->getMessage(), "migrations' doesn't exist") === false)
140
			{
141
				throw $exception;
142
			}
143
144
			// If given a version, we can only execute it if it is the first migration, otherwise we've got other problems
145
			if ($version !== null && $version !== '')
146
			{
147
				$firstMigration = $this->filesystem->listContents()[0];
148
149
				if ($firstMigration['filename'] !== $version)
150
				{
151
					throw new CannotInitializeMigrationsException(
152
						'The migrations have not yet been initialized and the first migration has not been given as the version to run.'
153
					);
154
				}
155
			}
156
157
			$appliedMigrations = [];
158
		}
159
160
		// If a version is specified, check if that migration is already applied and if not, run that one only
161
		if ($version !== null && $version !== '')
162
		{
163
			// If it's already applied, there's nothing to do here
164
			if (\in_array($version, $appliedMigrations))
165
			{
166
				return;
167
			}
168
169
			$this->doMigration($version);
170
171
			return;
172
		}
173
174
		// We need to check the known migrations and filter out the applied ones to know what to do
175
		$knownMigrations = [];
176
177
		foreach ($this->filesystem->listContents() as $migrationFiles)
178
		{
179
			$knownMigrations[] = $migrationFiles['filename'];
180
		}
181
182
		foreach (array_diff($knownMigrations, $appliedMigrations) as $version)
183
		{
184
			$this->doMigration($version);
185
		}
186
	}
187
188
	/**
189
	 * Perform the database migration for the specified version
190
	 *
191
	 * @param   string  $version  Migration version to run
192
	 *
193
	 * @return  void
194
	 *
195
	 * @throws  UnknownMigrationException
196
	 * @throws  UnreadableFileException
197
	 */
198
	private function doMigration(string $version): void
199
	{
200
		$sqlFile = $version . '.sql';
201
202
		if (!$this->filesystem->has($sqlFile))
203
		{
204
			throw new UnknownMigrationException($sqlFile);
205
		}
206
207
		$queries = $this->filesystem->read($sqlFile);
208
209
		if ($queries === false)
210
		{
211
			throw new UnreadableFileException(
212
				sprintf(
213
					'Could not read data from the %s SQL file, please update the database manually.',
214
					$sqlFile
215
				)
216
			);
217
		}
218
219
		foreach (DatabaseDriver::splitSql($queries) as $query)
220
		{
221
			$query = trim($query);
222
223
			if (!empty($query))
224
			{
225
				$this->database->setQuery($query)->execute();
226
			}
227
		}
228
229
		// Log the migration into the database
230
		$this->database->setQuery(
231
			$this->database->getQuery(true)
232
				->insert('#__migrations')
233
				->columns('version')
234
				->values($version)
235
		)->execute();
236
	}
237
}
238