Pluf Framework

Pluf Framework Git Source Tree

Root/src/Pluf/Migration.php

1<?php
2/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3/*
4# ***** BEGIN LICENSE BLOCK *****
5# This file is part of Plume Framework, a simple PHP Application Framework.
6# Copyright (C) 2001-2007 Loic d'Anterroches and contributors.
7#
8# Plume Framework is free software; you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License as published by
10# the Free Software Foundation; either version 2.1 of the License, or
11# (at your option) any later version.
12#
13# Plume Framework is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Lesser General Public License for more details.
17#
18# You should have received a copy of the GNU Lesser General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21#
22# ***** END LICENSE BLOCK ***** */
23
24/**
25 * A class to manage the migration of the code from one version to
26 * another, upward or downward.
27 *
28 * You can directly use the migrate.php script.
29 *
30 * Simple example usage:
31 *
32 * <pre>
33 * $m = new Pluf_Migration('MyApp');
34 * $m->migrate();
35 *
36 * // Install the application MyApp
37 * $m = new Pluf_Migration('MyApp');
38 * $m->install();
39 * // Uninstall the application MyApp
40 * $m->unInstall();
41 *
42 * $m = new Pluf_Migration();
43 * $m->migrate(); // migrate all the installed app to the newest version.
44 *
45 * $m = new Pluf_Migration();
46 * $m->migrate(3); // migrate (upgrade or downgrade) to version 3
47 * </pre>
48 *
49 */
50class Pluf_Migration
51{
52 protected $app = ''; /**< Application beeing migrated. */
53 public $apps = array(); /**< Applications which are going to be migrated. */
54 public $to_version = null; /**< Target version for the migration. */
55 public $dry_run = false; /**< Set to true to not act. */
56 public $display = false; /**< Display on the console what is done. */
57
58 /**
59 * Create a new migration.
60 *
61 * @param mixed Application or array of applications to migrate.
62 */
63 public function __construct($app=null)
64 {
65 if (!is_null($app)) {
66 if (is_array($app)) {
67 $this->apps = $app;
68 } else {
69 $this->apps = array($app);
70 }
71 } else {
72 $this->apps = Pluf::f('installed_apps');
73 }
74 }
75
76
77 /**
78 * Install the application.
79 *
80 * Basically run the base install function for each application
81 * and then set the version to the latest migration.
82 */
83 public function install()
84 {
85 foreach ($this->apps as $app) {
86 $this->installApp($app);
87 }
88 return true;
89 }
90
91 /**
92 * Uninstall the application.
93 */
94 public function unInstall()
95 {
96 $apps = array_reverse($this->apps);
97 foreach ($apps as $app) {
98 $this->installApp($app, true);
99 }
100 }
101
102 /**
103 * Backup the application.
104 *
105 * @param string Path to the backup folder
106 * @param string Backup name (null)
107 */
108 public function backup($path, $name=null)
109 {
110 foreach ($this->apps as $app) {
111 $func = $app.'_Migrations_Backup_run';
112 Pluf::loadFunction($func);
113 if ($this->display) {
114 echo($func."\n");
115 }
116 if (!$this->dry_run) {
117 $ret = $func($path, $name);
118 }
119 }
120 return true;
121 }
122
123 /**
124 * Restore the application.
125 *
126 * @param string Path to the backup folder
127 * @param string Backup name
128 */
129 public function restore($path, $name)
130 {
131 foreach ($this->apps as $app) {
132 $func = $app.'_Migrations_Backup_restore';
133 Pluf::loadFunction($func);
134 if ($this->display) {
135 echo($func."\n");
136 }
137 if (!$this->dry_run) {
138 $ret = $func($path, $name);
139 }
140 }
141 return true;
142 }
143
144 /**
145 * Run the migration.
146 *
147 */
148 public function migrate($to_version=null)
149 {
150 $this->to_version = $to_version;
151 foreach ($this->apps as $app) {
152 $this->app = $app;
153 $migrations = $this->findMigrations();
154 // The run will throw an exception in case of error.
155 $this->runMigrations($migrations);
156 }
157 return true;
158 }
159
160 /**
161 * Un/Install the given application.
162 *
163 * @param string Application to install.
164 * @param bool Uninstall (false)
165 */
166 public function installApp($app, $uninstall=false)
167 {
168 if ($uninstall) {
169 $func = $app.'_Migrations_Install_teardown';
170 } else {
171 $func = $app.'_Migrations_Install_setup';
172 }
173 $ret = true;
174 Pluf::loadFunction($func);
175 if ($this->display) {
176 echo($func."\n");
177 }
178 if (!$this->dry_run) {
179 $ret = $func(); // Run the install/uninstall
180 if (!$uninstall) {
181 //
182 $this->app = $app;
183 $migrations = $this->findMigrations();
184 if (count($migrations) > 0) {
185 $to_version = max(array_keys($migrations));
186 } else {
187 $to_version = 0;
188 }
189 $this->setAppVersion($app, $to_version);
190 } else {
191 if ($app != 'Pluf') {
192 // If Pluf we do not have the schema info table
193 // anymore
194 $this->delAppInfo($app);
195 }
196 }
197 }
198 return $ret;
199 }
200
201
202 /**
203 * Find the migrations for the current app.
204 *
205 * @return array Migrations names indexed by order.
206 */
207 public function findMigrations()
208 {
209 $migrations = array();
210 if (false !== ($mdir = Pluf::fileExists($this->app.'/Migrations'))) {
211 $dir = new DirectoryIterator($mdir);
212 foreach($dir as $file) {
213 $matches = array();
214 if (!$file->isDot() && !$file->isDir()
215 && preg_match('#^(\d+)#', $file->getFilename(), $matches)) {
216 $info = pathinfo($file->getFilename());
217 $migrations[(int)$matches[1]] = $info['filename'];
218 }
219 }
220 }
221 return $migrations;
222 }
223
224 /**
225 * Run the migrations.
226 *
227 * From an array of possible migrations, it will first get the
228 * current version of the app and then based on $this->to_version
229 * will run the migrations in the right order or do nothing if
230 * nothing to be done.
231 *
232 * @param array Possible migrations.
233 */
234 public function runMigrations($migrations)
235 {
236 if (empty($migrations)) {
237 return;
238 }
239 $current = $this->getAppVersion($this->app);
240 if ($this->to_version === null) {
241 $to_version = max(array_keys($migrations));
242 } else {
243 $to_version = $this->to_version;
244 }
245 if ($to_version == $current) {
246 return; // Nothing to do
247 }
248 $the_way = 'up'; // Tribute to Pat Metheny
249 if ($to_version > $current) {
250 // upgrade
251 $min = $current + 1;
252 $max = $to_version;
253 } else {
254 // downgrade
255 $the_way = 'do';
256 $max = $current;
257 $min = $to_version + 1;
258 }
259 // Filter the migrations
260 $to_run = array();
261 foreach ($migrations as $order=>$name) {
262 if ($order < $min or $order > $max) {
263 continue;
264 }
265 if ($the_way == 'up') {
266 $to_run[] = array($order, $name);
267 } else {
268 array_unshift($to_run, array($order, $name));
269 }
270 }
271 asort($to_run);
272 // Run the migrations
273 foreach ($to_run as $migration) {
274 $this->runMigration($migration, $the_way);
275 }
276 }
277
278 /**
279 * Run the given migration.
280 */
281 public function runMigration($migration, $the_way='up')
282 {
283 $target_version = ($the_way == 'up') ? $migration[0] : $migration[0]-1;
284 if ($this->display) {
285 echo($migration[0].' '.$migration[1].' '.$the_way."\n");
286 }
287 if (!$this->dry_run) {
288 if ($the_way == 'up') {
289 $func = $this->app.'_Migrations_'.$migration[1].'_up';
290 } else {
291 $func = $this->app.'_Migrations_'.$migration[1].'_down';
292 }
293 Pluf::loadFunction($func);
294 $func(); // Real migration run
295 $this->setAppVersion($this->app, $target_version);
296 }
297 }
298
299 /**
300 * Set the application version.
301 *
302 * @param string Application
303 * @param int Version
304 * @return true
305 */
306 public function setAppVersion($app, $version)
307 {
308 $gschema = new Pluf_DB_SchemaInfo();
309 $sql = new Pluf_SQL('application=%s', $app);
310 $appinfo = $gschema->getList(array('filter' => $sql->gen()));
311 if ($appinfo->count() == 1) {
312 $appinfo[0]->version = $version;
313 $appinfo[0]->update();
314 } else {
315 $schema = new Pluf_DB_SchemaInfo();
316 $schema->application = $app;
317 $schema->version = $version;
318 $schema->create();
319 }
320 return true;
321 }
322
323 /**
324 * Remove the application information.
325 *
326 * @param string Application
327 * @return true
328 */
329 public function delAppInfo($app)
330 {
331 $gschema = new Pluf_DB_SchemaInfo();
332 $sql = new Pluf_SQL('application=%s', $app);
333 $appinfo = $gschema->getList(array('filter' => $sql->gen()));
334 if ($appinfo->count() == 1) {
335 $appinfo[0]->delete();
336 }
337 return true;
338 }
339
340
341
342 /**
343 * Get the current version of the app.
344 *
345 * @param string Application.
346 * @return int Version.
347 */
348 public function getAppVersion($app)
349 {
350 try {
351 $db =& Pluf::db();
352 $res = $db->select('SELECT version FROM '.$db->pfx.'schema_info WHERE application='.$db->esc($app));
353 return (int) $res[0]['version'];
354 } catch (Exception $e) {
355 // We should not be here, only in the case of nothing
356 // installed. I am not sure if this is a good way to
357 // handle this border case anyway. Maybe better to have an
358 // 'install' method to run all the migrations in order.
359 return 0;
360 }
361 }
362}

Archive Download this file

Branches

Tags