• Stars
    star
    157
  • Rank 237,749 (Top 5 %)
  • Language
    PHP
  • License
    MIT License
  • Created over 8 years ago
  • Updated 27 days ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Simple library writen in PHP without framework dependancy for database version control. Supports Sqlite, MySql, Sql Server and Postgres

Database Migrations PHP

Opensource ByJG GitHub source GitHub license GitHub release Scrutinizer Code Quality Build Status

Features

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.

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.

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

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?

Because of that this is an agnostic project (independent of framework and Programming Language) and use pure and native SQL commands for migrate your database.

Installing

PHP Library

If you want to use only the PHP Library in your project:

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

composer require "byjg/migration-cli":"4.1.*"

Please visit byjg/migration-cli to get more informations about Migration CLI.

Supported databases

Database Driver Connection String
Sqlite pdo_sqlite sqlite:///path/to/file
MySql/MariaDb pdo_mysql mysql://username:password@hostname:port/database
Postgres pdo_pgsql pgsql://username:password@hostname:port/database
Sql Server pdo_dblib, pdo_sysbase Linux dblib://username:password@hostname:port/database
Sql Server pdo_sqlsrv Windows sqlsrv://username:password@hostname:port/database

How It Works?

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.

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 directory scripts is :

 <root dir>
     |
     +-- base.sql
     |
     +-- /migrations
              |
              +-- /up
                   |
                   +-- 00001.sql
                   +-- 00002.sql
              +-- /down
                   |
                   +-- 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. For example: 00001.sql is the script for move the database from version '2' to '1'. The "down" folder is optional.

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.

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.

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.

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. 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.

Using the PHP API and Integrate it into your projects

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.

See an example:

<?php
// Create the Connection URI
// 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, '.');

// Add a callback progress function to receive info from the execution
$migration->addCallbackProgress(function ($action, $currentVersion, $fileInfo) {
    echo "$action, $currentVersion, ${fileInfo['description']}\n";
});

// Restore the database using the "base.sql" script
// and run ALL existing scripts for up the database version to the latest version
$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
$migration->update($version = null);

The Migration object controls the database version.

Creating a version control in your project

<?php
// 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, '.');

// This command will create the version table in your database
$migration->createVersion();

Getting the current version

<?php
$migration->getCurrentVersion();

Add Callback to control the progress

<?php
$migration->addCallbackProgress(function ($command, $version, $fileInfo) {
    echo "Doing Command: $command at version $version - ${fileInfo['description']}, ${fileInfo['exists']}, ${fileInfo['file']}, ${fileInfo['checksum']}\n";
});

Getting the Db Driver instance

<?php
$migration->getDbDriver();

To use it, please visit: https://github.com/byjg/anydataset-db

Tips on writing SQL migrations for Postgres

Rely on explicit transactions

-- DO
BEGIN;

ALTER TABLE 1;
UPDATE 1;
UPDATE 2;
UPDATE 3;
ALTER TABLE 2;

COMMIT;


-- DON'T
ALTER TABLE 1;
UPDATE 1;
UPDATE 2;
UPDATE 3;
ALTER TABLE 2;

It is generally desirable to wrap migration scripts inside a BEGIN; ... COMMIT; block. This way, if any of the inner statements fail, none of them are committed and the database does not end up in an inconsistent state.

Mind that in case of a failure byjg/migration will always mark the migration as partial and warn you when you attempt to run it again. The difference is that with explicit transactions you know that the database cannot be in an inconsistent state after an unexpected failure.

On creating triggers and SQL functions

-- DO
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
    BEGIN
        -- Check that empname and salary are given
        IF NEW.empname IS NULL THEN
            RAISE EXCEPTION 'empname cannot be null'; -- it doesn't matter if these comments are blank or not
        END IF; --
        IF NEW.salary IS NULL THEN
            RAISE EXCEPTION '% cannot have null salary', NEW.empname; --
        END IF; --

        -- Who works for us when they must pay for it?
        IF NEW.salary < 0 THEN
            RAISE EXCEPTION '% cannot have a negative salary', NEW.empname; --
        END IF; --

        -- Remember who changed the payroll when
        NEW.last_date := current_timestamp; --
        NEW.last_user := current_user; --
        RETURN NEW; --
    END; --
$emp_stamp$ LANGUAGE plpgsql;


-- DON'T
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
    BEGIN
        -- Check that empname and salary are given
        IF NEW.empname IS NULL THEN
            RAISE EXCEPTION 'empname cannot be null';
        END IF;
        IF NEW.salary IS NULL THEN
            RAISE EXCEPTION '% cannot have null salary', NEW.empname;
        END IF;

        -- Who works for us when they must pay for it?
        IF NEW.salary < 0 THEN
            RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
        END IF;

        -- Remember who changed the payroll when
        NEW.last_date := current_timestamp;
        NEW.last_user := current_user;
        RETURN NEW;
    END;
$emp_stamp$ LANGUAGE plpgsql;

Since the PDO database abstraction layer cannot run batches of SQL statements, when byjg/migration reads a migration file it has to split up the whole contents of the SQL file at the semicolons, and run the statements one by one. However, there is one kind of statement that can have multiple semicolons in-between its body: functions.

In order to be able to parse functions correctly, byjg/migration 2.1.0 started splitting migration files at the semicolon + EOL sequence instead of just the semicolon. This way, if you append an empty comment after every inner semicolon of a function definition byjg/migration will be able to parse it.

Unfortunately, if you forget to add any of these comments the library will split the CREATE FUNCTION statement in multiple parts and the migration will fail.

Avoid the colon character (:)

-- DO
CREATE TABLE bookings (
  booking_id UUID PRIMARY KEY,
  booked_at  TIMESTAMPTZ NOT NULL CHECK (CAST(booked_at AS DATE) <= check_in),
  check_in   DATE NOT NULL
);


-- DON'T
CREATE TABLE bookings (
  booking_id UUID PRIMARY KEY,
  booked_at  TIMESTAMPTZ NOT NULL CHECK (booked_at::DATE <= check_in),
  check_in   DATE NOT NULL
);

Since PDO uses the colon character to prefix named parameters in prepared statements, its use will trip it up in other contexts.

For instance, PostgreSQL statements can use :: to cast values between types. On the other hand PDO will read this as an invalid named parameter in an invalid context and fail when it tries to run it.

The only way to fix this inconsistency is avoiding colons altogether (in this case, PostgreSQL also has an alternative syntax: CAST(value AS type)).

Use an SQL editor

Finally, writing manual SQL migrations can be tiresome, but it is significantly easier if 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.

To do this, you need to create different "migration tables" by passing the parameter to the constructor.

<?php
$migration = new \ByJG\DbMigration\Migration("db:/uri", "/path", true, "NEW_MIGRATION_TABLE_NAME");

For security reasons, this feature is not available at command line, but you can use the environment variable MIGRATION_VERSION to store the name.

We really recommend do not use this feature. The recommendation is one migration for one schema.

Running Unit tests

Basic unit tests can be running by:

vendor/bin/phpunit

Running database tests

Run integration tests require you to have the databases up and running. We provided a basic docker-compose.yml and you can use to start the databases for test.

Running the databases

docker-compose up -d postgres mysql mssql

Run the tests

vendor/bin/phpunit
vendor/bin/phpunit tests/SqliteDatabase*
vendor/bin/phpunit tests/MysqlDatabase*
vendor/bin/phpunit tests/PostgresDatabase*
vendor/bin/phpunit tests/SqlServerDblibDatabase*
vendor/bin/phpunit tests/SqlServerSqlsrvDatabase*

Optionally you can set the host and password used by the unit tests

export MYSQL_TEST_HOST=localhost     # defaults to localhost
export MYSQL_PASSWORD=newpassword    # use '.' if want have a null password
export PSQL_TEST_HOST=localhost      # defaults to localhost
export PSQL_PASSWORD=newpassword     # use '.' if want have a null password
export MSSQL_TEST_HOST=localhost     # defaults to localhost
export MSSQL_PASSWORD=Pa55word
export SQLITE_TEST_HOST=/tmp/test.db      # defaults to /tmp/test.db

Related Projects


Open source ByJG

More Repositories

1

php-swagger-test

A set of tools for testing your REST calls based on the swagger documentation using PHPUnit
PHP
98
star
2

docker-easy-haproxy

Discover services and create dynamically the haproxy.cfg based on the labels defined in docker containers or from a simple static Yaml
Python
55
star
3

jquery-sse

jQuery Plugin for Server-Sent Events (SSE) EventSource Polyfill
JavaScript
52
star
4

php-jwt-session

JwtSession is a PHP session replacement. Instead of use FileSystem, just use JWT TOKEN. The implementation follow the SessionHandlerInterface.
PHP
49
star
5

docker-nginx-extras

Nginx extended version: provides a version of nginx with the standard modules, plus extra features and modules
Dockerfile
27
star
6

php-micro-orm

A micro framework for create a very simple decoupled ORM (sqlite, mysql, postgres, sqlserver)
PHP
17
star
7

php-rest-reference-architecture

Boilerplate project template for create RESTFul services with docker and database integrated
PHP
16
star
8

php-imageutil

A wrapper collection for GD library in PHP.
PHP
13
star
9

php-jinja

Jinja for PHP is a PHP implementation of the [Jinja2](http://jinja.pocoo.org/) template engine.
PHP
12
star
10

docker-opencart

Opencart docker image prepared for production environment with volume persistent
Dockerfile
12
star
11

php-anydataset

Anydataset Core Module. Anydataset is an agnostic data source abstraction layer in PHP.
PHP
11
star
12

xmlnuke

This project is deprecated and split into a several projects. Please see: https://opensource.byjg.com/
PHP
10
star
13

php-cache-engine

A multi-purpose cache engine PSR-6 and PSR-16 implementation with several drivers.
PHP
9
star
14

php-singleton-pattern

A lightweight PHP implementation of the Design Pattern Singleton using trait.
PHP
8
star
15

php-migration-cli

Command Line Interface for Database Migration
PHP
8
star
16

b8

b8 is a statistical ("Bayesian") spam filter implemented in PHP
PHP
8
star
17

usdocker

A collection of Useful Scripts for Docker
Perl
7
star
18

php-anydataset-nosql

A NoSql abstraction dataset. Anydataset is an agnostic data source abstraction layer in PHP.
PHP
7
star
19

automate

Native bash script for automate tasks in a multiple servers
Shell
7
star
20

docker-php

A complete and small PHP Docker image based on Alpine Linux.
Python
6
star
21

k8s-hello-node

Simple Hello World in Node.JS Express to use as sample image and K8s
JavaScript
6
star
22

codegenx

Code generator highly flexible and extensible based on Torque XML and XSL transformations
C#
6
star
23

php-anydataset-db

Anydataset Database Relational abstraction. Anydataset is an agnostic data source abstraction layer in PHP.
PHP
5
star
24

php-daemonize

Transform any class in a *nix daemon process or cron job without changes or refactoring.
PHP
5
star
25

php-mailwrapper

A lightweight wrapper for send mail. The interface is tottaly decoupled from the sender. The sender availables are: PHP Mailer, AWS SES Api, Mandril Api.
PHP
5
star
26

php-webrequest

A lightweight and highly customized CURL wrapper for making RESt calls and a wrapper for call dynamically SOAP requests.
PHP
5
star
27

php-phpthread

Polyfill Implementation of Threads in PHP. This class supports both FORK process and native Threads using ZTS compilation.
PHP
5
star
28

yaj

Yet another jQuery Replacement (lightweight and polyfill)
JavaScript
5
star
29

php-anydataset-text

Anydataset Text File abstraction. Anydataset is an agnostic data source abstraction layer in PHP.
PHP
4
star
30

php-serializer

Serialize any object into array and bind any data structure to an object/array; Can (de)serialize into JSON or XML.
PHP
4
star
31

php-jwt-wrapper

A very simple wrapper for create, encode, decode JWT Tokens and abstract the PHP JWT Component
PHP
4
star
32

docker-bash-scripts

Here you can find a collection of docker bash scripts to automate some tasks
Shell
4
star
33

php-config

A very basic and minimalist component for config management and dependency injection.
PHP
4
star
34

activemq-easy-config

Create a Network of Brokers using a simple tool to make the process easier.
Python
3
star
35

docker-static-httpserver

Really minimal HTTP Server for static files written in GO
CSS
3
star
36

php-uri

An implementation of PSR UriInterface
PHP
3
star
37

php-authuser

A simple and customizable class for enable user authentication inside your application. It is available on XML files, Relational Databases and Moodle.
PHP
3
star
38

php-restserver

Create RESTFull services with different and customizable output handlers (JSON, XML, Html, etc.). Auto-Generate routes from swagger.json definition.
PHP
3
star
39

omnipay-komerci

Komerci WebService (Rede) driver for the Omnipay PHP payment processing library
PHP
3
star
40

php-shortid

Create short string IDs from numbers
PHP
3
star
41

php-anydataset-json

Anydataset JSON abstraction. Anydataset is an agnostic data source abstraction layer in PHP.
PHP
3
star
42

yaj-sse

A lightweigth Yaj Plugin for Server-Sent Events (SSE) EventSource Polyfill
JavaScript
3
star
43

wireguard-ui-setup

Quick tool to setup wireguard-ui
Shell
2
star
44

anydataset-sparql

Anydataset SparQL abstraction. Anydataset is an agnostic data source abstraction layer in PHP.
PHP
2
star
45

k8s-ci

A comprehensive ubuntu image with K8s and cloud tools to be used in CI/CD
Dockerfile
2
star
46

docker-openssh-server

Docker OpenSSH Server Image / Bastion
Shell
2
star
47

php-anydataset-array

Anydataset Array abstraction. Anydataset is an agnostic data source abstraction layer in PHP.
PHP
2
star
48

php-convert

A lightweight utility for string conversion between text from UTF8 to a lot of formats and vice-versa.
PHP
2
star
49

php-fonemabr

O Fonema BR tem por objetivo criar uma simplificação de palavras de tal forma que erros de ortografia e vogais não interfiram na busca
PHP
2
star
50

php-anydataset-xml

Anydataset Xml abstraction. Anydataset is an agnostic data source abstraction layer in PHP
PHP
2
star
51

xmlnuke-php5-installer

XMLNuke PHP5 Installer is a Web Interface to aid the XMLNuke install process.
PHP
1
star
52

restserver-swagger

Enable to create RESTFull services with strong model schema. The routes are automatically created from a swagger.json file.
PHP
1
star
53

php-xmlutil

A utility class to make easy work with XML in PHP.
PHP
1
star
54

php-wordnumber

Write float number in a word number.
PHP
1
star
55

php-account-statements

Very simple component to manage Account Statements in PHP
PHP
1
star
56

swarm_tf

Create a Swarm Cluster with a Python script wrapping the Terraform HCL
Python
1
star
57

docker-sonar-scanner

A Docker Image for SonarQube Scanner
Shell
1
star
58

exemplos-aula

Exemplos de aula de HTML / JavaScript
HTML
1
star
59

docker-node-multi-version

Docker Image hosting multiple node versions
Shell
1
star
60

php-crypto

A generic repository for implement cryptographic algorithms with a customizable strong key generator.
PHP
1
star
61

xmlnuke-cmdline

Enables run an existing XMLNuke module from the command line without to make changes in your module.
PHP
1
star
62

docker-htpasswd-volume

Shell
1
star