Completed
Push — master ( 10f95b...bf15d4 )
by Michael
09:02
created

Migrations::migrateDatabase()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 34
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

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