Subversion-Projekte lars-tiefland.ci

Revision

Revision 2242 | Revision 2257 | Zur aktuellen Revision | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
68 lars 1
<?php
2
/**
3
 * CodeIgniter
4
 *
5
 * An open source application development framework for PHP
6
 *
7
 * This content is released under the MIT License (MIT)
8
 *
2254 lars 9
 * Copyright (c) 2014 - 2017, British Columbia Institute of Technology
68 lars 10
 *
11
 * Permission is hereby granted, free of charge, to any person obtaining a copy
12
 * of this software and associated documentation files (the "Software"), to deal
13
 * in the Software without restriction, including without limitation the rights
14
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
 * copies of the Software, and to permit persons to whom the Software is
16
 * furnished to do so, subject to the following conditions:
17
 *
18
 * The above copyright notice and this permission notice shall be included in
19
 * all copies or substantial portions of the Software.
20
 *
21
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
 * THE SOFTWARE.
28
 *
29
 * @package	CodeIgniter
30
 * @author	EllisLab Dev Team
31
 * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
2254 lars 32
 * @copyright	Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/)
68 lars 33
 * @license	http://opensource.org/licenses/MIT	MIT License
34
 * @link	https://codeigniter.com
35
 * @since	Version 3.0.0
36
 * @filesource
37
 */
38
defined('BASEPATH') OR exit('No direct script access allowed');
39
 
40
/**
41
 * Migration Class
42
 *
43
 * All migrations should implement this, forces up() and down() and gives
44
 * access to the CI super-global.
45
 *
46
 * @package		CodeIgniter
47
 * @subpackage	Libraries
48
 * @category	Libraries
49
 * @author		Reactor Engineers
50
 * @link
51
 */
52
class CI_Migration {
53
 
54
	/**
55
	 * Whether the library is enabled
56
	 *
57
	 * @var bool
58
	 */
59
	protected $_migration_enabled = FALSE;
60
 
61
	/**
62
	 * Migration numbering type
63
	 *
64
	 * @var	bool
65
	 */
66
	protected $_migration_type = 'sequential';
67
 
68
	/**
69
	 * Path to migration classes
70
	 *
71
	 * @var string
72
	 */
73
	protected $_migration_path = NULL;
74
 
75
	/**
76
	 * Current migration version
77
	 *
78
	 * @var mixed
79
	 */
80
	protected $_migration_version = 0;
81
 
82
	/**
83
	 * Database table with migration info
84
	 *
85
	 * @var string
86
	 */
87
	protected $_migration_table = 'migrations';
88
 
89
	/**
90
	 * Whether to automatically run migrations
91
	 *
92
	 * @var	bool
93
	 */
94
	protected $_migration_auto_latest = FALSE;
95
 
96
	/**
97
	 * Migration basename regex
98
	 *
99
	 * @var string
100
	 */
101
	protected $_migration_regex;
102
 
103
	/**
104
	 * Error message
105
	 *
106
	 * @var string
107
	 */
108
	protected $_error_string = '';
109
 
110
	/**
111
	 * Initialize Migration Class
112
	 *
113
	 * @param	array	$config
114
	 * @return	void
115
	 */
116
	public function __construct($config = array())
117
	{
118
		// Only run this constructor on main library load
119
		if ( ! in_array(get_class($this), array('CI_Migration', config_item('subclass_prefix').'Migration'), TRUE))
120
		{
121
			return;
122
		}
123
 
124
		foreach ($config as $key => $val)
125
		{
126
			$this->{'_'.$key} = $val;
127
		}
128
 
129
		log_message('info', 'Migrations Class Initialized');
130
 
131
		// Are they trying to use migrations while it is disabled?
132
		if ($this->_migration_enabled !== TRUE)
133
		{
134
			show_error('Migrations has been loaded but is disabled or set up incorrectly.');
135
		}
136
 
137
		// If not set, set it
138
		$this->_migration_path !== '' OR $this->_migration_path = APPPATH.'migrations/';
139
 
140
		// Add trailing slash if not set
141
		$this->_migration_path = rtrim($this->_migration_path, '/').'/';
142
 
143
		// Load migration language
144
		$this->lang->load('migration');
145
 
146
		// They'll probably be using dbforge
147
		$this->load->dbforge();
148
 
149
		// Make sure the migration table name was set.
150
		if (empty($this->_migration_table))
151
		{
152
			show_error('Migrations configuration file (migration.php) must have "migration_table" set.');
153
		}
154
 
155
		// Migration basename regex
156
		$this->_migration_regex = ($this->_migration_type === 'timestamp')
157
			? '/^\d{14}_(\w+)$/'
158
			: '/^\d{3}_(\w+)$/';
159
 
160
		// Make sure a valid migration numbering type was set.
161
		if ( ! in_array($this->_migration_type, array('sequential', 'timestamp')))
162
		{
163
			show_error('An invalid migration numbering type was specified: '.$this->_migration_type);
164
		}
165
 
166
		// If the migrations table is missing, make it
167
		if ( ! $this->db->table_exists($this->_migration_table))
168
		{
169
			$this->dbforge->add_field(array(
170
				'version' => array('type' => 'BIGINT', 'constraint' => 20),
171
			));
172
 
173
			$this->dbforge->create_table($this->_migration_table, TRUE);
174
 
175
			$this->db->insert($this->_migration_table, array('version' => 0));
176
		}
177
 
178
		// Do we auto migrate to the latest migration?
179
		if ($this->_migration_auto_latest === TRUE && ! $this->latest())
180
		{
181
			show_error($this->error_string());
182
		}
183
	}
184
 
185
	// --------------------------------------------------------------------
186
 
187
	/**
188
	 * Migrate to a schema version
189
	 *
190
	 * Calls each migration step required to get to the schema version of
191
	 * choice
192
	 *
193
	 * @param	string	$target_version	Target schema version
194
	 * @return	mixed	TRUE if no migrations are found, current version string on success, FALSE on failure
195
	 */
196
	public function version($target_version)
197
	{
198
		// Note: We use strings, so that timestamp versions work on 32-bit systems
199
		$current_version = $this->_get_version();
200
 
201
		if ($this->_migration_type === 'sequential')
202
		{
203
			$target_version = sprintf('%03d', $target_version);
204
		}
205
		else
206
		{
207
			$target_version = (string) $target_version;
208
		}
209
 
210
		$migrations = $this->find_migrations();
211
 
212
		if ($target_version > 0 && ! isset($migrations[$target_version]))
213
		{
214
			$this->_error_string = sprintf($this->lang->line('migration_not_found'), $target_version);
215
			return FALSE;
216
		}
217
 
218
		if ($target_version > $current_version)
219
		{
220
			$method = 'up';
221
		}
222
		elseif ($target_version < $current_version)
223
		{
224
			$method = 'down';
225
			// We need this so that migrations are applied in reverse order
226
			krsort($migrations);
227
		}
228
		else
229
		{
230
			// Well, there's nothing to migrate then ...
231
			return TRUE;
232
		}
233
 
234
		// Validate all available migrations within our target range.
235
		//
236
		// Unfortunately, we'll have to use another loop to run them
237
		// in order to avoid leaving the procedure in a broken state.
238
		//
239
		// See https://github.com/bcit-ci/CodeIgniter/issues/4539
240
		$pending = array();
241
		foreach ($migrations as $number => $file)
242
		{
243
			// Ignore versions out of our range.
244
			//
245
			// Because we've previously sorted the $migrations array depending on the direction,
246
			// we can safely break the loop once we reach $target_version ...
247
			if ($method === 'up')
248
			{
249
				if ($number <= $current_version)
250
				{
251
					continue;
252
				}
253
				elseif ($number > $target_version)
254
				{
255
					break;
256
				}
257
			}
258
			else
259
			{
260
				if ($number > $current_version)
261
				{
262
					continue;
263
				}
264
				elseif ($number <= $target_version)
265
				{
266
					break;
267
				}
268
			}
269
 
270
			// Check for sequence gaps
271
			if ($this->_migration_type === 'sequential')
272
			{
273
				if (isset($previous) && abs($number - $previous) > 1)
274
				{
275
					$this->_error_string = sprintf($this->lang->line('migration_sequence_gap'), $number);
276
					return FALSE;
277
				}
278
 
279
				$previous = $number;
280
			}
281
 
282
			include_once($file);
283
			$class = 'Migration_'.ucfirst(strtolower($this->_get_migration_name(basename($file, '.php'))));
284
 
285
			// Validate the migration file structure
286
			if ( ! class_exists($class, FALSE))
287
			{
288
				$this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class);
289
				return FALSE;
290
			}
1257 lars 291
			elseif ( ! is_callable(array($class, $method)))
68 lars 292
			{
293
				$this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class);
294
				return FALSE;
295
			}
296
 
297
			$pending[$number] = array($class, $method);
298
		}
299
 
300
		// Now just run the necessary migrations
301
		foreach ($pending as $number => $migration)
302
		{
303
			log_message('debug', 'Migrating '.$method.' from version '.$current_version.' to version '.$number);
304
 
305
			$migration[0] = new $migration[0];
306
			call_user_func($migration);
307
			$current_version = $number;
308
			$this->_update_version($current_version);
309
		}
310
 
311
		// This is necessary when moving down, since the the last migration applied
312
		// will be the down() method for the next migration up from the target
313
		if ($current_version <> $target_version)
314
		{
315
			$current_version = $target_version;
316
			$this->_update_version($current_version);
317
		}
318
 
319
		log_message('debug', 'Finished migrating to '.$current_version);
320
		return $current_version;
321
	}
322
 
323
	// --------------------------------------------------------------------
324
 
325
	/**
326
	 * Sets the schema to the latest migration
327
	 *
328
	 * @return	mixed	Current version string on success, FALSE on failure
329
	 */
330
	public function latest()
331
	{
332
		$migrations = $this->find_migrations();
333
 
334
		if (empty($migrations))
335
		{
336
			$this->_error_string = $this->lang->line('migration_none_found');
337
			return FALSE;
338
		}
339
 
340
		$last_migration = basename(end($migrations));
341
 
342
		// Calculate the last migration step from existing migration
343
		// filenames and proceed to the standard version migration
344
		return $this->version($this->_get_migration_number($last_migration));
345
	}
346
 
347
	// --------------------------------------------------------------------
348
 
349
	/**
350
	 * Sets the schema to the migration version set in config
351
	 *
352
	 * @return	mixed	TRUE if no migrations are found, current version string on success, FALSE on failure
353
	 */
354
	public function current()
355
	{
356
		return $this->version($this->_migration_version);
357
	}
358
 
359
	// --------------------------------------------------------------------
360
 
361
	/**
362
	 * Error string
363
	 *
364
	 * @return	string	Error message returned as a string
365
	 */
366
	public function error_string()
367
	{
368
		return $this->_error_string;
369
	}
370
 
371
	// --------------------------------------------------------------------
372
 
373
	/**
374
	 * Retrieves list of available migration scripts
375
	 *
376
	 * @return	array	list of migration file paths sorted by version
377
	 */
378
	public function find_migrations()
379
	{
380
		$migrations = array();
381
 
382
		// Load all *_*.php files in the migrations path
383
		foreach (glob($this->_migration_path.'*_*.php') as $file)
384
		{
385
			$name = basename($file, '.php');
386
 
387
			// Filter out non-migration files
388
			if (preg_match($this->_migration_regex, $name))
389
			{
390
				$number = $this->_get_migration_number($name);
391
 
392
				// There cannot be duplicate migration numbers
393
				if (isset($migrations[$number]))
394
				{
395
					$this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $number);
396
					show_error($this->_error_string);
397
				}
398
 
399
				$migrations[$number] = $file;
400
			}
401
		}
402
 
403
		ksort($migrations);
404
		return $migrations;
405
	}
406
 
407
	// --------------------------------------------------------------------
408
 
409
	/**
410
	 * Extracts the migration number from a filename
411
	 *
412
	 * @param	string	$migration
413
	 * @return	string	Numeric portion of a migration filename
414
	 */
415
	protected function _get_migration_number($migration)
416
	{
417
		return sscanf($migration, '%[0-9]+', $number)
418
			? $number : '0';
419
	}
420
 
421
	// --------------------------------------------------------------------
422
 
423
	/**
424
	 * Extracts the migration class name from a filename
425
	 *
426
	 * @param	string	$migration
427
	 * @return	string	text portion of a migration filename
428
	 */
429
	protected function _get_migration_name($migration)
430
	{
431
		$parts = explode('_', $migration);
432
		array_shift($parts);
433
		return implode('_', $parts);
434
	}
435
 
436
	// --------------------------------------------------------------------
437
 
438
	/**
439
	 * Retrieves current schema version
440
	 *
441
	 * @return	string	Current migration version
442
	 */
443
	protected function _get_version()
444
	{
445
		$row = $this->db->select('version')->get($this->_migration_table)->row();
446
		return $row ? $row->version : '0';
447
	}
448
 
449
	// --------------------------------------------------------------------
450
 
451
	/**
452
	 * Stores the current schema version
453
	 *
454
	 * @param	string	$migration	Migration reached
455
	 * @return	void
456
	 */
457
	protected function _update_version($migration)
458
	{
459
		$this->db->update($this->_migration_table, array(
460
			'version' => $migration
461
		));
462
	}
463
 
464
	// --------------------------------------------------------------------
465
 
466
	/**
467
	 * Enable the use of CI super-global
468
	 *
469
	 * @param	string	$var
470
	 * @return	mixed
471
	 */
472
	public function __get($var)
473
	{
474
		return get_instance()->$var;
475
	}
476
 
477
}