Skip to content

Commit 306f42f

Browse files
committed
Merge pull request #2 from joegreen88/master
pull latest up to smrtr
2 parents 8af1db4 + 1cdd1a0 commit 306f42f

File tree

4 files changed

+335
-0
lines changed

4 files changed

+335
-0
lines changed

bin/up.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
$app = new \Symfony\Component\Console\Application('up');
4+
5+
foreach (\Smrtr\MysqlVersionControl\DbConfig::getEnvironments() as $env) {
6+
7+
$app->add(
8+
new \Smrtr\MysqlVersionControl\UpCommand($env)
9+
);
10+
}
11+
12+
$app->run();
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace Smrtr\MysqlVersionControl;
4+
use Smrtr\MysqlVersionControlException;
5+
6+
/**
7+
* Class DbConfig reads database config from an ini file.
8+
*
9+
* @package Smrtr\MysqlVersionControl
10+
* @author Joe Green
11+
*/
12+
class DbConfig
13+
{
14+
public static function getEnvironments()
15+
{
16+
$config = new \Zend_Config_Ini(self::getConfigFile(), 'environments');
17+
$config = $config->toArray();
18+
return $config['environments'];
19+
}
20+
21+
public static function getTestingEnvironments()
22+
{
23+
$config = new \Zend_Config_Ini(self::getConfigFile(), 'environments');
24+
$config = $config->toArray();
25+
return $config['testing_environments'];
26+
}
27+
28+
public static function getPDO($env, $buildtime = false)
29+
{
30+
$key = $buildtime ? 'buildtime' : 'runtime';
31+
$config = self::getConfig($env);
32+
$config = $config[$key];
33+
34+
$dsn = sprintf('mysql:host=%s;dbname=%s', $config['host'], $config['dbname']);
35+
$db = new \PDO($dsn, $config['user'], $config['password']);
36+
37+
return $db;
38+
}
39+
40+
public static function getConfig($env)
41+
{
42+
$config = new \Zend_Config_Ini(self::getConfigFile(), $env);
43+
return $config->toArray();
44+
}
45+
46+
protected static function getConfigFile()
47+
{
48+
$configFile = self::getProjectPath() . '/db/db.ini';
49+
50+
if (!is_readable($configFile)) {
51+
throw new MysqlVersionControlException(
52+
"Cannot find or open database config; looked in '$configFile'"
53+
);
54+
}
55+
56+
return $configFile;
57+
}
58+
59+
protected static function getProjectPath()
60+
{
61+
$parts = explode('/vendor', __FILE__);
62+
array_pop($parts);
63+
return implode('/vendor', $parts);
64+
}
65+
}
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
<?php
2+
3+
namespace Smrtr\MysqlVersionControl;
4+
5+
use Symfony\Component\Console\Command\Command;
6+
use Symfony\Component\Console\Input\InputArgument;
7+
use Symfony\Component\Console\Input\InputInterface;
8+
use Symfony\Component\Console\Input\InputOption;
9+
use Symfony\Component\Console\Output\OutputInterface;
10+
use Symfony\Component\Process\Process;
11+
12+
/**
13+
* Class UpCommand
14+
* @package Smrtr\MysqlVersionControl
15+
* @author Joe Green
16+
*/
17+
class UpCommand extends Command
18+
{
19+
protected $env;
20+
21+
public function __construct($env)
22+
{
23+
$this->env = $env;
24+
parent::__construct();
25+
}
26+
27+
protected function configure()
28+
{
29+
$this
30+
->setName($this->env)
31+
->setDescription('Install the '.$this->env.' versions')
32+
->addArgument(
33+
'mysqlbin',
34+
InputArgument::OPTIONAL,
35+
'Where is the MySQL binary located?'
36+
)
37+
->addOption(
38+
'confirm',
39+
null,
40+
InputOption::VALUE_NONE,
41+
'If set, the command will bypass the confirmation prompt'
42+
)
43+
;
44+
}
45+
46+
/**
47+
* Load a few settings then run the installer.
48+
*
49+
* @param InputInterface $input
50+
* @param OutputInterface $output
51+
* @return int|null|void
52+
*/
53+
protected function execute(InputInterface $input, OutputInterface $output)
54+
{
55+
$mysqlbin = $input->getArgument('mysqlbin') ?: 'mysql';
56+
$buildConf = DbConfig::getPDO($this->env, true);
57+
$runConf = DbConfig::getPDO($this->env);
58+
59+
// 1. Make sure that db_config table is present
60+
61+
$output->writeln('');
62+
$output->writeln('Checking database status... ');
63+
$output->writeln('');
64+
65+
66+
if (!$buildConf instanceof \PDO) {
67+
$output->writeln('<error>Failed: unable to obtain a database connection.</error>');
68+
return false;
69+
}
70+
71+
if ($buildConf->query("SHOW TABLES LIKE 'db_config'")->rowCount()) {
72+
73+
$output->writeln('<info>Database version control is already installed.</info>');
74+
self::$checkList['db-schema'] = true;
75+
return true;
76+
77+
} else {
78+
79+
$output->writeln('Installing version control...');
80+
81+
$result = $buildConf->query(
82+
"CREATE TABLE `db_config`
83+
(
84+
`key` VARCHAR(50) COLLATE 'utf8_general_ci' NOT NULL,
85+
`value` TEXT,
86+
`created_at` DATETIME,
87+
`updated_at` DATETIME,
88+
PRIMARY KEY (`key`),
89+
UNIQUE INDEX `db_config_U_1` (`key`)
90+
) ENGINE=MyISAM;"
91+
)
92+
->execute();
93+
94+
if (!$result) {
95+
$output->writeln('<error>Installing version control failed.</error>');
96+
return false;
97+
}
98+
99+
$output->writeln('<info>Installed version control successfully.</info>');
100+
}
101+
102+
// 2. Check for current version and available version
103+
104+
// what is the current version?
105+
$query = $runConf->query("SELECT `value` FROM `db_config` WHERE `key`='version'");
106+
if ($query->rowCount()) {
107+
$versionRow = $query->fetch(\PDO::FETCH_ASSOC);
108+
$currentVersion = (int) $versionRow['value'];
109+
} else {
110+
$currentVersion = 0;
111+
}
112+
113+
// what is the available version?
114+
$availableVersion = 0;
115+
$versionsPath = realpath(dirname(__FILE__).'/../../../../../../db/versions');
116+
foreach (scandir($versionsPath) as $path) {
117+
if (preg_match("/^(\\d)+$/", $path) && (int) $path > $availableVersion) {
118+
$availableVersion = (int) $path;
119+
}
120+
}
121+
122+
if ($currentVersion >= $availableVersion) {
123+
$output->writeln('<info>Database version is already up to date.</info>');
124+
return true;
125+
}
126+
127+
$noun = ($availableVersion - $currentVersion > 1) ? 'updates' : 'update';
128+
$output->writeln(
129+
"Installing database $noun (Current version: $currentVersion, Available version: $availableVersion)..."
130+
);
131+
132+
// go from current to latest version, building stack of SQL files
133+
$filesToLookFor = [];
134+
$filesToLookFor[] = 'schema.sql'; // structural changes, alters, creates, drops
135+
$filesToLookFor[] = 'data.sql'; // core data, inserts, replaces, updates, deletes
136+
if (in_array($this->env, DbConfig::getTestingEnvironments())) {
137+
$filesToLookFor[] = 'testing.sql'; // extra data on top of data.sql for the testing environment(s)
138+
}
139+
$filesToLookFor[] = 'runme.php'; // custom php hook
140+
141+
$stack = array();
142+
for ($i = $currentVersion + 1; $i <= $availableVersion; $i++) {
143+
144+
$path = $versionsPath.DIRECTORY_SEPARATOR.$i;
145+
if (!is_dir($path) || !is_readable($path)) {
146+
continue;
147+
}
148+
149+
foreach ($filesToLookFor as $file) {
150+
if (is_readable($path.DIRECTORY_SEPARATOR.$file)) {
151+
$stack[$i][$file] = $path.DIRECTORY_SEPARATOR.$file;
152+
}
153+
}
154+
}
155+
156+
$s = '\\' == DIRECTORY_SEPARATOR ? "%s" : "'%s'"; // Windows doesn't like quoted params
157+
$cmdMySQL = "$mysqlbin -h $s --user=$s --password=$s --database=$s < %s";
158+
159+
// loop sql file stack and execute on mysql CLI
160+
161+
$dbConf = DbConfig::getConfig($this->env);
162+
163+
$previousVersion = $currentVersion;
164+
$result = true;
165+
foreach ($stack as $version => $files) {
166+
167+
$output->write($previousVersion." -> $version ");
168+
169+
if (!$result) {
170+
$output->write('skipped');
171+
continue;
172+
}
173+
174+
foreach ($files as $file) {
175+
176+
if ('schema.sql' === $file) {
177+
$conf = $dbConf['buildtime'];
178+
} else {
179+
$conf = $dbConf['runtime'];
180+
}
181+
$host = $conf['host'];
182+
$user = $conf['user'];
183+
$pass = $conf['password'];
184+
$name = $conf['database'];
185+
186+
if ('.sql' === substr($file, -4)) {
187+
188+
$command = sprintf(
189+
$cmdMySQL,
190+
$host,
191+
$user,
192+
$pass,
193+
$name,
194+
$file
195+
);
196+
197+
$process = new Process($command);
198+
$process->run();
199+
200+
if (!$process->isSuccessful()) {
201+
$result = false;
202+
break;
203+
}
204+
205+
continue;
206+
}
207+
208+
if ('.php' === substr($file, -4)) {
209+
210+
$feedback = require_once $file;
211+
}
212+
}
213+
214+
if ($result) {
215+
$result = $buildConf->query(
216+
"REPLACE INTO `db_config` (`key`, `value`, `updated_at`) VALUES ('version', $version, now())"
217+
)->execute();
218+
}
219+
220+
$statusMsg = $result ? '<info>OK</info>' : '<error>Failed</error>';
221+
$output->write($statusMsg, true);
222+
223+
if (isset($feedback) && is_string($feedback) && strlen($feedback)) {
224+
$output->write($feedback);
225+
unset($feedback);
226+
}
227+
228+
if (!$result) {
229+
$output->write('<error>'.$process->getErrorOutput().'</error>');
230+
}
231+
232+
$previousVersion = $version;
233+
}
234+
235+
if ($result) {
236+
$output->writeln('<info>Database updates installed successfully.</info>');
237+
self::$checkList['db-update'] = true;
238+
return true;
239+
240+
} else {
241+
$output->writeln('<error>Installing database updates failed.</error>');
242+
return false;
243+
}
244+
}
245+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Smrtr;
4+
5+
/**
6+
* Class MysqlVersionControlException
7+
* @package Smrtr
8+
* @author Joe Green
9+
*/
10+
class MysqlVersionControlException extends \Exception
11+
{
12+
// Intentionally empty
13+
}

0 commit comments

Comments
 (0)