Completed
Push — Parse-Tags ( be9c93...0426dd )
by Michael
04:21
created

Migrations::doMigration()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 39
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

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