{"id":13607569,"url":"https://github.com/byjg/php-migration","last_synced_at":"2025-05-15T15:08:37.281Z","repository":{"id":9079810,"uuid":"60751798","full_name":"byjg/php-migration","owner":"byjg","description":"Simple library writen in PHP without framework dependancy for database version control. Supports Sqlite, MySql, Sql Server and Postgres","archived":false,"fork":false,"pushed_at":"2025-03-25T02:50:48.000Z","size":288,"stargazers_count":163,"open_issues_count":14,"forks_count":27,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-05-11T14:49:03.067Z","etag":null,"topics":["database-migrations","mysql","php","php56","php7","php71","postgresql","sql-script","sqlite3","sqlserver"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/byjg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"byjg"}},"created_at":"2016-06-09T05:25:46.000Z","updated_at":"2025-04-16T11:18:22.000Z","dependencies_parsed_at":"2024-01-05T22:03:22.237Z","dependency_job_id":"9bd75c9e-91bf-4145-a93b-610f1e7f79c8","html_url":"https://github.com/byjg/php-migration","commit_stats":null,"previous_names":["byjg/php-migration","byjg/migration"],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byjg%2Fphp-migration","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byjg%2Fphp-migration/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byjg%2Fphp-migration/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byjg%2Fphp-migration/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/byjg","download_url":"https://codeload.github.com/byjg/php-migration/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254364270,"owners_count":22058878,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database-migrations","mysql","php","php56","php7","php71","postgresql","sql-script","sqlite3","sqlserver"],"created_at":"2024-08-01T19:01:19.766Z","updated_at":"2025-05-15T15:08:32.269Z","avatar_url":"https://github.com/byjg.png","language":"PHP","readme":"# Database Migrations PHP\n\n[![Opensource ByJG](https://img.shields.io/badge/opensource-byjg-success.svg)](http://opensource.byjg.com)\n[![GitHub source](https://img.shields.io/badge/Github-source-informational?logo=github)](https://github.com/byjg/php-migration/)\n[![GitHub license](https://img.shields.io/github/license/byjg/php-migration.svg)](https://opensource.byjg.com/opensource/licensing.html)\n[![GitHub release](https://img.shields.io/github/release/byjg/php-migration.svg)](https://github.com/byjg/php-migration/releases/)\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/byjg/migration/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/byjg/migration/?branch=master)\n[![Build Status](https://github.com/byjg/migration/actions/workflows/phpunit.yml/badge.svg?branch=master)](https://github.com/byjg/migration/actions/workflows/phpunit.yml)\n\n## Features\n\nThis is a simple library written in PHP for database version control. Currently supports Sqlite, MySql, Sql Server and Postgres.\n\nDatabase Migration can be used as:\n\n* Command Line Interface\n* PHP Library to be integrated in your functional tests\n* Integrated in you CI/CD indenpent of your programming language or framework.\n\nDatabase Migrates uses only SQL commands for versioning your database.\n\n## Why pure SQL commands?\n\nThe most of the frameworks tend to use programming statements for versioning your database instead of use pure SQL.\n\nThere are some advantages to use the native programming language of your framework to maintain the database:\n\n* Framework commands have some trick codes to do complex tasks;\n* You can code once and deploy to different database systems;\n* And others\n\nBut 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?\n\nBecause of that this is an agnostic project (independent of framework and Programming Language) and use pure and native SQL commands for migrate your database.\n\n## Installing\n\n### PHP Library\n\nIf you want to use only the PHP Library in your project:\n\n```bash\ncomposer require \"byjg/migration\"\n```\n\n### Command Line Interface\n\nThe command line interface is standalone and does not require you install with your project.\n\nYou can install global and create a symbolic lynk\n\n```bash\ncomposer require \"byjg/migration-cli\"\n```\n\nPlease visit [byjg/migration-cli](https://github.com/byjg/migration-cli) to get more informations about Migration CLI.\n\n## Supported databases\n\n| Database      | Driver                                                                          | Connection String                                        |\n| --------------| ------------------------------------------------------------------------------- | -------------------------------------------------------- |\n| Sqlite        | [pdo_sqlite](https://www.php.net/manual/en/ref.pdo-sqlite.php)                  | sqlite:///path/to/file                                   |\n| MySql/MariaDb | [pdo_mysql](https://www.php.net/manual/en/ref.pdo-mysql.php)                    | mysql://username:password@hostname:port/database         |\n| Postgres      | [pdo_pgsql](https://www.php.net/manual/en/ref.pdo-pgsql.php)                    | pgsql://username:password@hostname:port/database         |\n| Sql Server    | [pdo_dblib, pdo_sysbase](https://www.php.net/manual/en/ref.pdo-dblib.php) Linux | dblib://username:password@hostname:port/database         |\n| Sql Server    | [pdo_sqlsrv](http://msdn.microsoft.com/en-us/sqlserver/ff657782.aspx) Windows   | sqlsrv://username:password@hostname:port/database        |\n\n## How It Works?\n\nThe Database Migration uses PURE SQL to manage the database versioning.\nIn order to get working you need to:\n\n* Create the SQL Scripts\n* Manage using Command Line or the API.\n\n### The SQL Scripts\n\nThe scripts are divided in three set of scripts:\n\n* The BASE script contains ALL sql commands for create a fresh database;\n* The UP scripts contain all sql migration commands for \"up\" the database version;\n* The DOWN scripts contain all sql migration commands for \"down\" or revert the database version;\n\nThe directory scripts is :\n\n```text\n \u003croot dir\u003e\n     |\n     +-- base.sql\n     |\n     +-- /migrations\n              |\n              +-- /up\n                   |\n                   +-- 00001.sql\n                   +-- 00002.sql\n              +-- /down\n                   |\n                   +-- 00000.sql\n                   +-- 00001.sql\n```\n\n* \"base.sql\" is the base script\n* \"up\" folder contains the scripts for migrate up the version.\n   For example: 00002.sql is the script for move the database from version '1' to '2'.\n* \"down\" folder contains the scripts for migrate down the version.\n   For example: 00001.sql is the script for move the database from version '2' to '1'.\n   The \"down\" folder is optional.\n\n### Multi Development environment\n\nIf you work with multiple developers and multiple branches it is to difficult to determine what is the next number.\n\nIn that case you have the suffix \"-dev\" after the version number.\n\nSee the scenario:\n\n* Developer 1 create a branch and the most recent version in e.g. 42.\n* Developer 2 create a branch at the same time and have the same database version number.\n\nIn both case the developers will create a file called 43-dev.sql. Both developers will migrate UP and DOWN with\nno problem and your local version will be 43.\n\nBut developer 1 merged your changes and created a final version 43.sql (`git mv 43-dev.sql 43.sql`). If the developer 2\nupdate your local branch he will have a file 43.sql (from dev 1) and your file 43-dev.sql.\nIf he is try to migrate UP or DOWN\nthe migration script will down and alert him there a TWO versions 43. In that case, developer 2 will have to update your\nfile do 44-dev.sql and continue to work until merge your changes and generate a final version.\n\n## Using the PHP API and Integrate it into your projects\n\nThe basic usage is\n\n* Create a connection a ConnectionManagement object. For more information see the \"byjg/anydataset\" component\n* Create a Migration object with this connection and the folder where the scripts sql are located.\n* Use the proper command for \"reset\", \"up\" or \"down\" the migrations scripts.\n\nSee an example:\n\n```php\n\u003c?php\n// Create the Connection URI\n// See more: https://github.com/byjg/anydataset#connection-based-on-uri\n$connectionUri = new \\ByJG\\Util\\Uri('mysql://migrateuser:migratepwd@localhost/migratedatabase');\n\n// Register the Database or Databases can handle that URI:\n\\ByJG\\DbMigration\\Migration::registerDatabase(\\ByJG\\DbMigration\\Database\\MySqlDatabase::class);\n\n// Create the Migration instance\n$migration = new \\ByJG\\DbMigration\\Migration($connectionUri, '.');\n\n// Add a callback progress function to receive info from the execution\n$migration-\u003eaddCallbackProgress(function ($action, $currentVersion, $fileInfo) {\n    echo \"$action, $currentVersion, ${fileInfo['description']}\\n\";\n});\n\n// Restore the database using the \"base.sql\" script\n// and run ALL existing scripts for up the database version to the latest version\n$migration-\u003ereset();\n\n// Run ALL existing scripts for up or down the database version\n// from the current version until the $version number;\n// If the version number is not specified migrate until the last database version\n$migration-\u003eupdate($version = null);\n```\n\nThe Migration object controls the database version.\n\n### Creating a version control in your project\n\n```php\n\u003c?php\n// Register the Database or Databases can handle that URI:\n\\ByJG\\DbMigration\\Migration::registerDatabase(\\ByJG\\DbMigration\\Database\\MySqlDatabase::class);\n\n// Create the Migration instance\n$migration = new \\ByJG\\DbMigration\\Migration($connectionUri, '.');\n\n// This command will create the version table in your database\n$migration-\u003ecreateVersion();\n```\n\n### Getting the current version\n\n```php\n\u003c?php\n$migration-\u003egetCurrentVersion();\n```\n\n### Add Callback to control the progress\n\n```php\n\u003c?php\n$migration-\u003eaddCallbackProgress(function ($command, $version, $fileInfo) {\n    echo \"Doing Command: $command at version $version - ${fileInfo['description']}, ${fileInfo['exists']}, ${fileInfo['file']}, ${fileInfo['checksum']}\\n\";\n});\n```\n\n### Getting the Db Driver instance\n\n```php\n\u003c?php\n$migration-\u003egetDbDriver();\n```\n\nTo use it, please visit: [https://github.com/byjg/anydataset-db](https://github.com/byjg/anydataset-db)\n\n### Avoiding Partial Migration (not available for MySQL)\n\nA partial migration is when the migration script is interrupted in the middle of the process due to an error or a manual interruption.\n\nThe migration table will be with the status `partial up` or `partial down` and it needs to be fixed manually before be able to migrate again. \n\nTo avoid this situation you can specify the migration will be run in a transactional context. \nIf the migration script fails, the transaction will be rolled back and the migration table will be marked as `complete` and \nthe version will be the immediately previous version before the script that causes the error.\n\nTo enable this feature you need to call the method `withTransactionEnabled` passing `true` as parameter:\n\n```php\n\u003c?php\n$migration-\u003ewithTransactionEnabled(true);\n```\n\n**NOTE: This feature isn't available for MySQL as it doesn't support DDL commands inside a transaction.**\nIf you use this method with MySQL the Migration will ignore it silently. \nMore info: [https://dev.mysql.com/doc/refman/8.0/en/cannot-roll-back.html](https://dev.mysql.com/doc/refman/8.0/en/cannot-roll-back.html)\n\n## Tips on writing SQL migrations for Postgres\n\n### On creating triggers and SQL functions\n\n```sql\n-- DO\nCREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$\n    BEGIN\n        -- Check that empname and salary are given\n        IF NEW.empname IS NULL THEN\n            RAISE EXCEPTION 'empname cannot be null'; -- it doesn't matter if these comments are blank or not\n        END IF; --\n        IF NEW.salary IS NULL THEN\n            RAISE EXCEPTION '% cannot have null salary', NEW.empname; --\n        END IF; --\n\n        -- Who works for us when they must pay for it?\n        IF NEW.salary \u003c 0 THEN\n            RAISE EXCEPTION '% cannot have a negative salary', NEW.empname; --\n        END IF; --\n\n        -- Remember who changed the payroll when\n        NEW.last_date := current_timestamp; --\n        NEW.last_user := current_user; --\n        RETURN NEW; --\n    END; --\n$emp_stamp$ LANGUAGE plpgsql;\n\n\n-- DON'T\nCREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$\n    BEGIN\n        -- Check that empname and salary are given\n        IF NEW.empname IS NULL THEN\n            RAISE EXCEPTION 'empname cannot be null';\n        END IF;\n        IF NEW.salary IS NULL THEN\n            RAISE EXCEPTION '% cannot have null salary', NEW.empname;\n        END IF;\n\n        -- Who works for us when they must pay for it?\n        IF NEW.salary \u003c 0 THEN\n            RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;\n        END IF;\n\n        -- Remember who changed the payroll when\n        NEW.last_date := current_timestamp;\n        NEW.last_user := current_user;\n        RETURN NEW;\n    END;\n$emp_stamp$ LANGUAGE plpgsql;\n```\n\nSince the `PDO` database abstraction layer cannot run batches of SQL statements,\nwhen `byjg/migration` reads a migration file it has to split up the whole contents of the SQL\nfile at the semicolons, and run the statements one by one. However, there is one kind of\nstatement that can have multiple semicolons in-between its body: functions.\n\nIn order to be able to parse functions correctly, `byjg/migration` 2.1.0 started splitting migration\nfiles at the `semicolon + EOL` sequence instead of just the semicolon. This way, if you append an empty\ncomment after every inner semicolon of a function definition `byjg/migration` will be able to parse it.\n\nUnfortunately, if you forget to add any of these comments the library will split the `CREATE FUNCTION` statement in\nmultiple parts and the migration will fail.\n\n### Avoid the colon character (`:`)\n\n```sql\n-- DO\nCREATE TABLE bookings (\n  booking_id UUID PRIMARY KEY,\n  booked_at  TIMESTAMPTZ NOT NULL CHECK (CAST(booked_at AS DATE) \u003c= check_in),\n  check_in   DATE NOT NULL\n);\n\n\n-- DON'T\nCREATE TABLE bookings (\n  booking_id UUID PRIMARY KEY,\n  booked_at  TIMESTAMPTZ NOT NULL CHECK (booked_at::DATE \u003c= check_in),\n  check_in   DATE NOT NULL\n);\n```\n\nSince `PDO` uses the colon character to prefix named parameters in prepared statements, its use will trip it\nup in other contexts.\n\nFor instance, PostgreSQL statements can use `::` to cast values between types. On the other hand `PDO` will\nread this as an invalid named parameter in an invalid context and fail when it tries to run it.\n\nThe only way to fix this inconsistency is avoiding colons altogether (in this case, PostgreSQL also has an alternative\n syntax: `CAST(value AS type)`).\n\n### Use an SQL editor\n\nFinally, writing manual SQL migrations can be tiresome, but it is significantly easier if\nyou use an editor capable of understanding the SQL syntax, providing autocomplete,\nintrospecting your current database schema and/or autoformatting your code.\n\n## Handling different migration inside one schema\n\nIf you need to create different migration scripts and version inside the same schema it is possible\nbut is too risky and I **do not** recommend at all.\n\nTo do this, you need to create different \"migration tables\" by passing the parameter to the constructor.\n\n```php\n\u003c?php\n$migration = new \\ByJG\\DbMigration\\Migration(\"db:/uri\", \"/path\", true, \"NEW_MIGRATION_TABLE_NAME\");\n```\n\nFor security reasons, this feature is not available at command line, but you can use the environment variable\n`MIGRATION_VERSION` to store the name.\n\nWe really recommend do not use this feature. The recommendation is one migration for one schema.\n\n## Running Unit tests\n\nBasic unit tests can be running by:\n\n```bash\nvendor/bin/phpunit\n```\n\n## Running database tests\n\nRun integration tests require you to have the databases up and running. We provided a basic `docker-compose.yml` and you\ncan use to start the databases for test.\n\n### Running the databases\n\n```bash\ndocker-compose up -d postgres mysql mssql\n```\n\n### Run the tests\n\n```bash\nvendor/bin/phpunit\nvendor/bin/phpunit tests/SqliteDatabase*\nvendor/bin/phpunit tests/MysqlDatabase*\nvendor/bin/phpunit tests/PostgresDatabase*\nvendor/bin/phpunit tests/SqlServerDblibDatabase*\nvendor/bin/phpunit tests/SqlServerSqlsrvDatabase*\n```\n\nOptionally you can set the host and password used by the unit tests\n\n```bash\nexport MYSQL_TEST_HOST=localhost     # defaults to localhost\nexport MYSQL_PASSWORD=newpassword    # use '.' if want have a null password\nexport PSQL_TEST_HOST=localhost      # defaults to localhost\nexport PSQL_PASSWORD=newpassword     # use '.' if want have a null password\nexport MSSQL_TEST_HOST=localhost     # defaults to localhost\nexport MSSQL_PASSWORD=Pa55word\nexport SQLITE_TEST_HOST=/tmp/test.db      # defaults to /tmp/test.db\n```\n\n## Related Projects\n\n* [Micro ORM](https://github.com/byjg/micro-orm)\n* [Anydataset](https://github.com/byjg/anydataset)\n* [PHP Rest Template](https://github.com/byjg/php-rest-template)\n* [USDocker](https://github.com/usdocker/usdocker)\n\n## Dependencies\n\n```mermaid\nflowchart TD\n    byjg/migration --\u003e byjg/anydataset-db\n    byjg/migration --\u003e ext-pdo\n```\n\n\n----\n[Open source ByJG](http://opensource.byjg.com)","funding_links":["https://github.com/sponsors/byjg"],"categories":["PHP"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbyjg%2Fphp-migration","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbyjg%2Fphp-migration","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbyjg%2Fphp-migration/lists"}