diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index c4cd14c..5d163c0 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -69,7 +69,7 @@ jobs: MSSQL_TEST_HOST: sqlserver steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # - name: Spin up databases # run: | # apk add --no-cache python3 python3-dev py3-pip build-base libffi-dev @@ -92,5 +92,5 @@ jobs: env: DOC_GITHUB_TOKEN: '${{ secrets.DOC_TOKEN }}' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: curl https://opensource.byjg.com/add-doc.sh | bash /dev/stdin php migration diff --git a/.gitignore b/.gitignore index a1bd449..c51a039 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ fabric.properties node_modules package-lock.json .usdocker +.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7ffb61c..0000000 --- a/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ -language: php - -php: - - "7.3" - - "7.2" - - "7.1" - - "7.0" - - "5.6" - -services: - - docker - -before_install: - - sudo service mysql stop || echo "mysql not stopped" - - sudo service postgresql stop || echo "postgresql not stopped" - - docker-compose up -d postgres mysql - -install: - - composer install - -script: - - vendor/bin/phpunit - - vendor/bin/phpunit tests/SqliteDatabase* - - ./wait-for-db.sh postgres && vendor/bin/phpunit tests/PostgresDatabase* - - ./wait-for-db.sh mysql && vendor/bin/phpunit tests/MysqlDatabase* -# - ./wait-for-db.sh mssql && vendor/bin/phpunit tests/SqlServer* - -jobs: - include: - - stage: documentation - if: branch = master - install: skip - script: "curl https://opensource.byjg.com/add-doc.sh | bash /dev/stdin php migration" - diff --git a/README.md b/README.md index df559a0..d79f7e4 100644 --- a/README.md +++ b/README.md @@ -12,20 +12,22 @@ This is a simple library written in PHP for database version control. Currently supports Sqlite, MySql, Sql Server and Postgres. Database Migration can be used as: - - Command Line Interface - - PHP Library to be integrated in your functional tests - - Integrated in you CI/CD indenpent of your programming language or framework. - + +* Command Line Interface +* PHP Library to be integrated in your functional tests +* Integrated in you CI/CD indenpent of your programming language or framework. + Database Migrates uses only SQL commands for versioning your database. ## Why pure SQL commands? -The most of the frameworks tend to use programming statements for versioning your database instead of use pure SQL. +The most of the frameworks tend to use programming statements for versioning your database instead of use pure SQL. There are some advantages to use the native programming language of your framework to maintain the database: - - Framework commands have some trick codes to do complex tasks; - - You can code once and deploy to different database systems; - - And others + +* Framework commands have some trick codes to do complex tasks; +* You can code once and deploy to different database systems; +* And others But at the end despite these good features the reality in big projects someone will use the MySQL Workbench to change your database and then spend some hours translating that code for PHP. So, why do not use the feature existing in MySQL Workbench, JetBrains DataGrip and others that provides the SQL Commands necessary to update your database and put directly into the database versioning system? @@ -37,51 +39,51 @@ Because of that this is an agnostic project (independent of framework and Progra If you want to use only the PHP Library in your project: -``` +```bash composer require "byjg/migration":"4.2.*" ``` + ### Command Line Interface The command line interface is standalone and does not require you install with your project. -You can install global and create a symbolic lynk +You can install global and create a symbolic lynk -``` +```bash composer require "byjg/migration-cli":"4.1.*" ``` -Please visit https://github.com/byjg/migration-cli to get more informations about Migration CLI. +Please visit [byjg/migration-cli](https://github.com/byjg/migration-cli) to get more informations about Migration CLI. -## Supported databases: +## Supported databases | Database | Driver | Connection String | | --------------| ------------------------------------------------------------------------------- | -------------------------------------------------------- | -| Sqlite | [pdo_sqlite](https://www.php.net/manual/en/ref.pdo-sqlite.php) | sqlite:///path/to/file | +| Sqlite | [pdo_sqlite](https://www.php.net/manual/en/ref.pdo-sqlite.php) | sqlite:///path/to/file | | MySql/MariaDb | [pdo_mysql](https://www.php.net/manual/en/ref.pdo-mysql.php) | mysql://username:password@hostname:port/database | -| Postgres | [pdo_pgsql](https://www.php.net/manual/en/ref.pdo-pgsql.php) | pgsql://username:password@hostname:port/database | +| Postgres | [pdo_pgsql](https://www.php.net/manual/en/ref.pdo-pgsql.php) | pgsql://username:password@hostname:port/database | | Sql Server | [pdo_dblib, pdo_sysbase](https://www.php.net/manual/en/ref.pdo-dblib.php) Linux | dblib://username:password@hostname:port/database | | Sql Server | [pdo_sqlsrv](http://msdn.microsoft.com/en-us/sqlserver/ff657782.aspx) Windows | sqlsrv://username:password@hostname:port/database | - ## How It Works? -The Database Migration uses PURE SQL to manage the database versioning. +The Database Migration uses PURE SQL to manage the database versioning. In order to get working you need to: - - Create the SQL Scripts - - Manage using Command Line or the API. +* Create the SQL Scripts +* Manage using Command Line or the API. ### The SQL Scripts The scripts are divided in three set of scripts: -- The BASE script contains ALL sql commands for create a fresh database; -- The UP scripts contain all sql migration commands for "up" the database version; -- The DOWN scripts contain all sql migration commands for "down" or revert the database version; +* The BASE script contains ALL sql commands for create a fresh database; +* The UP scripts contain all sql migration commands for "up" the database version; +* The DOWN scripts contain all sql migration commands for "down" or revert the database version; The directory scripts is : -``` +```text | +-- base.sql @@ -96,43 +98,42 @@ The directory scripts is : | +-- 00000.sql +-- 00001.sql -``` +``` - - "base.sql" is the base script - - "up" folder contains the scripts for migrate up the version. - For example: 00002.sql is the script for move the database from version '1' to '2'. - - "down" folder contains the scripts for migrate down the version. +* "base.sql" is the base script +* "up" folder contains the scripts for migrate up the version. + For example: 00002.sql is the script for move the database from version '1' to '2'. +* "down" folder contains the scripts for migrate down the version. For example: 00001.sql is the script for move the database from version '2' to '1'. - The "down" folder is optional. + The "down" folder is optional. -### Multi Development environment +### Multi Development environment If you work with multiple developers and multiple branches it is to difficult to determine what is the next number. -In that case you have the suffix "-dev" after the version number. +In that case you have the suffix "-dev" after the version number. See the scenario: - - Developer 1 create a branch and the most recent version in e.g. 42. - - Developer 2 create a branch at the same time and have the same database version number. +* Developer 1 create a branch and the most recent version in e.g. 42. +* Developer 2 create a branch at the same time and have the same database version number. In both case the developers will create a file called 43-dev.sql. Both developers will migrate UP and DOWN with -no problem and your local version will be 43. +no problem and your local version will be 43. But developer 1 merged your changes and created a final version 43.sql (`git mv 43-dev.sql 43.sql`). If the developer 2 -update your local branch he will have a file 43.sql (from dev 1) and your file 43-dev.sql. +update your local branch he will have a file 43.sql (from dev 1) and your file 43-dev.sql. If he is try to migrate UP or DOWN the migration script will down and alert him there a TWO versions 43. In that case, developer 2 will have to update your -file do 44-dev.sql and continue to work until merge your changes and generate a final version. +file do 44-dev.sql and continue to work until merge your changes and generate a final version. +## Using the PHP API and Integrate it into your projects -## Using the PHP API and Integrate it into your projects. +The basic usage is -The basic usage is - -- Create a connection a ConnectionManagement object. For more information see the "byjg/anydataset" component -- Create a Migration object with this connection and the folder where the scripts sql are located. -- Use the proper command for "reset", "up" or "down" the migrations scripts. +* Create a connection a ConnectionManagement object. For more information see the "byjg/anydataset" component +* Create a Migration object with this connection and the folder where the scripts sql are located. +* Use the proper command for "reset", "up" or "down" the migrations scripts. See an example: @@ -142,13 +143,12 @@ See an example: // See more: https://github.com/byjg/anydataset#connection-based-on-uri $connectionUri = new \ByJG\Util\Uri('mysql://migrateuser:migratepwd@localhost/migratedatabase'); +// Register the Database or Databases can handle that URI: +\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class); + // Create the Migration instance $migration = new \ByJG\DbMigration\Migration($connectionUri, '.'); -// Register the Database or Databases can handle that URI: -$migration->registerDatabase('mysql', \ByJG\DbMigration\Database\MySqlDatabase::class); -$migration->registerDatabase('maria', \ByJG\DbMigration\Database\MySqlDatabase::class); - // Add a callback progress function to receive info from the execution $migration->addCallbackProgress(function ($action, $currentVersion, $fileInfo) { echo "$action, $currentVersion, ${fileInfo['description']}\n"; @@ -160,23 +160,22 @@ $migration->reset(); // Run ALL existing scripts for up or down the database version // from the current version until the $version number; -// If the version number is not specified migrate until the last database version +// If the version number is not specified migrate until the last database version $migration->update($version = null); ``` The Migration object controls the database version. - -### Creating a version control in your project: +### Creating a version control in your project ```php registerDatabase('mysql', \ByJG\DbMigration\Database\MySqlDatabase::class); - // This command will create the version table in your database $migration->createVersion(); ``` @@ -204,8 +203,7 @@ $migration->addCallbackProgress(function ($command, $version, $fileInfo) { $migration->getDbDriver(); ``` -To use it, please visit: https://github.com/byjg/anydataset-db - +To use it, please visit: ## Tips on writing SQL migrations for Postgres @@ -338,13 +336,12 @@ Finally, writing manual SQL migrations can be tiresome, but it is significantly you use an editor capable of understanding the SQL syntax, providing autocomplete, introspecting your current database schema and/or autoformatting your code. - ## Handling different migration inside one schema If you need to create different migration scripts and version inside the same schema it is possible -but is too risky and I **do not** recommend at all. +but is too risky and I **do not** recommend at all. -To do this, you need to create different "migration tables" by passing the parameter to the constructor. +To do this, you need to create different "migration tables" by passing the parameter to the constructor. ```php registerDatabase('mysql', \ByJG\DbMigration\Database\MySqlDatabase::class); +Migration::registerDatabase(MySqlDatabase::class); + +$migration = new Migration($uri, __DIR__); $migration->prepareEnvironment(); diff --git a/example/postgres/test_postgres.php b/example/postgres/test_postgres.php index 3689773..b85d430 100644 --- a/example/postgres/test_postgres.php +++ b/example/postgres/test_postgres.php @@ -1,5 +1,8 @@ registerDatabase('pgsql', \ByJG\DbMigration\Database\PgsqlDatabase::class); +Migration::registerDatabase(PgsqlDatabase::class); + +$migration = new Migration($uri, __DIR__); $migration->prepareEnvironment(); diff --git a/example/sql_server/test_sqlserver.php b/example/sql_server/test_sqlserver.php index ae9696f..0040d3f 100644 --- a/example/sql_server/test_sqlserver.php +++ b/example/sql_server/test_sqlserver.php @@ -1,5 +1,8 @@ registerDatabase('dblib', \ByJG\DbMigration\Database\DblibDatabase::class); +Migration::registerDatabase(DblibDatabase::class); + +$migration = new Migration($uri, __DIR__); $migration->prepareEnvironment(); diff --git a/example/sqlite/test_sqlite.php b/example/sqlite/test_sqlite.php index c67f7b8..dca6687 100644 --- a/example/sqlite/test_sqlite.php +++ b/example/sqlite/test_sqlite.php @@ -1,11 +1,15 @@ registerDatabase('sqlite', \ByJG\DbMigration\Database\SqliteDatabase::class); +Migration::registerDatabase(SqliteDatabase::class); + +$migration = new Migration($uri, __DIR__); $migration->reset(); diff --git a/phpunit.xml.dist b/phpunit.xml.dist index dabfb03..23c7c36 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -8,11 +8,19 @@ and open the template in the editor. + + + + + + ./src diff --git a/src/Database/DatabaseInterface.php b/src/Database/DatabaseInterface.php index 5a8e144..ddb9696 100644 --- a/src/Database/DatabaseInterface.php +++ b/src/Database/DatabaseInterface.php @@ -6,10 +6,12 @@ interface DatabaseInterface { + public static function schema(); + public static function prepareEnvironment(UriInterface $dbDriver); public function createDatabase(); - + public function dropDatabase(); /** @@ -20,11 +22,11 @@ public function dropDatabase(); public function getVersion(); public function updateVersionTable(); - + public function executeSql($sql); - + public function setVersion($version, $status); - + public function createVersion(); public function isDatabaseVersioned(); diff --git a/src/Database/DblibDatabase.php b/src/Database/DblibDatabase.php index c4a554e..940b3a7 100644 --- a/src/Database/DblibDatabase.php +++ b/src/Database/DblibDatabase.php @@ -8,6 +8,10 @@ class DblibDatabase extends AbstractDatabase { + public static function schema() + { + return ['dblib', 'sqlsrv']; + } public static function prepareEnvironment(UriInterface $uri) { @@ -39,9 +43,9 @@ protected function createTableIfNotExists($database, $createTable) { $this->getDbDriver()->execute("use $database"); - $sql = "IF (NOT EXISTS (SELECT * - FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_SCHEMA = 'dbo' + $sql = "IF (NOT EXISTS (SELECT * + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = '" . $this->getMigrationTable() . "')) BEGIN $createTable diff --git a/src/Database/MySqlDatabase.php b/src/Database/MySqlDatabase.php index 297e693..63f068d 100644 --- a/src/Database/MySqlDatabase.php +++ b/src/Database/MySqlDatabase.php @@ -8,6 +8,10 @@ class MySqlDatabase extends AbstractDatabase { + public static function schema() + { + return ['mysql', 'mariadb']; + } public static function prepareEnvironment(UriInterface $uri) { diff --git a/src/Database/PgsqlDatabase.php b/src/Database/PgsqlDatabase.php index f1b18ef..f1eaaea 100644 --- a/src/Database/PgsqlDatabase.php +++ b/src/Database/PgsqlDatabase.php @@ -8,6 +8,10 @@ class PgsqlDatabase extends AbstractDatabase { + public static function schema() + { + return 'pgsql'; + } public static function prepareEnvironment(UriInterface $uri) { diff --git a/src/Database/SqliteDatabase.php b/src/Database/SqliteDatabase.php index a2522ce..c682952 100644 --- a/src/Database/SqliteDatabase.php +++ b/src/Database/SqliteDatabase.php @@ -6,6 +6,10 @@ class SqliteDatabase extends AbstractDatabase { + public static function schema() + { + return 'sqlite'; + } public static function prepareEnvironment(UriInterface $uri) { @@ -18,9 +22,9 @@ public function createDatabase() public function dropDatabase() { $iterator = $this->getDbDriver()->getIterator(" - select - 'drop ' || type || ' ' || name || ';' as command - from sqlite_master + select + 'drop ' || type || ' ' || name || ';' as command + from sqlite_master where name <> 'sqlite_sequence' and name not like 'sqlite_autoindex_%' order by CASE type WHEN 'index' THEN 0 diff --git a/src/Migration.php b/src/Migration.php index 536867f..168e782 100644 --- a/src/Migration.php +++ b/src/Migration.php @@ -7,6 +7,7 @@ use ByJG\DbMigration\Exception\DatabaseDoesNotRegistered; use ByJG\DbMigration\Exception\DatabaseIsIncompleteException; use ByJG\DbMigration\Exception\InvalidMigrationFile; +use InvalidArgumentException; use Psr\Http\Message\UriInterface; class Migration @@ -43,7 +44,7 @@ class Migration /** * @var array */ - protected $databases = []; + protected static $databases = []; /** * @var string */ @@ -73,10 +74,16 @@ public function __construct(UriInterface $uri, $folder, $requiredBase = true, $m * @param $className * @return $this */ - public function registerDatabase($scheme, $className) + public static function registerDatabase($class) { - $this->databases[$scheme] = $className; - return $this; + if (!in_array(DatabaseInterface::class, class_implements($class))) { + throw new InvalidArgumentException('Class not implements DatabaseInterface!'); + } + + $protocolList = $class::schema(); + foreach ((array)$protocolList as $item) { + self::$databases[$item] = $class; + } } /** @@ -112,8 +119,8 @@ public function getMigrationTable() */ protected function getDatabaseClassName() { - if (isset($this->databases[$this->uri->getScheme()])) { - return $this->databases[$this->uri->getScheme()]; + if (isset(self::$databases[$this->uri->getScheme()])) { + return self::$databases[$this->uri->getScheme()]; } throw new DatabaseDoesNotRegistered( 'Scheme "' . $this->uri->getScheme() . '" does not found. Did you registered it?' diff --git a/tests/MigrationTest.php b/tests/MigrationTest.php index 8cbb713..b37640a 100644 --- a/tests/MigrationTest.php +++ b/tests/MigrationTest.php @@ -63,7 +63,7 @@ public function testGetMigrationSql3() public function testGetMigrationSql4() { $this->expectException(InvalidMigrationFile::class); - $this->expectErrorMessage("version number '13'"); + $this->expectExceptionMessage("version number '13'"); $this->object->getMigrationSql(13, 1); } @@ -88,7 +88,7 @@ public function testGetMigrationSqlDown3() public function testGetMigrationSqlDown4() { $this->expectException(InvalidMigrationFile::class); - $this->expectErrorMessage("version number '13'"); + $this->expectExceptionMessage("version number '13'"); $this->object->getMigrationSql(13, -1); } diff --git a/tests/MysqlDatabaseTest.php b/tests/MysqlDatabaseTest.php index ba5f8bf..e03ba01 100644 --- a/tests/MysqlDatabaseTest.php +++ b/tests/MysqlDatabaseTest.php @@ -30,10 +30,11 @@ public function setUp(): void $password = ""; } - $uri = "mysql://root:${password}@${host}/migratedatabase"; + $uri = "mysql://root:{$password}@{$host}/migratedatabase"; + + Migration::registerDatabase(MySqlDatabase::class); $this->migrate = new Migration(new Uri($uri), __DIR__ . '/../example/mysql', true, $this->migrationTable); - $this->migrate->registerDatabase("mysql", MySqlDatabase::class); parent::setUp(); } } diff --git a/tests/PostgresDatabaseTest.php b/tests/PostgresDatabaseTest.php index d98e59a..18a7e84 100644 --- a/tests/PostgresDatabaseTest.php +++ b/tests/PostgresDatabaseTest.php @@ -31,10 +31,11 @@ public function setUp(): void $password = ""; } - $uri = "pgsql://postgres:${password}@${host}/migratedatabase"; + $uri = "pgsql://postgres:{$password}@{$host}/migratedatabase"; + + Migration::registerDatabase(PgsqlDatabase::class); $this->migrate = new Migration(new Uri($uri), __DIR__ . '/../example/postgres', true, $this->migrationTable); - $this->migrate->registerDatabase("pgsql", PgsqlDatabase::class); parent::setUp(); } } diff --git a/tests/SqlServerDblibDatabaseTest.php b/tests/SqlServerDblibDatabaseTest.php index ce25146..e5d6fb1 100644 --- a/tests/SqlServerDblibDatabaseTest.php +++ b/tests/SqlServerDblibDatabaseTest.php @@ -29,10 +29,11 @@ public function setUp(): void $password = 'Pa55word'; } - $uri = $this->scheme . "://sa:${password}@${host}/migratedatabase"; + $uri = $this->scheme . "://sa:{$password}@{$host}/migratedatabase"; + + Migration::registerDatabase(DblibDatabase::class); $this->migrate = new Migration(new Uri($uri), __DIR__ . '/../example/sql_server', true, $this->migrationTable); - $this->migrate->registerDatabase($this->scheme, DblibDatabase::class); parent::setUp(); } diff --git a/tests/SqliteDatabaseTest.php b/tests/SqliteDatabaseTest.php index 4262bf1..ad4cbde 100644 --- a/tests/SqliteDatabaseTest.php +++ b/tests/SqliteDatabaseTest.php @@ -30,8 +30,9 @@ public function setUp(): void $uri = new Uri("sqlite://{$this->path}"); + Migration::registerDatabase(SqliteDatabase::class); + $this->migrate = new Migration($uri, __DIR__ . '/../example/sqlite', true, $this->migrationTable); - $this->migrate->registerDatabase("sqlite", SqliteDatabase::class); parent::setUp(); } @@ -41,9 +42,10 @@ public function testUsingCustomTable() $this->prepareDatabase(); + Migration::registerDatabase(SqliteDatabase::class); + $uri = new Uri("sqlite://{$this->path}"); $this->migrate = new Migration($uri, __DIR__ . '/../example/sqlite', true, $this->migrationTable); - $this->migrate->registerDatabase("sqlite", SqliteDatabase::class); parent::testUpVersion1(); }