Completed
Push — mysql_improvements ( 2e95ce...dedbef )
by Michael
03:52
created

Migrations::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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