Completed
Push — master ( 9a3a3c...24148b )
by Michael
14s
created

Migrations::doMigration()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5.1727

Importance

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