├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin ├── smyver.php ├── teardown └── up ├── composer.json ├── composer.lock ├── examples ├── README.md ├── db.ini ├── softcoded-config │ ├── README.md │ ├── composer.json │ └── db │ │ ├── db.php │ │ └── versions │ │ ├── 1 │ │ ├── data.sql │ │ ├── schema.sql │ │ └── testing.sql │ │ ├── 2 │ │ ├── data.sql │ │ └── schema.sql │ │ └── 3 │ │ ├── data.sql │ │ └── schema.sql └── two-databases │ ├── README.md │ ├── composer.json │ └── db │ ├── config.php │ ├── employees │ ├── 1 │ │ ├── data.sql │ │ ├── schema.sql │ │ └── testing.sql │ ├── 2 │ │ ├── data.sql │ │ └── schema.sql │ └── 3 │ │ ├── data.sql │ │ └── schema.sql │ └── web │ ├── 1 │ ├── data.sql │ └── schema.sql │ └── 2 │ └── testing.sql └── src └── Smrtr ├── MysqlVersionControl ├── Command │ ├── Parameters │ │ ├── CommonParametersTrait.php │ │ └── ComposerParams.php │ ├── Status.php │ ├── Teardown.php │ └── Up.php ├── DbConfig.php ├── DbConfigAdapter │ ├── ConfigAdapterInterface.php │ ├── Ini.php │ ├── PhpArray.php │ └── PhpFile.php ├── Helper │ ├── Configuration.php │ └── VersionPaths.php └── Receiver │ ├── Status.php │ ├── Teardown.php │ └── Up.php └── MysqlVersionControlException.php /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | !build/phpcs.xml 3 | !build/phpmd.xml 4 | vendor/* 5 | .idea/* 6 | .DS_Store -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | ## 1.3.0 5 | 6 | ### General Improvements 7 | 8 | - Added a new command: `status` 9 | - Added composer parameter loading to CLI commands 10 | - Bundle all commands into single script smyver.php 11 | - Added port configuration option 12 | - Refactor: implemented the command pattern 13 | - Refactor: implemented the configuration adapter pattern 14 | - Added some examples 15 | 16 | ### Configuration adapters: 17 | 18 | - Ini (the default adapter) 19 | - PhpArray 20 | - PhpFile 21 | 22 | ### Up command: 23 | 24 | - Option `--versions-path` is relative to project root if not an absolute path 25 | 26 | ## 1.2.0 27 | 28 | ### Up command: 29 | 30 | - Added option `--install-provisional-version` 31 | - Added option `--provisional-version` 32 | 33 | ## 1.1.0 34 | 35 | ### Up command: 36 | 37 | - Added option `--no-schema` 38 | - Added option `--versions-path` 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Joe Green 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mysql-version-control 2 | ===================== 3 | 4 | Effective mysql database version control in one library. Quick and simple to use, but with a rich and flexible 5 | API for those who want to customise the set up. 6 | 7 | ## Requirements 8 | - PHP >= 5.4 9 | - Composer 10 | - MySQL client 11 | 12 | Install with composer: 13 | 14 | $ composer require smrtr/mysql-version-control:~1.0 15 | 16 | # Versioning 17 | Your database versions will be stored in `/db/versions` by default. 18 | The sql for each version is stored in a directory directly under this directory. 19 | So the directories are named `db/versions/1`, `db/versions/2` and so on. 20 | Each version must contain at least one of the following files: 21 | 22 | - `schema.sql` - always runs first, contains `CREATE TABLE IF NOT EXISTS` and `ALTER` statements and the like. 23 | - `data.sql` - contains `REPLACE INTO`, `INSERT`, `UPDATE` and `DELETE` statements and the like. 24 | - `testing.sql` - same as `data.sql` but with test data which doesn't need to exist outside of testing environments. 25 | - `runme.php` - a custom php hook for running php code with your version. 26 | 27 | The files for each version are run in the order specified above. 28 | 29 | # Configuration 30 | The quickest way to get started is to set up your database configuration in a file at `/db/db.ini`. 31 | 32 | See *examples/db.ini* for an example of this file. 33 | 34 | ### Environments 35 | Define a list of environments and testing environments under the tag `[environments]`. 36 | 37 | List out all of the available environments with entries like so: `environments[] = "local-dev"`. 38 | 39 | List the testing environments like so: `testing_environments[] = "local-dev"`. 40 | This list is a subset of the environments list and comprises those environments which should receive test data. 41 | 42 | ### Connections 43 | You must define two database connection configurations for each environment. 44 | The two configurations are called `buildtime` and `runtime` and they are used for processing schema changes and data 45 | changes respectively. 46 | 47 | Each connection requires a `host`, `user`, `password` and `database`. You can optionally add a `port`. 48 | 49 | # Command Line Interface 50 | The command line tool is located at `vendor/bin/smyver.php`. 51 | 52 | *Remember it!* It stands for **S**mrtr **MY**sql **VER**sion control. 53 | 54 | ## status 55 | Run `vendor/bin/smyver.php status ` to get the current status of the database for that environment. 56 | 57 | ## up 58 | Run `vendor/bin/smyver.php up ` to install or update the database on the given environment. 59 | This command looks at the available versions in the `db/versions` directory and applies new versions sequentially 60 | from the current version. 61 | 62 | If this is the first run on the given environment, then a table called `db_config` is created and used to store the 63 | current database version. 64 | 65 | You may optionally provide a second argument specifying the mysql client binary to use. 66 | This argument is required if mysql is not on your $PATH. 67 | 68 | #### `--no-schema` 69 | Use this flag to skip the schema files. This can be useful if you use an ORM to build the database schema. 70 | 71 | #### `--install-provisional-version` 72 | Use this flag to install a provisional version. This allows you to test out your database version, which may currently 73 | be in development, before you commit to it by giving it a version number. This command looks for your provisional 74 | version in `/db/versions/new` by default. 75 | 76 | ## teardown 77 | Run `vendor/bin/smyver.php teardown ` to tear down the tables on the given environment. 78 | 79 | This command is useful for development & testing environments. 80 | 81 | Use the `confirm` option to bypass the confirmation prompt, e.g. 82 | 83 | vendor/bin/smyver.php --confirm 84 | 85 | ## Global CLI options 86 | These options can be used with all console commands. 87 | 88 | #### `--config-adapter` 89 | Specify a configuration adapter to use instead of the Ini adapter which is used by default. 90 | 91 | If you are using one of the standard adapters shipped with this package you only need to enter the class name, 92 | e.g. PhpFile. 93 | 94 | If you are using your own custom adapter class then you must provide a fully qualified class name and your class 95 | must implement `Smrtr\MysqlVersionControl\DbConfigAdapter\ConfigAdapterInterface`. 96 | 97 | #### `--config-adapter-param` 98 | You can specify one or more constructor parameters for the configuration adapter class with this option. 99 | 100 | To specify multiple parameters simply use the option more than once, e.g. 101 | `--config-adapter-param="One" --config-adapter-param="Two"` 102 | would result in the configuration adapter being instantiated like so: `new $adapter("One", "Two")`. 103 | 104 | #### `--provisional-version` 105 | Use this option to provide a custom path to your provisional version. Your custom path is relative to the versions path. 106 | 107 | #### `--versions-path` 108 | Use this option, or `-p` for short, to provide a custom path to your versions. 109 | This allows you to override the default versions path which is `/db/versions`. 110 | If the path provided is not an absolute path then it is assumed to be relative to the project root. -------------------------------------------------------------------------------- /bin/smyver.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 6 | */ 7 | 8 | require_once realpath(dirname(__FILE__).'/../../../autoload.php'); 9 | 10 | $app = new \Symfony\Component\Console\Application('smyver'); 11 | 12 | $app->add(new \Smrtr\MysqlVersionControl\Command\Status); 13 | $app->add(new \Smrtr\MysqlVersionControl\Command\Up); 14 | $app->add(new \Smrtr\MysqlVersionControl\Command\Teardown); 15 | 16 | $app->run(); 17 | -------------------------------------------------------------------------------- /bin/teardown: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | this_dir="$(cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | php "$this_dir/smyver.php" teardown "$@" -------------------------------------------------------------------------------- /bin/up: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | this_dir="$(cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | php "$this_dir/smyver.php" up "$@" 6 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smrtr/mysql-version-control", 3 | "description": "A crude version control system for mysql written in php", 4 | "keywords": ["database", "db", "mysql", "version control", "version", "migration", "update", "data", "structure"], 5 | "bin": ["bin/up", "bin/teardown", "bin/smyver.php"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Joe Green", 10 | "email": "joe.green@smrtr.co.uk" 11 | } 12 | ], 13 | "require": { 14 | "symfony/console": "~2.7", 15 | "symfony/process": "~2.7", 16 | "hearstuk/zf1-component-config": "~1.1" 17 | }, 18 | "autoload": { 19 | "psr-0": { 20 | "": "src/" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "1f6cd7b2e0b457f563c5f3a5e8209a39", 8 | "content-hash": "b3730a36bd19d217967aaeaa2524c36f", 9 | "packages": [ 10 | { 11 | "name": "hearstuk/zf1-component-config", 12 | "version": "1.1", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/joegreen88/zf1-component-config.git", 16 | "reference": "144959fee0ba098acb0a33e50a377ff90583161b" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/joegreen88/zf1-component-config/zipball/144959fee0ba098acb0a33e50a377ff90583161b", 21 | "reference": "144959fee0ba098acb0a33e50a377ff90583161b", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "hearstuk/zf1-components-base": "~1.2", 26 | "php": ">=5.3.0" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "3.7.20" 30 | }, 31 | "type": "library", 32 | "autoload": { 33 | "classmap": [ 34 | "src/Zend/Config.php" 35 | ], 36 | "psr-0": { 37 | "Zend_Config": "src/", 38 | "Tests\\Zend\\Config": "" 39 | } 40 | }, 41 | "notification-url": "https://packagist.org/downloads/", 42 | "authors": [ 43 | { 44 | "name": "Joe Green", 45 | "email": "Joe.Green@hearst.co.uk" 46 | } 47 | ], 48 | "description": "The Zend_Config component from Zend Framework 1.12 abstracted out for use with composer.", 49 | "homepage": "http://github.com/joegreen88/zf1-component-config", 50 | "time": "2013-09-19 16:12:46" 51 | }, 52 | { 53 | "name": "hearstuk/zf1-components-base", 54 | "version": "1.2", 55 | "source": { 56 | "type": "git", 57 | "url": "https://github.com/joegreen88/zf1-components-base.git", 58 | "reference": "b3833cd81ab01ccfacd8c34177b7f4e3032cd611" 59 | }, 60 | "dist": { 61 | "type": "zip", 62 | "url": "https://api.github.com/repos/joegreen88/zf1-components-base/zipball/b3833cd81ab01ccfacd8c34177b7f4e3032cd611", 63 | "reference": "b3833cd81ab01ccfacd8c34177b7f4e3032cd611", 64 | "shasum": "" 65 | }, 66 | "require": { 67 | "php": ">=5.3.0" 68 | }, 69 | "require-dev": { 70 | "phpunit/phpunit": "3.7.20" 71 | }, 72 | "type": "library", 73 | "autoload": { 74 | "classmap": [ 75 | "src/Zend/Exception.php", 76 | "src/Zend/Loader.php", 77 | "src/Zend/Registry.php", 78 | "src/Zend/Version.php" 79 | ], 80 | "psr-0": { 81 | "Zend_Loader": "src/", 82 | "Tests\\Zend": "" 83 | } 84 | }, 85 | "notification-url": "https://packagist.org/downloads/", 86 | "license": [ 87 | "MIT" 88 | ], 89 | "authors": [ 90 | { 91 | "name": "Joe Green", 92 | "email": "Joe.Green@hearst.co.uk" 93 | } 94 | ], 95 | "description": "Base files required for abstracted composer-compatible ZF1 components.", 96 | "homepage": "http://github.com/joegreen88/zf1-components-base", 97 | "time": "2013-09-19 16:03:57" 98 | }, 99 | { 100 | "name": "symfony/console", 101 | "version": "v2.8.8", 102 | "source": { 103 | "type": "git", 104 | "url": "https://github.com/symfony/console.git", 105 | "reference": "c392a6ec72f2122748032c2ad6870420561ffcfa" 106 | }, 107 | "dist": { 108 | "type": "zip", 109 | "url": "https://api.github.com/repos/symfony/console/zipball/c392a6ec72f2122748032c2ad6870420561ffcfa", 110 | "reference": "c392a6ec72f2122748032c2ad6870420561ffcfa", 111 | "shasum": "" 112 | }, 113 | "require": { 114 | "php": ">=5.3.9", 115 | "symfony/polyfill-mbstring": "~1.0" 116 | }, 117 | "require-dev": { 118 | "psr/log": "~1.0", 119 | "symfony/event-dispatcher": "~2.1|~3.0.0", 120 | "symfony/process": "~2.1|~3.0.0" 121 | }, 122 | "suggest": { 123 | "psr/log": "For using the console logger", 124 | "symfony/event-dispatcher": "", 125 | "symfony/process": "" 126 | }, 127 | "type": "library", 128 | "extra": { 129 | "branch-alias": { 130 | "dev-master": "2.8-dev" 131 | } 132 | }, 133 | "autoload": { 134 | "psr-4": { 135 | "Symfony\\Component\\Console\\": "" 136 | }, 137 | "exclude-from-classmap": [ 138 | "/Tests/" 139 | ] 140 | }, 141 | "notification-url": "https://packagist.org/downloads/", 142 | "license": [ 143 | "MIT" 144 | ], 145 | "authors": [ 146 | { 147 | "name": "Fabien Potencier", 148 | "email": "fabien@symfony.com" 149 | }, 150 | { 151 | "name": "Symfony Community", 152 | "homepage": "https://symfony.com/contributors" 153 | } 154 | ], 155 | "description": "Symfony Console Component", 156 | "homepage": "https://symfony.com", 157 | "time": "2016-06-29 07:02:14" 158 | }, 159 | { 160 | "name": "symfony/polyfill-mbstring", 161 | "version": "v1.2.0", 162 | "source": { 163 | "type": "git", 164 | "url": "https://github.com/symfony/polyfill-mbstring.git", 165 | "reference": "dff51f72b0706335131b00a7f49606168c582594" 166 | }, 167 | "dist": { 168 | "type": "zip", 169 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594", 170 | "reference": "dff51f72b0706335131b00a7f49606168c582594", 171 | "shasum": "" 172 | }, 173 | "require": { 174 | "php": ">=5.3.3" 175 | }, 176 | "suggest": { 177 | "ext-mbstring": "For best performance" 178 | }, 179 | "type": "library", 180 | "extra": { 181 | "branch-alias": { 182 | "dev-master": "1.2-dev" 183 | } 184 | }, 185 | "autoload": { 186 | "psr-4": { 187 | "Symfony\\Polyfill\\Mbstring\\": "" 188 | }, 189 | "files": [ 190 | "bootstrap.php" 191 | ] 192 | }, 193 | "notification-url": "https://packagist.org/downloads/", 194 | "license": [ 195 | "MIT" 196 | ], 197 | "authors": [ 198 | { 199 | "name": "Nicolas Grekas", 200 | "email": "p@tchwork.com" 201 | }, 202 | { 203 | "name": "Symfony Community", 204 | "homepage": "https://symfony.com/contributors" 205 | } 206 | ], 207 | "description": "Symfony polyfill for the Mbstring extension", 208 | "homepage": "https://symfony.com", 209 | "keywords": [ 210 | "compatibility", 211 | "mbstring", 212 | "polyfill", 213 | "portable", 214 | "shim" 215 | ], 216 | "time": "2016-05-18 14:26:46" 217 | }, 218 | { 219 | "name": "symfony/process", 220 | "version": "v2.8.8", 221 | "source": { 222 | "type": "git", 223 | "url": "https://github.com/symfony/process.git", 224 | "reference": "89f33c16796415ccfd8bb3cf8d520cbb79899bfe" 225 | }, 226 | "dist": { 227 | "type": "zip", 228 | "url": "https://api.github.com/repos/symfony/process/zipball/89f33c16796415ccfd8bb3cf8d520cbb79899bfe", 229 | "reference": "89f33c16796415ccfd8bb3cf8d520cbb79899bfe", 230 | "shasum": "" 231 | }, 232 | "require": { 233 | "php": ">=5.3.9" 234 | }, 235 | "type": "library", 236 | "extra": { 237 | "branch-alias": { 238 | "dev-master": "2.8-dev" 239 | } 240 | }, 241 | "autoload": { 242 | "psr-4": { 243 | "Symfony\\Component\\Process\\": "" 244 | }, 245 | "exclude-from-classmap": [ 246 | "/Tests/" 247 | ] 248 | }, 249 | "notification-url": "https://packagist.org/downloads/", 250 | "license": [ 251 | "MIT" 252 | ], 253 | "authors": [ 254 | { 255 | "name": "Fabien Potencier", 256 | "email": "fabien@symfony.com" 257 | }, 258 | { 259 | "name": "Symfony Community", 260 | "homepage": "https://symfony.com/contributors" 261 | } 262 | ], 263 | "description": "Symfony Process Component", 264 | "homepage": "https://symfony.com", 265 | "time": "2016-06-29 05:29:29" 266 | } 267 | ], 268 | "packages-dev": [], 269 | "aliases": [], 270 | "minimum-stability": "stable", 271 | "stability-flags": [], 272 | "prefer-stable": false, 273 | "prefer-lowest": false, 274 | "platform": [], 275 | "platform-dev": [] 276 | } 277 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | These examples demonstrate some different set ups and features that can help you to understand mysql-version-control and 5 | get the most out of the smyver CLI tool. 6 | -------------------------------------------------------------------------------- /examples/db.ini: -------------------------------------------------------------------------------- 1 | [environments] 2 | environments[] = "dev" 3 | environments[] = "prod" 4 | testing_environments[] = "dev" 5 | 6 | [dev] 7 | buildtime.host = "localhost" 8 | buildtime.user = "root" 9 | buildtime.password = "root-password" 10 | buildtime.database = "my_app" 11 | runtime.host = "localhost" 12 | runtime.user = "developer" 13 | runtime.password = "developer-password" 14 | runtime.database = "my_app" 15 | 16 | [prod] 17 | buildtime.host = "db-production" 18 | buildtime.user = "root" 19 | buildtime.password = "root-password" 20 | buildtime.database = "my_app" 21 | runtime.host = "db-production" 22 | runtime.user = "www" 23 | runtime.password = "www-password" 24 | runtime.database = "my_app" 25 | -------------------------------------------------------------------------------- /examples/softcoded-config/README.md: -------------------------------------------------------------------------------- 1 | Example: softcoded database configuration 2 | ========================================= 3 | 4 | **Level:** Advanced 5 | 6 | This sample configuration loads the database connection from a php file with softcoded values that come from 7 | the server environment. 8 | 9 | - composer.json points to a custom config file 10 | - the custom config file loads database config from the `$_SERVER` variable. 11 | 12 | In this example the buildtime and runtime connections are identical. 13 | 14 | ## The details 15 | 16 | The variable `$_SERVER["APP_ENV"]` needs to be one of the values in the *environments* section of `db/db.php`. 17 | 18 | The following `$_SERVER` variables also need to be defined: 19 | 20 | - DB_HOST 21 | - DB_USERNAME 22 | - DB_PASSWORD 23 | - DB_NAME 24 | - DB_PORT 25 | 26 | ## Usage 27 | 28 | Just run the smyver commands without any additional parameters, e.g.: 29 | 30 | $ vendor/bin/smyver.php status development 31 | $ vendor/bin/smyver.php up development 32 | 33 | ## Notes 34 | 35 | To use this set up you must run the smyver commands for each database environment from the correct app environment. 36 | In other words, you cannot update the production db from the development environment. 37 | You may consider this an inconvenience or a security enhancement. -------------------------------------------------------------------------------- /examples/softcoded-config/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "smrtr/mysql-version-control": "~1.3" 4 | }, 5 | "extra": { 6 | "mysql-version-control": { 7 | "cli": { 8 | "--config-adapter": "PhpFile", 9 | "--config-adapter-param": ["db/db.php"] 10 | } 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /examples/softcoded-config/db/db.php: -------------------------------------------------------------------------------- 1 | $_SERVER["DB_HOST"], 10 | 'user' => $_SERVER["DB_USERNAME"], 11 | 'password' => $_SERVER["DB_PASSWORD"], 12 | 'database' => $_SERVER["DB_NAME"], 13 | 'port' => $_SERVER["DB_PORT"], 14 | ]; 15 | 16 | return [ 17 | 'environments' => [ 18 | 'environments' => ['development', 'qa', 'staging', 'production'], 19 | 'testing_environments' => ['development', 'qa'], 20 | ], 21 | $_SERVER["APP_ENV"] => [ 22 | 'buildtime' => $dbConf, 23 | 'runtime' => $dbConf, 24 | ] 25 | ]; 26 | -------------------------------------------------------------------------------- /examples/softcoded-config/db/versions/1/data.sql: -------------------------------------------------------------------------------- 1 | # noinspection SqlNoDataSourceInspectionForFile 2 | INSERT INTO `departments` VALUES 3 | ('d001','Marketing'), 4 | ('d002','Finance'), 5 | ('d003','Human Resources'), 6 | ('d004','Production'), 7 | ('d005','Development'), 8 | ('d006','Quality Management'), 9 | ('d007','Sales'), 10 | ('d008','Research'), 11 | ('d009','Customer Service'); 12 | 13 | INSERT INTO `employees` VALUES 14 | (10001,'1953-09-02','Georgi','Facello','M','1986-06-26'), 15 | (10002,'1964-06-02','Bezalel','Simmel','F','1985-11-21'), 16 | (10003,'1959-12-03','Parto','Bamford','M','1986-08-28'), 17 | (10004,'1954-05-01','Chirstian','Koblick','M','1986-12-01'), 18 | (10005,'1955-01-21','Kyoichi','Maliniak','M','1989-09-12'), 19 | (10006,'1953-04-20','Anneke','Preusig','F','1989-06-02'), 20 | (10007,'1957-05-23','Tzvetan','Zielinski','F','1989-02-10'), 21 | (10008,'1958-02-19','Saniya','Kalloufi','M','1994-09-15'), 22 | (10009,'1952-04-19','Sumant','Peac','F','1985-02-18'), 23 | (10010,'1963-06-01','Duangkaew','Piveteau','F','1989-08-24'); -------------------------------------------------------------------------------- /examples/softcoded-config/db/versions/1/schema.sql: -------------------------------------------------------------------------------- 1 | -- Sample employee database 2 | -- See changelog table for details 3 | -- Copyright (C) 2007,2008, MySQL AB 4 | -- 5 | -- Original data created by Fusheng Wang and Carlo Zaniolo 6 | -- http://www.cs.aau.dk/TimeCenter/software.htm 7 | -- http://www.cs.aau.dk/TimeCenter/Data/employeeTemporalDataSet.zip 8 | -- 9 | -- Current schema by Giuseppe Maxia 10 | -- Data conversion from XML to relational by Patrick Crews 11 | -- 12 | -- This work is licensed under the 13 | -- Creative Commons Attribution-Share Alike 3.0 Unported License. 14 | -- To view a copy of this license, visit 15 | -- http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to 16 | -- Creative Commons, 171 Second Street, Suite 300, San Francisco, 17 | -- California, 94105, USA. 18 | -- 19 | -- DISCLAIMER 20 | -- To the best of our knowledge, this data is fabricated, and 21 | -- it does not correspond to real people. 22 | -- Any similarity to existing people is purely coincidental. 23 | -- 24 | 25 | CREATE TABLE employees ( 26 | emp_no INT NOT NULL, 27 | birth_date DATE NOT NULL, 28 | first_name VARCHAR(14) NOT NULL, 29 | last_name VARCHAR(16) NOT NULL, 30 | gender ENUM ('M','F') NOT NULL, 31 | hire_date DATE NOT NULL, 32 | PRIMARY KEY (emp_no) 33 | ); 34 | 35 | CREATE TABLE departments ( 36 | dept_no CHAR(4) NOT NULL, 37 | dept_name VARCHAR(40) NOT NULL, 38 | PRIMARY KEY (dept_no), 39 | UNIQUE KEY (dept_name) 40 | ); 41 | -------------------------------------------------------------------------------- /examples/softcoded-config/db/versions/1/testing.sql: -------------------------------------------------------------------------------- 1 | # noinspection SqlNoDataSourceInspectionForFile 2 | INSERT INTO `departments` VALUES 3 | ('d0010','Test Department'); 4 | 5 | INSERT INTO `employees` VALUES 6 | (10011,'1953-09-02','Testing','Male','M','1986-06-26'); -------------------------------------------------------------------------------- /examples/softcoded-config/db/versions/2/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `dept_emp` VALUES 2 | (10001,'d005','1986-06-26','9999-01-01'), 3 | (10002,'d007','1996-08-03','9999-01-01'), 4 | (10003,'d004','1995-12-03','9999-01-01'), 5 | (10004,'d004','1986-12-01','9999-01-01'), 6 | (10005,'d003','1989-09-12','9999-01-01'), 7 | (10006,'d005','1990-08-05','9999-01-01'), 8 | (10007,'d008','1989-02-10','9999-01-01'), 9 | (10008,'d005','1998-03-11','2000-07-31'), 10 | (10009,'d006','1985-02-18','9999-01-01'), 11 | (10010,'d004','1996-11-24','2000-06-26'), 12 | (10010,'d006','2000-06-26','9999-01-01'); 13 | 14 | -------------------------------------------------------------------------------- /examples/softcoded-config/db/versions/2/schema.sql: -------------------------------------------------------------------------------- 1 | -- Sample employee database 2 | -- See changelog table for details 3 | -- Copyright (C) 2007,2008, MySQL AB 4 | -- 5 | -- Original data created by Fusheng Wang and Carlo Zaniolo 6 | -- http://www.cs.aau.dk/TimeCenter/software.htm 7 | -- http://www.cs.aau.dk/TimeCenter/Data/employeeTemporalDataSet.zip 8 | -- 9 | -- Current schema by Giuseppe Maxia 10 | -- Data conversion from XML to relational by Patrick Crews 11 | -- 12 | -- This work is licensed under the 13 | -- Creative Commons Attribution-Share Alike 3.0 Unported License. 14 | -- To view a copy of this license, visit 15 | -- http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to 16 | -- Creative Commons, 171 Second Street, Suite 300, San Francisco, 17 | -- California, 94105, USA. 18 | -- 19 | -- DISCLAIMER 20 | -- To the best of our knowledge, this data is fabricated, and 21 | -- it does not correspond to real people. 22 | -- Any similarity to existing people is purely coincidental. 23 | -- 24 | 25 | CREATE TABLE dept_emp ( 26 | emp_no INT NOT NULL, 27 | dept_no CHAR(4) NOT NULL, 28 | from_date DATE NOT NULL, 29 | to_date DATE NOT NULL, 30 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 31 | FOREIGN KEY (dept_no) REFERENCES departments (dept_no) ON DELETE CASCADE, 32 | PRIMARY KEY (emp_no,dept_no) 33 | ); 34 | -------------------------------------------------------------------------------- /examples/softcoded-config/db/versions/3/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `titles` VALUES (10001,'Senior Engineer','1986-06-26','9999-01-01'), 2 | (10002,'Staff','1996-08-03','9999-01-01'), 3 | (10003,'Senior Engineer','1995-12-03','9999-01-01'), 4 | (10004,'Engineer','1986-12-01','1995-12-01'), 5 | (10004,'Senior Engineer','1995-12-01','9999-01-01'), 6 | (10005,'Senior Staff','1996-09-12','9999-01-01'), 7 | (10005,'Staff','1989-09-12','1996-09-12'), 8 | (10006,'Senior Engineer','1990-08-05','9999-01-01'), 9 | (10007,'Senior Staff','1996-02-11','9999-01-01'), 10 | (10007,'Staff','1989-02-10','1996-02-11'), 11 | (10008,'Assistant Engineer','1998-03-11','2000-07-31'), 12 | (10009,'Assistant Engineer','1985-02-18','1990-02-18'), 13 | (10009,'Engineer','1990-02-18','1995-02-18'), 14 | (10009,'Senior Engineer','1995-02-18','9999-01-01'), 15 | (10010,'Engineer','1996-11-24','9999-01-01'); -------------------------------------------------------------------------------- /examples/softcoded-config/db/versions/3/schema.sql: -------------------------------------------------------------------------------- 1 | -- Sample employee database 2 | -- See changelog table for details 3 | -- Copyright (C) 2007,2008, MySQL AB 4 | -- 5 | -- Original data created by Fusheng Wang and Carlo Zaniolo 6 | -- http://www.cs.aau.dk/TimeCenter/software.htm 7 | -- http://www.cs.aau.dk/TimeCenter/Data/employeeTemporalDataSet.zip 8 | -- 9 | -- Current schema by Giuseppe Maxia 10 | -- Data conversion from XML to relational by Patrick Crews 11 | -- 12 | -- This work is licensed under the 13 | -- Creative Commons Attribution-Share Alike 3.0 Unported License. 14 | -- To view a copy of this license, visit 15 | -- http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to 16 | -- Creative Commons, 171 Second Street, Suite 300, San Francisco, 17 | -- California, 94105, USA. 18 | -- 19 | -- DISCLAIMER 20 | -- To the best of our knowledge, this data is fabricated, and 21 | -- it does not correspond to real people. 22 | -- Any similarity to existing people is purely coincidental. 23 | -- 24 | 25 | CREATE TABLE titles ( 26 | emp_no INT NOT NULL, 27 | title VARCHAR(50) NOT NULL, 28 | from_date DATE NOT NULL, 29 | to_date DATE, 30 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 31 | PRIMARY KEY (emp_no,title, from_date) 32 | ); 33 | -------------------------------------------------------------------------------- /examples/two-databases/README.md: -------------------------------------------------------------------------------- 1 | Example: multiple database configurations 2 | ========================================= 3 | 4 | **Level:** Advanced 5 | 6 | In this sample configuration there are two separate databases, web and employees, being used by the application. 7 | 8 | - each database has its own versions path configured in composer.json 9 | - the custom config file loads database config from the `$_SERVER` variable. 10 | 11 | In this example the buildtime and runtime connections are identical. 12 | 13 | ## The details 14 | 15 | The variable `$_SERVER["APP_ENV"]` needs to be one of *development*, *qa*, *staging* and *production* as you can 16 | see from looking at the *environments* section of `db/config.php`. 17 | 18 | The following `$_SERVER` variables also need to be defined: 19 | 20 | - DB_WEB_HOST 21 | - DB_WEB_USERNAME 22 | - DB_WEB_PASSWORD 23 | - DB_WEB_NAME 24 | - DB_WEB_PORT 25 | - DB_EMPLOYEES_HOST 26 | - DB_EMPLOYEES_USERNAME 27 | - DB_EMPLOYEES_PASSWORD 28 | - DB_EMPLOYEES_NAME 29 | - DB_EMPLOYEES_PORT 30 | 31 | ## Usage 32 | 33 | Just run the smyver commands referencing the database you wish to operate on, e.g.: 34 | 35 | $ vendor/bin/smyver.php status web-development 36 | $ vendor/bin/smyver.php status employees-development 37 | $ vendor/bin/smyver.php up web-development 38 | $ vendor/bin/smyver.php up employees-development 39 | 40 | ## Notes 41 | 42 | To use this set up you must run the smyver commands for each database environment from the correct app environment. 43 | In other words, you cannot update the production db from the development environment. 44 | You may consider this an inconvenience or a security enhancement. -------------------------------------------------------------------------------- /examples/two-databases/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "smrtr/mysql-version-control": "~1.3" 4 | }, 5 | "extra": { 6 | "mysql-version-control": { 7 | "cli": { 8 | "--config-adapter": "PhpFile", 9 | "--config-adapter-param": ["db/config.php"] 10 | }, 11 | "env": { 12 | "web-development, web-qa, web-staging, web-production": { 13 | "--versions-path": "db/web" 14 | }, 15 | "employees-development, employees-qa, employees-staging, employees-production": { 16 | "--versions-path": "db/employees" 17 | } 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /examples/two-databases/db/config.php: -------------------------------------------------------------------------------- 1 | $_SERVER["DB_EMPLOYEES_HOST"], 10 | 'user' => $_SERVER["DB_EMPLOYEES_USERNAME"], 11 | 'password' => $_SERVER["DB_EMPLOYEES_PASSWORD"], 12 | 'database' => $_SERVER["DB_EMPLOYEES_NAME"], 13 | 'port' => $_SERVER["DB_EMPLOYEES_PORT"], 14 | ]; 15 | 16 | $dbConfWeb = [ 17 | 'host' => $_SERVER["DB_WEB_HOST"], 18 | 'user' => $_SERVER["DB_WEB_USERNAME"], 19 | 'password' => $_SERVER["DB_WEB_PASSWORD"], 20 | 'database' => $_SERVER["DB_WEB_NAME"], 21 | 'port' => $_SERVER["DB_WEB_PORT"], 22 | ]; 23 | 24 | return [ 25 | 'environments' => [ 26 | 'environments' => [ 27 | 'employees-development', 'employees-qa', 'employees-staging', 'employees-production', 28 | 'web-development', 'web-qa', 'web-staging', 'web-production', 29 | ], 30 | 'testing_environments' => [ 31 | 'employees-development', 'employees-qa', 32 | 'web-development', 'web-qa', 33 | ], 34 | ], 35 | "employees-".$_SERVER["APP_ENV"] => [ 36 | 'buildtime' => $dbConfEmployees, 37 | 'runtime' => $dbConfEmployees, 38 | ], 39 | "web-".$_SERVER["APP_ENV"] => [ 40 | 'buildtime' => $dbConfWeb, 41 | 'runtime' => $dbConfWeb, 42 | ] 43 | ]; 44 | -------------------------------------------------------------------------------- /examples/two-databases/db/employees/1/data.sql: -------------------------------------------------------------------------------- 1 | # noinspection SqlNoDataSourceInspectionForFile 2 | INSERT INTO `departments` VALUES 3 | ('d001','Marketing'), 4 | ('d002','Finance'), 5 | ('d003','Human Resources'), 6 | ('d004','Production'), 7 | ('d005','Development'), 8 | ('d006','Quality Management'), 9 | ('d007','Sales'), 10 | ('d008','Research'), 11 | ('d009','Customer Service'); 12 | 13 | INSERT INTO `employees` VALUES 14 | (10001,'1953-09-02','Georgi','Facello','M','1986-06-26'), 15 | (10002,'1964-06-02','Bezalel','Simmel','F','1985-11-21'), 16 | (10003,'1959-12-03','Parto','Bamford','M','1986-08-28'), 17 | (10004,'1954-05-01','Chirstian','Koblick','M','1986-12-01'), 18 | (10005,'1955-01-21','Kyoichi','Maliniak','M','1989-09-12'), 19 | (10006,'1953-04-20','Anneke','Preusig','F','1989-06-02'), 20 | (10007,'1957-05-23','Tzvetan','Zielinski','F','1989-02-10'), 21 | (10008,'1958-02-19','Saniya','Kalloufi','M','1994-09-15'), 22 | (10009,'1952-04-19','Sumant','Peac','F','1985-02-18'), 23 | (10010,'1963-06-01','Duangkaew','Piveteau','F','1989-08-24'); -------------------------------------------------------------------------------- /examples/two-databases/db/employees/1/schema.sql: -------------------------------------------------------------------------------- 1 | -- Sample employee database 2 | -- See changelog table for details 3 | -- Copyright (C) 2007,2008, MySQL AB 4 | -- 5 | -- Original data created by Fusheng Wang and Carlo Zaniolo 6 | -- http://www.cs.aau.dk/TimeCenter/software.htm 7 | -- http://www.cs.aau.dk/TimeCenter/Data/employeeTemporalDataSet.zip 8 | -- 9 | -- Current schema by Giuseppe Maxia 10 | -- Data conversion from XML to relational by Patrick Crews 11 | -- 12 | -- This work is licensed under the 13 | -- Creative Commons Attribution-Share Alike 3.0 Unported License. 14 | -- To view a copy of this license, visit 15 | -- http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to 16 | -- Creative Commons, 171 Second Street, Suite 300, San Francisco, 17 | -- California, 94105, USA. 18 | -- 19 | -- DISCLAIMER 20 | -- To the best of our knowledge, this data is fabricated, and 21 | -- it does not correspond to real people. 22 | -- Any similarity to existing people is purely coincidental. 23 | -- 24 | 25 | CREATE TABLE employees ( 26 | emp_no INT NOT NULL, 27 | birth_date DATE NOT NULL, 28 | first_name VARCHAR(14) NOT NULL, 29 | last_name VARCHAR(16) NOT NULL, 30 | gender ENUM ('M','F') NOT NULL, 31 | hire_date DATE NOT NULL, 32 | PRIMARY KEY (emp_no) 33 | ); 34 | 35 | CREATE TABLE departments ( 36 | dept_no CHAR(4) NOT NULL, 37 | dept_name VARCHAR(40) NOT NULL, 38 | PRIMARY KEY (dept_no), 39 | UNIQUE KEY (dept_name) 40 | ); 41 | -------------------------------------------------------------------------------- /examples/two-databases/db/employees/1/testing.sql: -------------------------------------------------------------------------------- 1 | # noinspection SqlNoDataSourceInspectionForFile 2 | INSERT INTO `departments` VALUES 3 | ('d0010','Test Department'); 4 | 5 | INSERT INTO `employees` VALUES 6 | (10011,'1953-09-02','Testing','Male','M','1986-06-26'); -------------------------------------------------------------------------------- /examples/two-databases/db/employees/2/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `dept_emp` VALUES 2 | (10001,'d005','1986-06-26','9999-01-01'), 3 | (10002,'d007','1996-08-03','9999-01-01'), 4 | (10003,'d004','1995-12-03','9999-01-01'), 5 | (10004,'d004','1986-12-01','9999-01-01'), 6 | (10005,'d003','1989-09-12','9999-01-01'), 7 | (10006,'d005','1990-08-05','9999-01-01'), 8 | (10007,'d008','1989-02-10','9999-01-01'), 9 | (10008,'d005','1998-03-11','2000-07-31'), 10 | (10009,'d006','1985-02-18','9999-01-01'), 11 | (10010,'d004','1996-11-24','2000-06-26'), 12 | (10010,'d006','2000-06-26','9999-01-01'); 13 | 14 | -------------------------------------------------------------------------------- /examples/two-databases/db/employees/2/schema.sql: -------------------------------------------------------------------------------- 1 | -- Sample employee database 2 | -- See changelog table for details 3 | -- Copyright (C) 2007,2008, MySQL AB 4 | -- 5 | -- Original data created by Fusheng Wang and Carlo Zaniolo 6 | -- http://www.cs.aau.dk/TimeCenter/software.htm 7 | -- http://www.cs.aau.dk/TimeCenter/Data/employeeTemporalDataSet.zip 8 | -- 9 | -- Current schema by Giuseppe Maxia 10 | -- Data conversion from XML to relational by Patrick Crews 11 | -- 12 | -- This work is licensed under the 13 | -- Creative Commons Attribution-Share Alike 3.0 Unported License. 14 | -- To view a copy of this license, visit 15 | -- http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to 16 | -- Creative Commons, 171 Second Street, Suite 300, San Francisco, 17 | -- California, 94105, USA. 18 | -- 19 | -- DISCLAIMER 20 | -- To the best of our knowledge, this data is fabricated, and 21 | -- it does not correspond to real people. 22 | -- Any similarity to existing people is purely coincidental. 23 | -- 24 | 25 | CREATE TABLE dept_emp ( 26 | emp_no INT NOT NULL, 27 | dept_no CHAR(4) NOT NULL, 28 | from_date DATE NOT NULL, 29 | to_date DATE NOT NULL, 30 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 31 | FOREIGN KEY (dept_no) REFERENCES departments (dept_no) ON DELETE CASCADE, 32 | PRIMARY KEY (emp_no,dept_no) 33 | ); 34 | -------------------------------------------------------------------------------- /examples/two-databases/db/employees/3/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `titles` VALUES (10001,'Senior Engineer','1986-06-26','9999-01-01'), 2 | (10002,'Staff','1996-08-03','9999-01-01'), 3 | (10003,'Senior Engineer','1995-12-03','9999-01-01'), 4 | (10004,'Engineer','1986-12-01','1995-12-01'), 5 | (10004,'Senior Engineer','1995-12-01','9999-01-01'), 6 | (10005,'Senior Staff','1996-09-12','9999-01-01'), 7 | (10005,'Staff','1989-09-12','1996-09-12'), 8 | (10006,'Senior Engineer','1990-08-05','9999-01-01'), 9 | (10007,'Senior Staff','1996-02-11','9999-01-01'), 10 | (10007,'Staff','1989-02-10','1996-02-11'), 11 | (10008,'Assistant Engineer','1998-03-11','2000-07-31'), 12 | (10009,'Assistant Engineer','1985-02-18','1990-02-18'), 13 | (10009,'Engineer','1990-02-18','1995-02-18'), 14 | (10009,'Senior Engineer','1995-02-18','9999-01-01'), 15 | (10010,'Engineer','1996-11-24','9999-01-01'); -------------------------------------------------------------------------------- /examples/two-databases/db/employees/3/schema.sql: -------------------------------------------------------------------------------- 1 | -- Sample employee database 2 | -- See changelog table for details 3 | -- Copyright (C) 2007,2008, MySQL AB 4 | -- 5 | -- Original data created by Fusheng Wang and Carlo Zaniolo 6 | -- http://www.cs.aau.dk/TimeCenter/software.htm 7 | -- http://www.cs.aau.dk/TimeCenter/Data/employeeTemporalDataSet.zip 8 | -- 9 | -- Current schema by Giuseppe Maxia 10 | -- Data conversion from XML to relational by Patrick Crews 11 | -- 12 | -- This work is licensed under the 13 | -- Creative Commons Attribution-Share Alike 3.0 Unported License. 14 | -- To view a copy of this license, visit 15 | -- http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to 16 | -- Creative Commons, 171 Second Street, Suite 300, San Francisco, 17 | -- California, 94105, USA. 18 | -- 19 | -- DISCLAIMER 20 | -- To the best of our knowledge, this data is fabricated, and 21 | -- it does not correspond to real people. 22 | -- Any similarity to existing people is purely coincidental. 23 | -- 24 | 25 | CREATE TABLE titles ( 26 | emp_no INT NOT NULL, 27 | title VARCHAR(50) NOT NULL, 28 | from_date DATE NOT NULL, 29 | to_date DATE, 30 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 31 | PRIMARY KEY (emp_no,title, from_date) 32 | ); 33 | -------------------------------------------------------------------------------- /examples/two-databases/db/web/1/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `sections` (`id`, `slug`, `title`, `created_at`, `updated_at`) 2 | VALUES 3 | (1, 'news', 'News', now(), now()), 4 | (2, 'tasks', 'Tasks', now(), now()), 5 | (3, 'questions', 'Questions', now(), now()); 6 | 7 | INSERT INTO `articles` (`id`, `slug`, `title`, `body`, `section_id`, `created_at`, `updated_at`) 8 | VALUES 9 | (1, 'fizzbuzz-announces-new-website', 'FizzBuzz announces new website', '

FizzBuzz today announced plans to begin development of a new website.

\n

The new website will organize articles into sections.

\n

The development plan has been described by onlookers as ambitious and challenging.

', 1, now(), now()); 10 | -------------------------------------------------------------------------------- /examples/two-databases/db/web/1/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `articles` ( 2 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 3 | `slug` varchar(255) NOT NULL DEFAULT '', 4 | `title` varchar(255) NOT NULL DEFAULT '', 5 | `body` text NOT NULL, 6 | `section_id` int(11) unsigned NOT NULL, 7 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 8 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 9 | PRIMARY KEY (`id`) 10 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 11 | 12 | CREATE TABLE `sections` ( 13 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 14 | `slug` varchar(255) NOT NULL DEFAULT '', 15 | `title` varchar(255) NOT NULL DEFAULT '', 16 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 17 | `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 18 | PRIMARY KEY (`id`) 19 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 20 | -------------------------------------------------------------------------------- /examples/two-databases/db/web/2/testing.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `sections` (`slug`, `title`, `created_at`, `updated_at`) 2 | VALUES 3 | ('test', 'Testing', now(), now()); 4 | -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/Command/Parameters/CommonParametersTrait.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | trait CommonParametersTrait 17 | { 18 | /** 19 | * @return $this 20 | */ 21 | protected function addGlobalOptions() 22 | { 23 | return $this 24 | ->addConfigAdapterOption() 25 | ->addConfigAdapterParametersOption() 26 | ->addVersionsPathOption() 27 | ->addProvisionalVersionOption() 28 | ; 29 | } 30 | 31 | /** 32 | * @return $this 33 | */ 34 | protected function addEnvironmentArgument() 35 | { 36 | return $this->addArgument( 37 | 'env', 38 | InputArgument::REQUIRED, 39 | "The name of the enviornment to perform database operations on" 40 | ); 41 | } 42 | 43 | /** 44 | * @return $this 45 | */ 46 | protected function addMysqlBinArgument() 47 | { 48 | return $this->addArgument( 49 | 'mysql-bin', 50 | InputArgument::OPTIONAL, 51 | 'Where is the MySQL binary located?', 52 | 'mysql' 53 | ); 54 | } 55 | 56 | /** 57 | * @return $this 58 | */ 59 | protected function addConfigAdapterOption() 60 | { 61 | return $this->addOption( 62 | 'config-adapter', 63 | null, 64 | InputOption::VALUE_REQUIRED, 65 | 'Specify a database configuration adapter to use. Give the unqualified class name of a shipped adapter, '. 66 | 'or the fully qualified class name of your own custom adapter.' 67 | ); 68 | } 69 | 70 | /** 71 | * @return $this 72 | */ 73 | protected function addConfigAdapterParametersOption() 74 | { 75 | return $this->addOption( 76 | 'config-adapter-param', 77 | null, 78 | InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY , 79 | 'An array of parameters to pass to the constructor function of the database configuration adapter', 80 | [] 81 | ); 82 | } 83 | 84 | /** 85 | * @return $this 86 | */ 87 | protected function addVersionsPathOption() 88 | { 89 | return $this->addOption( 90 | 'versions-path', 91 | 'p', 92 | InputOption::VALUE_REQUIRED, 93 | 'Optional custom path to database versions directory' 94 | ); 95 | } 96 | 97 | /** 98 | * @return $this 99 | */ 100 | protected function addProvisionalVersionOption() 101 | { 102 | return $this->addOption( 103 | 'provisional-version', 104 | null, 105 | InputOption::VALUE_REQUIRED, 106 | 'The name of the provisional version' 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/Command/Parameters/ComposerParams.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | class ComposerParams 33 | { 34 | /** 35 | * Applies parameters that are defined in the extra section of composer.json. 36 | * 37 | * If the Input object already has a value for the argument or parameter then the value in composer is ignored. 38 | * 39 | * @param Command $command 40 | * @param InputInterface $input 41 | * 42 | * @return $this 43 | * @throws MysqlVersionControlException 44 | */ 45 | public function applyComposerParams(Command $command, InputInterface $input) 46 | { 47 | $params = $this->getComposerParams($input->getArgument("env")); 48 | $definition = $command->getDefinition(); 49 | 50 | foreach ($this->filterComposerParams($params, $definition) as $param => $value) { 51 | 52 | if (0 === strpos($param, "--")) { // option 53 | 54 | $option = substr($param, 2); 55 | $Option = $definition->getOption($option); 56 | 57 | if (!$Option->acceptValue() && false === $input->getOption($option)) { 58 | $input->setOption($option, null); 59 | } elseif ($Option->acceptValue() && $Option->getDefault() === $input->getOption($option)) { 60 | if ($Option->isArray()) { 61 | $input->setOption($option, is_array($value) ? $value : [$value]); 62 | } elseif (is_array($value)) { 63 | throw new MysqlVersionControlException( 64 | "The '$option' option does not accept arrays. Check your composer.json" 65 | ); 66 | } else { 67 | $input->setOption($option, $value); 68 | } 69 | } 70 | 71 | } else { // argument 72 | $argument = $definition->getArgument($param); 73 | if ($argument->getDefault() === $input->getArgument($param)) { 74 | $input->setArgument($param, $value); 75 | } 76 | } 77 | } 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * @param string $env 84 | * @param string|null $composerJsonFilePath 85 | * 86 | * @return array 87 | */ 88 | protected function getComposerParams($env, $composerJsonFilePath = null) 89 | { 90 | if (null === $composerJsonFilePath) { 91 | $composerJsonFilePath = realpath(__DIR__.'/../../../../../../../../composer.json'); 92 | } 93 | 94 | if (!is_file($composerJsonFilePath) or !is_readable($composerJsonFilePath)) { 95 | return []; 96 | } 97 | 98 | $parsedJson = json_decode(file_get_contents($composerJsonFilePath), true); 99 | 100 | if ( 101 | !isset($parsedJson["extra"]["mysql-version-control"]) or 102 | !is_array($parsedJson["extra"]["mysql-version-control"]) 103 | ) { 104 | return []; 105 | } 106 | 107 | $smyverJsonParsed = $parsedJson["extra"]["mysql-version-control"]; 108 | $cliParams = $this->getCliComposerParams($smyverJsonParsed); 109 | $envParams = $this->getEnvironmentComposerParams($env, $smyverJsonParsed); 110 | 111 | return array_merge($cliParams, $envParams); 112 | } 113 | 114 | /** 115 | * @param array $smyverJsonParsed 116 | * 117 | * @return array 118 | */ 119 | protected function getCliComposerParams(array $smyverJsonParsed) 120 | { 121 | if (!isset($smyverJsonParsed["cli"]) or !is_array($smyverJsonParsed["cli"])) { 122 | return []; 123 | } 124 | 125 | return $smyverJsonParsed["cli"]; 126 | } 127 | 128 | /** 129 | * @param string $env 130 | * @param array $smyverJsonParsed 131 | * 132 | * @return array 133 | */ 134 | protected function getEnvironmentComposerParams($env, array $smyverJsonParsed) 135 | { 136 | if (!isset($smyverJsonParsed["env"]) or !is_array($smyverJsonParsed["env"])) { 137 | return []; 138 | } 139 | 140 | $return = []; 141 | foreach ($smyverJsonParsed["env"] as $environment => $params) { 142 | $environments = preg_split("/,[\\s]*/", $environment); 143 | if (in_array($env, $environments)) { 144 | $return = array_merge($return, $params); 145 | } 146 | } 147 | 148 | return $return; 149 | } 150 | 151 | /** 152 | * @param array $params 153 | * @param InputDefinition $definition 154 | * 155 | * @return array 156 | */ 157 | protected function filterComposerParams(array $params, InputDefinition $definition) 158 | { 159 | foreach ($params as $param => $value) { 160 | if (0 === strpos($param, "--")) { 161 | if (!$definition->hasOption(substr($param, 2))) { 162 | unset($params[$param]); 163 | } 164 | } else { 165 | if (!$definition->hasArgument($param)) { 166 | unset($params[$param]); 167 | } 168 | } 169 | } 170 | return $params; 171 | } 172 | } -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/Command/Status.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Status extends Command 20 | { 21 | use CommonParametersTrait; 22 | 23 | /** 24 | * @inheritDoc 25 | */ 26 | protected function configure() 27 | { 28 | // Parameters 29 | $this 30 | ->addEnvironmentArgument() 31 | ->addGlobalOptions() 32 | ; 33 | 34 | // Name & description 35 | $this 36 | ->setName("status") 37 | ->setDescription('Reports the status of the database for the given environment') 38 | ; 39 | } 40 | 41 | /** 42 | * Pass parameters to the receiver and execute. 43 | * 44 | * @param InputInterface $input 45 | * @param OutputInterface $output 46 | * 47 | * @return int|null|void 48 | */ 49 | protected function execute(InputInterface $input, OutputInterface $output) 50 | { 51 | $composerParams = new ComposerParams; 52 | $composerParams->applyComposerParams($this, $input); 53 | Configuration::applyConsoleConfigurationOptions($input); 54 | 55 | $receiver = new StatusReceiver; 56 | return $receiver->execute( 57 | $input, 58 | $output, 59 | $input->getArgument('env'), 60 | $input->getOption('versions-path'), 61 | $input->getOption('provisional-version') 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/Command/Teardown.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class Teardown extends Command 21 | { 22 | use CommonParametersTrait; 23 | 24 | /** 25 | * @inheritDoc 26 | */ 27 | protected function configure() 28 | { 29 | // Parameters 30 | $this 31 | ->addEnvironmentArgument() 32 | ->addGlobalOptions() 33 | ->addOption( 34 | 'confirm', 35 | null, 36 | InputOption::VALUE_NONE, 37 | 'If set, the command will bypass the confirmation prompt' 38 | ) 39 | ; 40 | 41 | // Name & description 42 | $this 43 | ->setName("teardown") 44 | ->setDescription('Tear down the database tables from the given environment') 45 | ; 46 | } 47 | 48 | /** 49 | * Pass parameters to the receiver and execute. 50 | * 51 | * @param InputInterface $input 52 | * @param OutputInterface $output 53 | * 54 | * @return int|null|void 55 | */ 56 | protected function execute(InputInterface $input, OutputInterface $output) 57 | { 58 | $composerParams = new ComposerParams; 59 | $composerParams->applyComposerParams($this, $input); 60 | Configuration::applyConsoleConfigurationOptions($input); 61 | 62 | $receiver = new TeardownReceiver; 63 | return $receiver->execute( 64 | $input, 65 | $output, 66 | $input->getArgument('env'), 67 | $input->getOption('confirm') 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/Command/Up.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class Up extends Command 21 | { 22 | use CommonParametersTrait; 23 | 24 | /** 25 | * @inheritDoc 26 | */ 27 | protected function configure() 28 | { 29 | // Parameters 30 | $this 31 | ->addEnvironmentArgument() 32 | ->addMysqlBinArgument() 33 | ->addGlobalOptions() 34 | ->addOption( 35 | 'no-schema', 36 | null, 37 | InputOption::VALUE_NONE, 38 | 'Skip execution of the schema files' 39 | ) 40 | ->addOption( 41 | 'install-provisional-version', 42 | null, 43 | InputOption::VALUE_NONE, 44 | 'Install a provisional version which may still be in development and is not final.' 45 | ) 46 | ; 47 | 48 | // Name & description 49 | $this 50 | ->setName("up") 51 | ->setDescription('Install the latest database versions on the given environment') 52 | ; 53 | } 54 | 55 | /** 56 | * Pass parameters to the receiver and execute. 57 | * 58 | * @param InputInterface $input 59 | * @param OutputInterface $output 60 | * 61 | * @return int|null|void 62 | */ 63 | protected function execute(InputInterface $input, OutputInterface $output) 64 | { 65 | $composerParams = new ComposerParams; 66 | $composerParams->applyComposerParams($this, $input); 67 | Configuration::applyConsoleConfigurationOptions($input); 68 | 69 | $receiver = new UpReceiver; 70 | return $receiver->execute( 71 | $input, 72 | $output, 73 | $input->getArgument('env'), 74 | $input->getArgument('mysql-bin'), 75 | $input->getOption('versions-path'), 76 | $input->getOption('no-schema'), 77 | $input->getOption('install-provisional-version'), 78 | $input->getOption('provisional-version') 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/DbConfig.php: -------------------------------------------------------------------------------- 1 | getEnvironments(); 59 | } 60 | 61 | /** 62 | * Get a list of testing environments. 63 | * 64 | * @return string[] A list of environment names which are testing environments 65 | */ 66 | public static function getTestingEnvironments() 67 | { 68 | return static::getAdapter()->getTestingEnvironments(); 69 | } 70 | 71 | /** 72 | * Get the database configurations for the given environment. 73 | * 74 | * Returns an array with keys 'buildtime' and 'runtime', 75 | * where both elements are themselves arrays of database config 76 | * with keys 'host', 'database', 'user', 'password', 'port'. 77 | * 78 | * @param string $env The name of the environment 79 | * 80 | * @return array 81 | */ 82 | public static function getConfig($env) 83 | { 84 | return static::getAdapter()->getConfig($env); 85 | } 86 | 87 | /** 88 | * Get a PDO connection object for the given environment. 89 | * 90 | * @param string $env 91 | * @param bool $buildtime Optional; false by default. Set to true to get buildtime config instead of runtime. 92 | * 93 | * @return \PDO 94 | */ 95 | public static function getPDO($env, $buildtime = false) 96 | { 97 | $key = $buildtime ? 'buildtime' : 'runtime'; 98 | $config = static::getConfig($env); 99 | $config = $config[$key]; 100 | 101 | $dsn = sprintf("mysql:host=%s", $config["host"]); 102 | if (isset($config["port"]) && $config["port"]) { 103 | $dsn .= sprintf(";port=%s", $config["port"]); 104 | } 105 | $dsn .= sprintf(";dbname=%s", $config["database"]); 106 | 107 | $db = new \PDO($dsn, $config['user'], $config['password']); 108 | 109 | return $db; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/DbConfigAdapter/ConfigAdapterInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface ConfigAdapterInterface 14 | { 15 | /** 16 | * Get a list of all environments. 17 | * 18 | * @return string[] A list of all environment names 19 | */ 20 | public function getEnvironments(); 21 | 22 | /** 23 | * Get a list of testing environments. 24 | * 25 | * @return string[] A list of environment names which are testing environments 26 | */ 27 | public function getTestingEnvironments(); 28 | 29 | /** 30 | * Get the database configurations for the given environment. 31 | * 32 | * Returns an array with keys 'buildtime' and 'runtime', 33 | * where both elements are themselves arrays of database config with keys 'host', 'database', 'user', 'password'. 34 | * 35 | * @param string $env The name of the environment 36 | * 37 | * @return array 38 | */ 39 | public function getConfig($env); 40 | } -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/DbConfigAdapter/Ini.php: -------------------------------------------------------------------------------- 1 | /db/db.ini. 12 | * 13 | * An example db.ini: 14 | * 15 | * ``` 16 | * [environments] 17 | * environments[] = "development" 18 | * environments[] = "production" 19 | * 20 | * testing_environments[] = "development" 21 | * 22 | * [development] 23 | * runtime.host = "localhost" 24 | * runtime.user = "buzz" 25 | * runtime.password = "lightyear" 26 | * runtime.database = "buzz" 27 | * runtime.port = 3306 28 | * 29 | * buildtime.host = "localhost" 30 | * buildtime.user = "buzz" 31 | * buildtime.password = "lightyear" 32 | * buildtime.database = "buzz" 33 | * buildtime.port = 3306 34 | * 35 | * [production] 36 | * runtime.host = "localhost" 37 | * runtime.user = "root" 38 | * runtime.password = "root" 39 | * runtime.database = "buzz" 40 | * runtime.port = 3306 41 | * 42 | * buildtime.host = "localhost" 43 | * buildtime.user = "buzz" 44 | * buildtime.password = "lightyear" 45 | * buildtime.database = "buzz" 46 | * buildtime.port = 3306 47 | * ``` 48 | * 49 | * @package Smrtr\MysqlVersionControl\DbConfigAdapter 50 | * @author Joe Green 51 | */ 52 | class Ini implements ConfigAdapterInterface 53 | { 54 | /** 55 | * @var string The template for the file path of the default config file. 56 | */ 57 | const DEFAULT_CONFIG_FILE_TPL = '%s/db/db.ini'; 58 | 59 | /** 60 | * @var string|null The actual config file to load. 61 | */ 62 | protected $configFile; 63 | 64 | /** 65 | * Ini constructor. 66 | * 67 | * @param string|null $path 68 | */ 69 | public function __construct($path = null) 70 | { 71 | if ($path) { 72 | $this->setConfigFile($path); 73 | } 74 | } 75 | 76 | /** 77 | * Set the file path of the config file to load. 78 | * 79 | * @param string $path 80 | * 81 | * @return $this 82 | */ 83 | public function setConfigFile($path) 84 | { 85 | $this->configFile = (string) $path; 86 | return $this; 87 | } 88 | 89 | /** 90 | * Get the file path of the config file. 91 | * 92 | * @return string 93 | * 94 | * @throws MysqlVersionControlException If the file path is not readable 95 | */ 96 | public function getConfigFile() 97 | { 98 | if (null === $this->configFile) { 99 | $this->configFile = sprintf(static::DEFAULT_CONFIG_FILE_TPL, $this->getProjectPath()); 100 | } 101 | if (!is_readable($this->configFile)) { 102 | throw new MysqlVersionControlException( 103 | "Cannot find or open database config; looked in '{$this->configFile}'" 104 | ); 105 | } 106 | return $this->configFile; 107 | } 108 | 109 | /** 110 | * @inheritDoc 111 | */ 112 | public function getEnvironments() 113 | { 114 | $config = new \Zend_Config_Ini($this->getConfigFile(), 'environments'); 115 | $config = $config->toArray(); 116 | return $config['environments']; 117 | } 118 | 119 | /** 120 | * @inheritDoc 121 | */ 122 | public function getTestingEnvironments() 123 | { 124 | $config = new \Zend_Config_Ini(self::getConfigFile(), 'environments'); 125 | $config = $config->toArray(); 126 | return $config['testing_environments']; 127 | } 128 | 129 | /** 130 | * @inheritDoc 131 | */ 132 | public function getConfig($env) 133 | { 134 | $config = new \Zend_Config_Ini(self::getConfigFile(), $env); 135 | return $config->toArray(); 136 | } 137 | 138 | /** 139 | * @return string The project root path. 140 | */ 141 | protected function getProjectPath() 142 | { 143 | $parts = explode('vendor', __FILE__); 144 | array_pop($parts); 145 | return rtrim(implode('vendor', $parts), '/\\'); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/DbConfigAdapter/PhpArray.php: -------------------------------------------------------------------------------- 1 | [ 15 | * 'environments' => ['A', 'B'], 16 | * 'testing_environments' => ['B'], 17 | * ], 18 | * 'A' => [ 19 | * 'buildtime' => [ 20 | * 'host' => '', 21 | * 'database' => '', 22 | * 'user' => '', 23 | * 'password' => '', 24 | * 'port' => '', 25 | * ], 26 | * 'runtime' => [ 27 | * 'host' => '', 28 | * 'database' => '', 29 | * 'user' => '', 30 | * 'password' => '', 31 | * 'port' => '', 32 | * ], 33 | * ], 34 | * 'B' => [ 35 | * 'buildtime' => [ 36 | * 'host' => '', 37 | * 'database' => '', 38 | * 'user' => '', 39 | * 'password' => '', 40 | * 'port' => '', 41 | * ], 42 | * 'runtime' => [ 43 | * 'host' => '', 44 | * 'database' => '', 45 | * 'user' => '', 46 | * 'password' => '', 47 | * 'port' => '', 48 | * ], 49 | * ], 50 | * ] 51 | * ``` 52 | * where A, B are the names of the available environments. 53 | * 54 | * Pass the array in via the constructor or after construction using the method setArray(). 55 | * 56 | * @package Smrtr\MysqlVersionControl\DbConfigAdapter 57 | * @author Joe Green 58 | */ 59 | class PhpArray implements ConfigAdapterInterface 60 | { 61 | /** 62 | * @var array|null This array holds the entire database configuration data 63 | */ 64 | protected $array; 65 | 66 | /** 67 | * PhpArray constructor. 68 | * 69 | * @param array|null $array Optional array of config to inject right away. 70 | */ 71 | public function __construct(array $array = null) 72 | { 73 | if ($array) { 74 | $this->setArray($array); 75 | } 76 | } 77 | 78 | /** 79 | * Inject the entire configuration array into the adapter. 80 | * 81 | * @param array $array 82 | * 83 | * @return $this 84 | */ 85 | public function setArray(array $array) 86 | { 87 | $this->array = $array; 88 | return $this; 89 | } 90 | 91 | /** 92 | * Get the full array of database configurations. 93 | * 94 | * @return array 95 | * 96 | * @throws MysqlVersionControlException If no configuration is loaded. 97 | */ 98 | public function getArray() 99 | { 100 | if (!is_array($this->array)) { 101 | throw new MysqlVersionControlException("No configuration is loaded into the PhpArray adapter"); 102 | } 103 | return $this->array; 104 | } 105 | 106 | /** 107 | * @inheritDoc 108 | * 109 | * @throws MysqlVersionControlException If the required environments keys are not present in the array 110 | */ 111 | public function getEnvironments() 112 | { 113 | $conf = $this->getArray(); 114 | if (!isset($conf['environments']) or !is_array($conf['environments'])) { 115 | throw new MysqlVersionControlException( 116 | "The PhpArray adapter configuration is malformed; key 'environments' not found or not an array" 117 | ); 118 | } 119 | $conf = $conf['environments']; 120 | if (!isset($conf['environments']) or !is_array($conf['environments'])) { 121 | throw new MysqlVersionControlException( 122 | "The PhpArray adapter configuration is malformed; key 'environments['environments']' not found or not an array" 123 | ); 124 | } 125 | return $conf['environments']; 126 | } 127 | 128 | /** 129 | * @inheritDoc 130 | * 131 | * @throws MysqlVersionControlException If the required environments keys are not present in the array 132 | */ 133 | public function getTestingEnvironments() 134 | { 135 | $conf = $this->getArray(); 136 | if (!isset($conf['environments']) or !is_array($conf['environments'])) { 137 | throw new MysqlVersionControlException( 138 | "The PhpArray adapter configuration is malformed; key 'environments' not found or not an array" 139 | ); 140 | } 141 | $conf = $conf['environments']; 142 | if (!isset($conf['testing_environments']) or !is_array($conf['testing_environments'])) { 143 | throw new MysqlVersionControlException( 144 | "The PhpArray adapter configuration is malformed; key 'environments['testing_environments']' not found or not an array" 145 | ); 146 | } 147 | return $conf['testing_environments']; 148 | } 149 | 150 | /** 151 | * @inheritDoc 152 | * 153 | * @throws MysqlVersionControlException If the required environment key is not present in the array 154 | */ 155 | public function getConfig($env) 156 | { 157 | $conf = $this->getArray(); 158 | if (!isset($conf[$env]) or !is_array($conf[$env])) { 159 | throw new MysqlVersionControlException( 160 | "No configuration was found in the PhpArray adapter for environment '$env'" 161 | ); 162 | } 163 | return $conf[$env]; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/DbConfigAdapter/PhpFile.php: -------------------------------------------------------------------------------- 1 | [ 14 | * 'environments' => ['A', 'B'], 15 | * 'testing_environments' => ['B'], 16 | * ], 17 | * 'A' => [ 18 | * 'buildtime' => [ 19 | * 'host' => '', 20 | * 'database' => '', 21 | * 'user' => '', 22 | * 'password' => '', 23 | * 'port' => '', 24 | * ], 25 | * 'runtime' => [ 26 | * 'host' => '', 27 | * 'database' => '', 28 | * 'user' => '', 29 | * 'password' => '', 30 | * 'port' => '', 31 | * ], 32 | * ], 33 | * 'B' => [ 34 | * 'buildtime' => [ 35 | * 'host' => '', 36 | * 'database' => '', 37 | * 'user' => '', 38 | * 'password' => '', 39 | * 'port' => '', 40 | * ], 41 | * 'runtime' => [ 42 | * 'host' => '', 43 | * 'database' => '', 44 | * 'user' => '', 45 | * 'password' => '', 46 | * 'port' => '', 47 | * ], 48 | * ], 49 | * ] 50 | * ``` 51 | * where A, B are the names of the available environments. 52 | * 53 | * Pass the file path in via the constructor or after construction using the method setFilePath(). 54 | * 55 | * @package Smrtr\MysqlVersionControl\DbConfigAdapter 56 | * @author Joe Green 57 | */ 58 | class PhpFile implements ConfigAdapterInterface 59 | { 60 | /** 61 | * @var array|null This array holds the returned from file database configuration data 62 | */ 63 | protected $array; 64 | 65 | /** 66 | * @var string|null The path to the php file 67 | */ 68 | protected $filePath; 69 | 70 | /** 71 | * PhpFile constructor. 72 | * 73 | * @param string|null $phpFilePath Optional path to the php file 74 | */ 75 | public function __construct($phpFilePath = null) 76 | { 77 | if ($phpFilePath) { 78 | $this->setFilePath($phpFilePath); 79 | } 80 | } 81 | 82 | /** 83 | * Set the path to the php file and invalidate any previously loaded configuration. 84 | * 85 | * If the path provided is not absolute then it is assumed to be relative to the project root. 86 | * 87 | * @param string $phpFilePath The path to the php file 88 | * 89 | * @return $this 90 | */ 91 | public function setFilePath($phpFilePath) 92 | { 93 | if (!in_array(substr($phpFilePath, 0, 1), ["/", "\\"])) { // then we assume path is relative to project root 94 | $projectPath = realpath(dirname(__FILE__).'/../../../../../../..'); 95 | $phpFilePath = "$projectPath/$phpFilePath"; 96 | } 97 | $this->filePath = $phpFilePath; 98 | $this->array = null; 99 | return $this; 100 | } 101 | 102 | /** 103 | * @return null|string The configured file path to the php file that will return the database configurations. 104 | */ 105 | public function getFilePath() 106 | { 107 | return $this->filePath; 108 | } 109 | 110 | /** 111 | * Get the full array of database configurations. 112 | * 113 | * @return array 114 | * 115 | * @throws MysqlVersionControlException If no configuration is loaded and no php file is specified. 116 | */ 117 | public function getArray() 118 | { 119 | if (!is_array($this->array)) { 120 | if (!$this->loadConfig()) { 121 | throw new MysqlVersionControlException("No configuration is loaded into the PhpFile adapter"); 122 | } 123 | } 124 | return $this->array; 125 | } 126 | 127 | /** 128 | * @inheritDoc 129 | * 130 | * @throws MysqlVersionControlException If the required environments keys are not present in the array 131 | */ 132 | public function getEnvironments() 133 | { 134 | $conf = $this->getArray(); 135 | if (!isset($conf['environments']) or !is_array($conf['environments'])) { 136 | throw new MysqlVersionControlException( 137 | "The PhpFile adapter configuration is malformed; key 'environments' not found or not an array" 138 | ); 139 | } 140 | $conf = $conf['environments']; 141 | if (!isset($conf['environments']) or !is_array($conf['environments'])) { 142 | throw new MysqlVersionControlException( 143 | "The PhpFile adapter configuration is malformed; key 'environments['environments']' not found or not an array" 144 | ); 145 | } 146 | return $conf['environments']; 147 | } 148 | 149 | /** 150 | * @inheritDoc 151 | * 152 | * @throws MysqlVersionControlException If the required environments keys are not present in the array 153 | */ 154 | public function getTestingEnvironments() 155 | { 156 | $conf = $this->getArray(); 157 | if (!isset($conf['environments']) or !is_array($conf['environments'])) { 158 | throw new MysqlVersionControlException( 159 | "The PhpFile adapter configuration is malformed; key 'environments' not found or not an array" 160 | ); 161 | } 162 | $conf = $conf['environments']; 163 | if (!isset($conf['testing_environments']) or !is_array($conf['testing_environments'])) { 164 | throw new MysqlVersionControlException( 165 | "The PhpFile adapter configuration is malformed; key 'environments['testing_environments']' not found or not an array" 166 | ); 167 | } 168 | return $conf['testing_environments']; 169 | } 170 | 171 | /** 172 | * @inheritDoc 173 | * 174 | * @throws MysqlVersionControlException If the required environment key is not present in the array 175 | */ 176 | public function getConfig($env) 177 | { 178 | $conf = $this->getArray(); 179 | if (!isset($conf[$env]) or !is_array($conf[$env])) { 180 | throw new MysqlVersionControlException( 181 | "No configuration was found in the PhpFile adapter for environment '$env'" 182 | ); 183 | } 184 | return $conf[$env]; 185 | } 186 | 187 | /** 188 | * Attempt to load an array from the file and store it in $this->array. 189 | * 190 | * @return bool True if array was loaded from file, false otherwise 191 | */ 192 | protected function loadConfig() 193 | { 194 | $array = include $this->filePath; 195 | if (is_array($array)) { 196 | $this->array = $array; 197 | return true; 198 | } 199 | return false; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/Helper/Configuration.php: -------------------------------------------------------------------------------- 1 | getOption("config-adapter"); 32 | $configAdapterParams = (array) $input->getOption("config-adapter-param"); 33 | 34 | if (!count($configAdapterParams) && !strlen($configAdapter)) { 35 | return; 36 | } 37 | 38 | if (!$configAdapter) { 39 | $configAdapter = static::DEFAULT_CONFIG_ADAPTER; 40 | } 41 | 42 | if (false === strpos($configAdapter, "\\")) { // Not a fully qualified class name 43 | $configAdapter = static::SMRTR_ADAPTER_NAMESPACE."\\$configAdapter"; 44 | } 45 | 46 | if (!class_exists($configAdapter)) { 47 | throw new MysqlVersionControlException("Unknown class '$configAdapter'"); 48 | } 49 | 50 | $adapter = (new \ReflectionClass($configAdapter))->newInstanceArgs($configAdapterParams); 51 | DbConfig::setAdapter($adapter); 52 | } 53 | } -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/Helper/VersionPaths.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class VersionPaths 16 | { 17 | /** 18 | * @var string The template for the default versions path 19 | */ 20 | const DEFAULT_VERSIONS_PATH_TPL = "%s/db/versions"; 21 | 22 | /** 23 | * @var string The template for the default provisional version path 24 | */ 25 | const DEFAULT_PROVISIONAL_VERSION_PATH_TPL = "%s/new"; 26 | 27 | /** 28 | * This method takes a versions path that could be relative or absolute and returns an absolute path. 29 | * 30 | * It is safe to run a versions path through this method multiple times, it will return the same path each time. 31 | * 32 | * @param string|null $versionsPath 33 | * 34 | * @return string Always returns an absolute path 35 | * 36 | * @throws MysqlVersionControlException 37 | */ 38 | public static function resolveVersionsPath($versionsPath) 39 | { 40 | // what is the versions path? 41 | $projectPath = realpath(dirname(__FILE__).'/../../../../../../..'); 42 | if (!$versionsPath) { 43 | $versionsPath = sprintf(static::DEFAULT_VERSIONS_PATH_TPL, $projectPath); 44 | } 45 | if (!in_array(substr($versionsPath, 0, 1), ["/", "\\"])) { // then we assume path is relative to project root 46 | $versionsPath = "$projectPath/$versionsPath"; 47 | } 48 | if (!is_readable($versionsPath)) { 49 | throw new MysqlVersionControlException("Versions path is not readable: '$versionsPath'"); 50 | } 51 | if (!is_dir($versionsPath)) { 52 | throw new MysqlVersionControlException("Versions path is not a directory: '$versionsPath'"); 53 | } 54 | return $versionsPath; 55 | } 56 | 57 | /** 58 | * This method takes a version path and a provisional version name and returns a full provisional version path. 59 | * 60 | * @param string|null $versionsPath 61 | * @param string|null $provisionalVersionPath 62 | * 63 | * @return string Always returns an absolute path 64 | */ 65 | public static function resolveProvisionalVersionPath($versionsPath, $provisionalVersionPath) 66 | { 67 | if (!$provisionalVersionPath) { 68 | return sprintf(static::DEFAULT_PROVISIONAL_VERSION_PATH_TPL, static::resolveVersionsPath($versionsPath)); 69 | } 70 | return static::resolveVersionsPath($versionsPath)."/".ltrim($provisionalVersionPath, "/\\"); 71 | } 72 | 73 | /** 74 | * Return a list of files to look for based on the given parameters. 75 | * 76 | * @param bool $includeSchema 77 | * @param bool $includeTesting 78 | * 79 | * @return array 80 | */ 81 | public static function getVersioningFilesToLookFor($includeSchema = true, $includeTesting = true) 82 | { 83 | $filesToLookFor = []; 84 | if ($includeSchema) { 85 | $filesToLookFor[] = 'schema.sql'; // structural changes, alters, creates, drops 86 | } 87 | $filesToLookFor[] = 'data.sql'; // core data, inserts, replaces, updates, deletes 88 | if ($includeTesting) { 89 | $filesToLookFor[] = 'testing.sql'; // extra data on top of data.sql for the testing environment(s) 90 | } 91 | $filesToLookFor[] = 'runme.php'; // custom php hook 92 | return $filesToLookFor; 93 | } 94 | } -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/Receiver/Status.php: -------------------------------------------------------------------------------- 1 | hasVersionControl($env)) { 40 | $output->writeln("The database is not currently under version control"); 41 | } 42 | 43 | $output->writeln("Current version: ".$this->getCurrentVersion($env)); 44 | 45 | $output->writeln("Available version: ".$this->getAvailableVersion($versionsPath)); 46 | 47 | if ($this->hasProvisionalVersion($versionsPath, $provisionalVersion)) { 48 | $output->writeln("A provisional version is also available"); 49 | } 50 | 51 | return 0; 52 | } 53 | 54 | /** 55 | * @param string $env 56 | * 57 | * @return bool 58 | */ 59 | public function hasVersionControl($env) 60 | { 61 | return (boolean) $this->getBuildtimeConnection($env)->query("SHOW TABLES LIKE 'db_config'")->rowCount(); 62 | } 63 | 64 | /** 65 | * @param string $env 66 | * 67 | * @return int 68 | */ 69 | public function getCurrentVersion($env) 70 | { 71 | $query = $this->getRuntimeConnection($env)->query("SELECT `value` FROM `db_config` WHERE `key`='version'"); 72 | if ($query instanceof \PDOStatement && $query->rowCount()) { 73 | $versionRow = $query->fetch(\PDO::FETCH_ASSOC); 74 | $currentVersion = (int) $versionRow['value']; 75 | } else { 76 | $currentVersion = 0; 77 | } 78 | return $currentVersion; 79 | } 80 | 81 | /** 82 | * @param string $versionsPath 83 | * 84 | * @return int 85 | */ 86 | public function getAvailableVersion($versionsPath) 87 | { 88 | $availableVersion = 0; 89 | foreach (scandir(VersionPaths::resolveVersionsPath($versionsPath)) as $path) { 90 | if (preg_match("/^(\\d)+$/", $path) && (int) $path > $availableVersion) { 91 | $availableVersion = (int) $path; 92 | } 93 | } 94 | return $availableVersion; 95 | } 96 | 97 | /** 98 | * Returns true iff there is a provisional version directory with at least one recognized file inside it. 99 | * 100 | * @param string|null $versionsPath 101 | * @param string|null $provisionalVersion 102 | * 103 | * @return bool 104 | */ 105 | public function hasProvisionalVersion($versionsPath, $provisionalVersion) 106 | { 107 | $filesToLookFor = VersionPaths::getVersioningFilesToLookFor(true, true); 108 | $path = VersionPaths::resolveProvisionalVersionPath($versionsPath, $provisionalVersion); 109 | if (is_readable($path) && is_dir($path)) { 110 | foreach ($filesToLookFor as $file) { 111 | if (is_readable($path.DIRECTORY_SEPARATOR.$file) && is_file($path.DIRECTORY_SEPARATOR.$file)) { 112 | return true; 113 | } 114 | } 115 | } 116 | return false; 117 | } 118 | 119 | /** 120 | * @param string $env 121 | * 122 | * @return \PDO 123 | * @throws MysqlVersionControlException 124 | */ 125 | protected function getBuildtimeConnection($env) 126 | { 127 | if (!array_key_exists($env, $this->buildtimeConnections)) { 128 | $this->buildtimeConnections[$env] = DbConfig::getPDO($env, true); 129 | if (! $this->buildtimeConnections[$env] instanceof \PDO) { 130 | throw new MysqlVersionControlException("Unable to obtain a database connection"); 131 | } 132 | } 133 | return $this->buildtimeConnections[$env]; 134 | } 135 | 136 | /** 137 | * @param string $env 138 | * 139 | * @return \PDO 140 | * @throws MysqlVersionControlException 141 | */ 142 | protected function getRuntimeConnection($env) 143 | { 144 | if (!array_key_exists($env, $this->runtimeConnections)) { 145 | $this->runtimeConnections[$env] = DbConfig::getPDO($env, false); 146 | if (! $this->runtimeConnections[$env] instanceof \PDO) { 147 | throw new MysqlVersionControlException("Unable to obtain a database connection"); 148 | } 149 | } 150 | return $this->runtimeConnections[$env]; 151 | } 152 | } -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/Receiver/Teardown.php: -------------------------------------------------------------------------------- 1 | query("SHOW TABLES"); 29 | $result = $stmt->fetchAll(); 30 | 31 | $tables = array(); 32 | foreach ($result as $row) { 33 | $tables[] = array_shift($row); 34 | } 35 | 36 | if (!$confirm) { 37 | $tableCount = count($tables); 38 | $questionHelper = new QuestionHelper; 39 | $question = new ConfirmationQuestion("Tear down $tableCount tables(y/n)?", false); 40 | if (!$questionHelper->ask($input, $output, $question)) { 41 | return 0; 42 | } 43 | } 44 | 45 | foreach ($tables as $table) { 46 | $con->exec("DROP TABLE IF EXISTS `$table`"); 47 | $output->writeln("Dropped table $table"); 48 | } 49 | 50 | return 0; 51 | } 52 | } -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControl/Receiver/Up.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class Up 25 | { 26 | /** 27 | * @param InputInterface $input 28 | * @param OutputInterface $output 29 | * @param string $env 30 | * @param string $mysqlBin 31 | * @param null $versionsPath 32 | * @param bool $noSchema 33 | * @param bool $installProvisionalVersion 34 | * @param string $provisionalVersion 35 | * 36 | * @return int 37 | */ 38 | public function execute( 39 | InputInterface $input, 40 | OutputInterface $output, 41 | $env, 42 | $mysqlBin = 'mysql', 43 | $versionsPath = null, 44 | $noSchema = false, 45 | $installProvisionalVersion = false, 46 | $provisionalVersion = null 47 | ) { 48 | $buildConn = DbConfig::getPDO($env, true); 49 | $runConn = DbConfig::getPDO($env); 50 | $output->writeln('Checking database status... '); 51 | 52 | if (!$buildConn instanceof \PDO) { 53 | $output->writeln('Failed: unable to obtain a database connection.'); 54 | return 1; 55 | } 56 | 57 | if ($buildConn->query("SHOW TABLES LIKE 'db_config'")->rowCount()) { 58 | 59 | $output->writeln('Database version control is already installed.'); 60 | 61 | } else { 62 | 63 | $output->writeln('Installing version control...'); 64 | 65 | $result = $buildConn->prepare( 66 | "CREATE TABLE `db_config` 67 | ( 68 | `key` VARCHAR(50) COLLATE 'utf8_general_ci' NOT NULL, 69 | `value` TEXT, 70 | `created_at` DATETIME, 71 | `updated_at` DATETIME, 72 | PRIMARY KEY (`key`), 73 | UNIQUE INDEX `db_config_U_1` (`key`) 74 | ) ENGINE=MyISAM;" 75 | )->execute(); 76 | 77 | if (!$result) { 78 | $output->writeln('Installing version control failed.'); 79 | return 1; 80 | } 81 | 82 | $output->writeln('Installed version control successfully.'); 83 | } 84 | 85 | // Check for current version and available version 86 | 87 | // what is the versions path? 88 | $versionsPath = VersionPaths::resolveVersionsPath($versionsPath); 89 | 90 | // what is the current version? 91 | $statusReceiver = new Status; 92 | $currentVersion = $statusReceiver->getCurrentVersion($env); 93 | $output->writeln('Current version: '.$currentVersion); 94 | 95 | // what is the available version? 96 | $availableVersion = $statusReceiver->getAvailableVersion($versionsPath); 97 | $output->writeln('Available version: '.$availableVersion); 98 | 99 | // Let's scan these versions and build a stack of files 100 | $filesToLookFor = VersionPaths::getVersioningFilesToLookFor( 101 | !$noSchema, 102 | in_array($env, DbConfig::getTestingEnvironments()) 103 | ); 104 | 105 | $stack = array(); 106 | if ($currentVersion < $availableVersion) { 107 | for ($i = $currentVersion + 1; $i <= $availableVersion; $i++) { 108 | 109 | $path = $versionsPath.DIRECTORY_SEPARATOR.$i; 110 | if (!is_dir($path) || !is_readable($path)) { 111 | continue; 112 | } 113 | 114 | foreach ($filesToLookFor as $file) { 115 | if (is_readable($path.DIRECTORY_SEPARATOR.$file) && is_file($path.DIRECTORY_SEPARATOR.$file)) { 116 | $stack[$i][$file] = $path.DIRECTORY_SEPARATOR.$file; 117 | } 118 | } 119 | } 120 | } 121 | 122 | // Look for a provisional version? 123 | if ($installProvisionalVersion) { 124 | $output->writeln('Looking for provisional version: '.$provisionalVersion); 125 | $path = VersionPaths::resolveProvisionalVersionPath($versionsPath, $provisionalVersion); 126 | if (is_readable($path) && is_dir($path)) { 127 | foreach ($filesToLookFor as $file) { 128 | if (is_readable($path.DIRECTORY_SEPARATOR.$file) && is_file($path.DIRECTORY_SEPARATOR.$file)) { 129 | $stack[$provisionalVersion][$file] = $path.DIRECTORY_SEPARATOR.$file; 130 | } 131 | } 132 | } 133 | } 134 | 135 | $updates = count($stack); 136 | if (!$updates) { 137 | $output->writeln('Database version is already up to date.'); 138 | return 0; 139 | } 140 | 141 | $noun = ($updates > 1) ? 'updates' : 'update'; 142 | $report = "Current version: $currentVersion, Available version: $availableVersion"; 143 | if (is_string($provisionalVersion) && array_key_exists($provisionalVersion, $stack)) { 144 | $report .= ", Provisional version: $provisionalVersion"; 145 | } 146 | $output->writeln( 147 | "Installing database $noun ($report)..." 148 | ); 149 | 150 | $s = '\\' == DIRECTORY_SEPARATOR ? "%s" : "'%s'"; // Windows doesn't like quoted params 151 | 152 | // loop sql file stack and execute on mysql CLI 153 | 154 | $dbConf = DbConfig::getConfig($env); 155 | 156 | $previousVersion = $currentVersion; 157 | $result = true; 158 | foreach ($stack as $version => $files) { 159 | 160 | $output->write($previousVersion." -> $version "); 161 | 162 | if (!$result) { 163 | $output->write('skipped'); 164 | continue; 165 | } 166 | 167 | foreach ($files as $file) { 168 | 169 | if ('schema.sql' === $file) { 170 | $conf = $dbConf['buildtime']; 171 | } else { 172 | $conf = $dbConf['runtime']; 173 | } 174 | $host = $conf['host']; 175 | $user = $conf['user']; 176 | $pass = $conf['password']; 177 | $name = $conf['database']; 178 | $port = isset($conf['port']) ? $conf['port'] : null; 179 | 180 | if ('.sql' === substr($file, -4)) { 181 | 182 | $cmdMySQL = "$mysqlBin -h $s --user=$s --password=$s --database=$s"; 183 | if ($port) { 184 | $cmdMySQL .= " --port=". (int) $port; 185 | } 186 | $cmdMySQL .= " < %s"; 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 && is_int($version)) { 215 | $result = $buildConn->query( 216 | "REPLACE INTO `db_config` (`key`, `value`, `updated_at`) VALUES ('version', $version, now())" 217 | )->execute(); 218 | } 219 | 220 | $statusMsg = $result ? 'OK' : 'Failed'; 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(''.$process->getErrorOutput().''); 230 | } 231 | 232 | $previousVersion = $version; 233 | } 234 | 235 | if ($result) { 236 | $output->writeln('Database updates installed successfully.'); 237 | return 0; 238 | 239 | } else { 240 | $output->writeln('Installing database updates failed.'); 241 | return 1; 242 | } 243 | } 244 | } -------------------------------------------------------------------------------- /src/Smrtr/MysqlVersionControlException.php: -------------------------------------------------------------------------------- 1 |