From db929a74e7e57f8424027c8fe685b37c6459db86 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Thu, 23 Sep 2021 10:07:04 +0100 Subject: [PATCH] Upgrade to CLI (#238) * build: update dependencies * build: upgrade deps * wip: extract migration into cli command * wip: rename bin script * wip: refactor execture command * feature: integrate migrator class * refactor: remove obsolete functionality * feature: improve exceptions --- bin/db-migrate | 81 -------- bin/migrate | 26 +++ composer.json | 9 +- composer.lock | 255 +++++++++++++++++------- src/Cli/ExecuteCommand.php | 129 ++++++++++++ src/Database.php | 12 +- src/Migration/Migrator.php | 25 +-- src/StatementExecutionException.php | 6 + src/StatementPreparationException.php | 6 + test/phpunit/Migration/MigratorTest.php | 8 +- 10 files changed, 372 insertions(+), 185 deletions(-) delete mode 100755 bin/db-migrate create mode 100755 bin/migrate create mode 100644 src/Cli/ExecuteCommand.php create mode 100644 src/StatementExecutionException.php create mode 100644 src/StatementPreparationException.php diff --git a/bin/db-migrate b/bin/db-migrate deleted file mode 100755 index 9f7a39f..0000000 --- a/bin/db-migrate +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env php -merge($default); -} - -$settings = new Settings( - implode(DIRECTORY_SEPARATOR, [ - $repoBasePath, - $config->get("database.query_path") - ]), - - $config->get("database.driver") ?? 'mysql', - $config->get("database.schema"), - $config->get("database.host") ?? "localhost", - $config->get("database.port") ?? "3306", - $config->get("database.username"), - $config->get("database.password") -); - -$migrationPath = implode(DIRECTORY_SEPARATOR, [ - $repoBasePath, - $config->get("database.query_path") ?? "query", - $config->get("database.migration_path") ?? "_migration", -]); -$migrationTable = $config->get("database.migration_table") ?? "_migration"; - -$migrator = new Migrator($settings, $migrationPath, $migrationTable, $forced); -$migrator->createMigrationTable(); -$migrationCount = $migrator->getMigrationCount(); -$migrationFileList = $migrator->getMigrationFileList(); -$migrator->checkIntegrity($migrationFileList, $migrationCount); -$migrator->performMigration($migrationFileList, $migrationCount); diff --git a/bin/migrate b/bin/migrate new file mode 100755 index 0000000..af5124b --- /dev/null +++ b/bin/migrate @@ -0,0 +1,26 @@ +#!/usr/bin/env php +run(); diff --git a/composer.json b/composer.json index e624bd3..d0a70f4 100644 --- a/composer.json +++ b/composer.json @@ -6,12 +6,13 @@ "require": { "php": ">=7.4", "ext-PDO": "*", - "phpgt/config": "^v1.1.0" + "phpgt/config": "^v1.1.0", + "phpgt/cli": "^1.3" }, "require-dev": { - "phpunit/phpunit": "^9.4.0", - "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^9.4", + "phpstan/phpstan": "^0.12", "ext-sqlite3": "*" }, @@ -45,7 +46,7 @@ }, "bin": [ - "bin/db-migrate" + "bin/migrate" ], "funding": [ diff --git a/composer.lock b/composer.lock index 46a8161..1dfccfc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a020cd8dec0b516b6aafb7b9b682a871", + "content-hash": "87f0b0c015fab236fc85362c43d91bf4", "packages": [ { "name": "magicalex/write-ini-file", @@ -57,6 +57,65 @@ }, "time": "2018-09-09T12:40:38+00:00" }, + { + "name": "phpgt/cli", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/PhpGt/Cli.git", + "reference": "3f00088f76225088602e6d9faa1cf90fbd2205d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PhpGt/Cli/zipball/3f00088f76225088602e6d9faa1cf90fbd2205d3", + "reference": "3f00088f76225088602e6d9faa1cf90fbd2205d3", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-readline": "*", + "php": ">=8.0", + "phpgt/daemon": "1.*" + }, + "require-dev": { + "phpstan/phpstan": ">=0.12.64", + "phpunit/phpunit": "9.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Gt\\Cli\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Bowler", + "email": "greg.bowler@g105b.com" + } + ], + "description": "Command line interface builder.", + "keywords": [ + "cli", + "phpgt", + "terminal", + "webengine" + ], + "support": { + "issues": "https://github.com/PhpGt/Cli/issues", + "source": "https://github.com/PhpGt/Cli/tree/v1.3.2" + }, + "funding": [ + { + "url": "https://github.com/sponsors/PhpGt", + "type": "github" + } + ], + "time": "2021-06-15T19:01:44+00:00" + }, { "name": "phpgt/config", "version": "v1.1.0", @@ -112,6 +171,47 @@ ], "time": "2021-01-30T14:24:07+00:00" }, + { + "name": "phpgt/daemon", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/PhpGt/Daemon.git", + "reference": "6490df99a22818149f30e3af408002ea7f73e035" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PhpGt/Daemon/zipball/6490df99a22818149f30e3af408002ea7f73e035", + "reference": "6490df99a22818149f30e3af408002ea7f73e035", + "shasum": "" + }, + "require": { + "php": ">=7.4" + }, + "require-dev": { + "phpstan/phpstan": ">=0.12.42", + "phpunit/phpunit": "9.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Gt\\Daemon\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "description": "Background script execution with cross-platform compatible streaming.", + "support": { + "issues": "https://github.com/PhpGt/Daemon/issues", + "source": "https://github.com/PhpGt/Daemon/tree/v1.1.2" + }, + "funding": [ + { + "url": "https://github.com/phpgt", + "type": "github" + } + ], + "time": "2021-02-02T17:33:16+00:00" + }, { "name": "phpgt/typesafegetter", "version": "v1.2.2", @@ -292,16 +392,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.10.5", + "version": "v4.13.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f" + "reference": "50953a2691a922aa1769461637869a0a2faa3f53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4432ba399e47c66624bc73c8c0f811e5c109576f", - "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53", + "reference": "50953a2691a922aa1769461637869a0a2faa3f53", "shasum": "" }, "require": { @@ -342,22 +442,22 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.5" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0" }, - "time": "2021-05-03T19:11:20+00:00" + "time": "2021-09-20T12:20:58+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", "shasum": "" }, "require": { @@ -402,9 +502,9 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/master" + "source": "https://github.com/phar-io/manifest/tree/2.0.3" }, - "time": "2020-06-27T14:33:11+00:00" + "time": "2021-07-20T11:28:43+00:00" }, { "name": "phar-io/version", @@ -568,16 +668,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + "reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/30f38bffc6f24293dadd1823936372dfa9e86e2f", + "reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f", "shasum": "" }, "require": { @@ -585,7 +685,8 @@ "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "*" + "ext-tokenizer": "*", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -611,39 +712,39 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.0" }, - "time": "2020-09-17T18:55:26+00:00" + "time": "2021-09-17T15:28:14+00:00" }, { "name": "phpspec/prophecy", - "version": "1.13.0", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.1", + "php": "^7.2 || ~8.0, <8.2", "phpdocumentor/reflection-docblock": "^5.2", "sebastian/comparator": "^3.0 || ^4.0", "sebastian/recursion-context": "^3.0 || ^4.0" }, "require-dev": { - "phpspec/phpspec": "^6.0", + "phpspec/phpspec": "^6.0 || ^7.0", "phpunit/phpunit": "^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -678,22 +779,22 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + "source": "https://github.com/phpspec/prophecy/tree/1.14.0" }, - "time": "2021-03-17T13:42:18+00:00" + "time": "2021-09-10T09:02:12+00:00" }, { "name": "phpstan/phpstan", - "version": "0.12.88", + "version": "0.12.99", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "464d1a81af49409c41074aa6640ed0c4cbd9bb68" + "reference": "b4d40f1d759942f523be267a1bab6884f46ca3f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/464d1a81af49409c41074aa6640ed0c4cbd9bb68", - "reference": "464d1a81af49409c41074aa6640ed0c4cbd9bb68", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b4d40f1d759942f523be267a1bab6884f46ca3f7", + "reference": "b4d40f1d759942f523be267a1bab6884f46ca3f7", "shasum": "" }, "require": { @@ -724,13 +825,17 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/0.12.88" + "source": "https://github.com/phpstan/phpstan/tree/0.12.99" }, "funding": [ { "url": "https://github.com/ondrejmirtes", "type": "github" }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, { "url": "https://www.patreon.com/phpstan", "type": "patreon" @@ -740,27 +845,27 @@ "type": "tidelift" } ], - "time": "2021-05-17T12:24:49+00:00" + "time": "2021-09-12T20:09:55+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.6", + "version": "9.2.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f6293e1b30a2354e8428e004689671b83871edde" + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", - "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d4c798ed8d51506800b441f7a13ecb0f76f12218", + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.10.2", + "nikic/php-parser": "^4.12.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -809,7 +914,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.7" }, "funding": [ { @@ -817,7 +922,7 @@ "type": "github" } ], - "time": "2021-03-28T07:26:59+00:00" + "time": "2021-09-17T05:39:03+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1062,16 +1167,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.4", + "version": "9.5.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c73c6737305e779771147af66c96ca6a7ed8a741" + "reference": "ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c73c6737305e779771147af66c96ca6a7ed8a741", - "reference": "c73c6737305e779771147af66c96ca6a7ed8a741", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b", + "reference": "ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b", "shasum": "" }, "require": { @@ -1083,7 +1188,7 @@ "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.1", + "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", "phpspec/prophecy": "^1.12.1", @@ -1101,7 +1206,7 @@ "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3", + "sebastian/type": "^2.3.4", "sebastian/version": "^3.0.2" }, "require-dev": { @@ -1149,7 +1254,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.4" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.9" }, "funding": [ { @@ -1161,7 +1266,7 @@ "type": "github" } ], - "time": "2021-03-23T07:16:29+00:00" + "time": "2021-08-31T06:47:40+00:00" }, { "name": "sebastian/cli-parser", @@ -1669,16 +1774,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.2", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "a90ccbddffa067b51f574dea6eb25d5680839455" + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/a90ccbddffa067b51f574dea6eb25d5680839455", - "reference": "a90ccbddffa067b51f574dea6eb25d5680839455", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", "shasum": "" }, "require": { @@ -1721,7 +1826,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" }, "funding": [ { @@ -1729,7 +1834,7 @@ "type": "github" } ], - "time": "2020-10-26T15:55:19+00:00" + "time": "2021-06-11T13:31:12+00:00" }, { "name": "sebastian/lines-of-code", @@ -2020,16 +2125,16 @@ }, { "name": "sebastian/type", - "version": "2.3.1", + "version": "2.3.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2" + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", "shasum": "" }, "require": { @@ -2064,7 +2169,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.1" + "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" }, "funding": [ { @@ -2072,7 +2177,7 @@ "type": "github" } ], - "time": "2020-10-26T13:18:59+00:00" + "time": "2021-06-15T12:49:02+00:00" }, { "name": "sebastian/version", @@ -2129,16 +2234,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", "shasum": "" }, "require": { @@ -2150,7 +2255,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2188,7 +2293,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" }, "funding": [ { @@ -2204,20 +2309,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "75a63c33a8577608444246075ea0af0d052e452a" + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", - "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "shasum": "" }, "require": { @@ -2246,7 +2351,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/master" + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" }, "funding": [ { @@ -2254,7 +2359,7 @@ "type": "github" } ], - "time": "2020-07-12T23:59:07+00:00" + "time": "2021-07-28T10:34:58+00:00" }, { "name": "webmozart/assert", diff --git a/src/Cli/ExecuteCommand.php b/src/Cli/ExecuteCommand.php new file mode 100644 index 0000000..8dc9405 --- /dev/null +++ b/src/Cli/ExecuteCommand.php @@ -0,0 +1,129 @@ +contains("force"); + + $repoBasePath = getcwd(); + $defaultPath = $this->getDefaultPath($repoBasePath); + + $config = ConfigFactory::createForProject($repoBasePath); + + $default = $defaultPath + ? ConfigFactory::createFromPathName($defaultPath) + : null; + + if($default) { + $config->merge($default); + } + + $settings = new Settings( + implode(DIRECTORY_SEPARATOR, [ + $repoBasePath, + $config->get("database.query_path") + ]), + + $config->get("database.driver") ?? 'mysql', + $config->get("database.schema"), + $config->get("database.host") ?? "localhost", + (int)($config->get("database.port") ?? "3306"), + $config->get("database.username"), + $config->get("database.password") + ); + + $migrationPath = implode(DIRECTORY_SEPARATOR, [ + $repoBasePath, + $config->get("database.query_path") ?? "query", + $config->get("database.migration_path") ?? "_migration", + ]); + $migrationTable = $config->get("database.migration_table") ?? "_migration"; + + $migrator = new Migrator($settings, $migrationPath, $migrationTable); + $migrator->setOutput( + $this->stream->getOutStream(), + $this->stream->getErrorStream() + ); + + if($forced) { + $migrator->deleteAndRecreateSchema(); + } + + $migrator->selectSchema(); + $migrator->createMigrationTable(); + $migrationCount = $migrator->getMigrationCount(); + $migrationFileList = $migrator->getMigrationFileList(); + + try { + $migrator->checkIntegrity($migrationFileList, $migrationCount); + $migrator->performMigration($migrationFileList, $migrationCount); + } + catch(MigrationIntegrityException $exception) { + $this->writeLine("There was an integrity error migrating file '" . $exception->getMessage() . "' - this migration is recorded to have been run already, but the contents of the file has changed.\nFor help, see https://www.php.gt/database/migrations#integrity-error"); + } + catch(StatementPreparationException|StatementExecutionException $exception) { + $this->writeLine("There was an error executing migration file: " . $exception->getMessage() . "'\nFor help, see https://www.php.gt/database/migrations#error"); + } + } + + public function getName():string { + return "execute"; + } + + public function getDescription():string { + return "Perform a database migration"; + } + + public function getRequiredNamedParameterList():array { + return []; + } + + public function getOptionalNamedParameterList():array { +// TODO: It would be an improvement to allow passing database settings here rather than always require a config.ini + return []; + } + + public function getRequiredParameterList():array { + return []; + } + + public function getOptionalParameterList():array { + return [ + new Parameter( + false, + "force", + "f", + "Forcefully drop the current schema and run from migration 1" + ) + ]; + } + + private function getDefaultPath(string $repoBasePath):?string { + $defaultPath = implode(DIRECTORY_SEPARATOR, [ + $repoBasePath, + "vendor", + "phpgt", + "webengine", + ]); + foreach(["config.default.ini", "default.ini"] as $defaultFile) { + $defaultFilePath = $defaultPath . DIRECTORY_SEPARATOR . $defaultFile; + + if(is_file($defaultFilePath)) { + return $defaultFilePath; + } + } + + return null; + } +} diff --git a/src/Database.php b/src/Database.php index 25f9a20..18e7bd6 100644 --- a/src/Database.php +++ b/src/Database.php @@ -99,13 +99,21 @@ public function executeSql( $statement = $connection->prepare($query); } catch(PDOException $exception) { - throw new DatabaseException( + throw new StatementPreparationException( $exception->getMessage(), intval($exception->getCode()) ); } - $statement->execute($bindings); + try { + $statement->execute($bindings); + } + catch(PDOException $exception) { + throw new StatementExecutionException( + $exception->getMessage(), + intval($exception->getCode()) + ); + } return new ResultSet($statement, $connection->lastInsertId()); } diff --git a/src/Migration/Migrator.php b/src/Migration/Migrator.php index 3e01050..eb43ac6 100644 --- a/src/Migration/Migrator.php +++ b/src/Migration/Migrator.php @@ -34,8 +34,7 @@ class Migrator { public function __construct( Settings $settings, string $path, - string $tableName = "_migration", - bool $forced = false + string $tableName = "_migration" ) { $this->schema = $settings->getSchema(); $this->path = $path; @@ -50,12 +49,6 @@ public function __construct( } $this->dbClient = new Database($settings); - - if($forced) { - $this->deleteAndRecreateSchema(); - } - - $this->selectSchema(); } public function setOutput( @@ -207,7 +200,7 @@ public function performMigration( ):int { $fileNumber = 0; $numCompleted = 0; - + foreach($migrationFileList as $i => $file) { $fileNumber = $i + 1; @@ -225,10 +218,6 @@ public function performMigration( $this->recordMigrationSuccess($fileNumber, $md5); } catch(DatabaseException $exception) { - $this->output( - $exception->getMessage(), - self::STREAM_ERROR - ); throw $exception; } @@ -236,10 +225,10 @@ public function performMigration( } if($numCompleted === 0) { - $this->output("No migrations were made."); + $this->output("Migrations are already up to date."); } else { - $this->output("Completed migrations successfully."); + $this->output("$numCompleted migrations were completed successfully."); } return $numCompleted; @@ -248,7 +237,7 @@ public function performMigration( /** * @codeCoverageIgnore */ - protected function selectSchema() { + public function selectSchema() { // SQLITE databases represent their own schema. if($this->driver === Settings::DRIVER_SQLITE) { return; @@ -295,7 +284,7 @@ protected function recordMigrationSuccess(int $number, string $hash) { /** * @codeCoverageIgnore */ - protected function deleteAndRecreateSchema() { + public function deleteAndRecreateSchema() { if($this->driver === Settings::DRIVER_SQLITE) { return; } @@ -333,4 +322,4 @@ protected function output( $stream->fwrite($message . PHP_EOL); } -} \ No newline at end of file +} diff --git a/src/StatementExecutionException.php b/src/StatementExecutionException.php new file mode 100644 index 0000000..3c61b50 --- /dev/null +++ b/src/StatementExecutionException.php @@ -0,0 +1,6 @@ +rewind(); $output = $streamOut->fread(4096); self::assertStringContainsString("Migration 1:", $output); - self::assertStringContainsString("Completed migrations successfully.", $output); + + $expectedCount = count($fileList); + self::assertStringContainsString("$expectedCount migrations were completed successfully.", $output); } /** @dataProvider dataMigrationFileList */ @@ -533,10 +535,6 @@ public function testMigrationErrorOutputToStream(array $fileList) { "Migration 1:", $outputError ); - self::assertStringContainsString( - "General error: 1 near \"nothing\": syntax error", - $outputError - ); self::assertStringNotContainsString( "General error: 1 near \"nothing\": syntax error", $output