PHPackages                             lucinda/migrations - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. lucinda/migrations

ActiveLibrary[Database &amp; ORM](/categories/database)

lucinda/migrations
==================

Lightweight PHP7.1 library for performing cross-environment migrations

v2.0.4(3y ago)015.0k↑75%12MITPHPPHP ^8.1

Since Feb 20Pushed 3y ago1 watchersCompare

[ Source](https://github.com/aherne/migration)[ Packagist](https://packagist.org/packages/lucinda/migrations)[ RSS](/packages/lucinda-migrations/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (2)Versions (12)Used By (2)

Lucinda Migrations
==================

[](#lucinda-migrations)

Table of contents:

- [About](#about)
    - [Methodology Used](#methodology-used)
    - [Implementation](#implementation)
- [Installation](#installation)
    - [Setting Cache](#setting-cache)
    - [Execution](#execution)
- [Console Commands](#console-commands)
    - [generate](#how-does-generate-command-work)
    - [migrate](#how-does-migrate-command-work)
    - [up](#how-does-up-command-work)
    - [down](#how-does-down-command-work)
- [Reference Guide](#reference-guide)
    - [Cache](#cache)
    - [Script](#script)
    - [Result](#result)
    - [Status](#status)
    - [Wrapper](#wrapper)
    - [ConsoleExecutor](#consoleexecutor)

About
-----

[](#about)

This API serves as platform able to automate **data migration** between development environments (eg: changes in database structure) useful in systems powered by continuous integration/delivery. Modeled on Symfony [DoctrineMigrationsBundle](https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html) operations, it performs its task in a starkly different way:

- like other Lucinda libraries, it is built with simplicity, flexibility and efficiency in mind (thus light weight as an effect). It is completely independent, not bundled to any library or framework, thus can be used in *any* PHP context whether Lucinda Framework is used or not
- it works like a skeleton onto which various migration patterns need to be built on (eg: SQL tables migration). This means it doesn't assume by default that database is to be migrated: *anything subject of being synchronized between development environments falls subject to migrations*

### Methodology Used

[](#methodology-used)

When developing this API I asked myself: *what do all migrations, regardless of what they migrate, have in common*? Following aspects came to my mind:

- a **migration** should a class able to execute one or more related operations on multiple environments
- class operations should be *transactional*, meaning it must have COMMIT/ROLLBACK actions
- API needs an ability to *generate* a migration class on developer request
- developer must *program* generated migration (eg: with queries)
- API needs an ability to *find* and *execute* migrations.
- API needs an ability to *track* migrations progress across environments
- tracking requires assigning every migration class to a status: PENDING, FAILED, PASSED
- once developer executed migrations on his environment and made sure all is fine, he must *commit* it (eg: to GIT)
- other developers pull new changes, see that new migrations were added, then execute migrations to keep their environment updated
- continuous integration/deployment system (eg: TeamCity) will automatically execute migrations on every build, thus keeping all environments up to date

This API only performs the logistics for API-level operations above without taking any assumption on:

- what is the subject of migrations (eg: is it SQL table related?)
- what is the storage medium of tracking migrations progress (eg: is it SQL table?)

Migration operations supported will be:

- generating migration
- running all migrations (whose status is PENDING or FAILED), which equates to a global commit on each
- committing individual migration (whose status is PENDING or FAILED)
- rolling back individual migration (whose status is PASSED)

Note that unlike DoctrineMigrationsBundle, **diff** and **dump-schema** operations will not be supported by default because they make both *subject and implementation assumption* (that SQL database is subject and that DAO implementation uses ORM model).

### Implementation

[](#implementation)

True to its goal of creating a *migration skeleton* for specialization to be built upon, this API only defines common logistics:

- [Cache](#cache): blueprint for structure where migration progress is saved and tracked
- [Script](#script): blueprint for a migration class supporting commit/rollback operations
- [Result](#result): class that implements results of a migration (class operation) execution
    - [Status](#status): enum that collects possible migration statuses (PENDING, PASSED, FAILED)
- [Wrapper](#wrapper): class that binds all four above in order to find and execute migration operations
- [ConsoleExecutor](#consoleexecutor): class that envelopes [Wrapper](#wrapper) to display migration operation results on console.

API is fully PSR-4 compliant, only requiring PHP 8.1+ interpreter and [Console Table API](https://github.com/aherne/console_table) (for displaying migration results on console). All classes inside belong to namespace **Lucinda\\Migration**! To quickly see how it works, check:

- **installation**: downloading API using composer, creating folder to store migrations into
- **setting cache**: setting up a [Cache](#cache) that stores migration progress
- **execution**: using [Wrapper](#wrapper) to run migrations or [ConsoleExecutor](#consoleexecutor) to display results in console and one or more migration [Script](#script)s

To insure reliability, API has been fully unit tested using [Unit Testing API](https://github.com/aherne/unit-testing), as you can see in **tests** folder. To run unit tests by yourselves, run these commands:

```
cd vendor/lucinda/migrations
php test.php
```

Installation
------------

[](#installation)

To install this API, go to your project root and run:

```
composer require lucinda/migrations
```

Then create a **migrations.php** script that will execute migrations:

```
require(__DIR__."/vendor/autoload.php");

// defines folder to store migrations and creates it if not exists
$folder = "migrations";
if (!file_exists($folder)) {
  mkdir($folder);
}

// TODO: instance a Lucinda\Migration\Cache into $cache variable

// run migrations based on console input
$executor = new Lucinda\Migration\ConsoleExecutor($folder, $cache);
$executor->execute((isset($argv[1])?$argv[1]:"migrate"), (isset($argv[2])?$argv[2]:""));
```

### Setting Cache

[](#setting-cache)

Implementing migration [Cache](#cache) is outside the scope of skeleton API, which makes no assumption on what is the subject of migrations or how should cache be stored (eg: could be MySQL, could be Amazon DynamoDB).

An example of a [Cache](#cache) implementation binding to [SQL Data Access API ](https://github.com/aherne/php-sql-data-access-api), using a MySQL table to store info:

```
class TableCache implements \Lucinda\Migration\Cache
{
    private $tableName;

    public function __construct(string $tableName)
    {
        $this->tableName = $tableName;
    }

    public function exists(): bool
    {
        return !empty(SQL("SHOW TABLES LIKE '".$this->tableName."'")->toRow());
    }

    public function create(): void
    {
        SQL("
        CREATE TABLE ".$this->tableName."
        (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT,
        class_name VARCHAR(255) NOT NULL,
        is_successful BOOLEAN NOT NULL DEFAULT TRUE,
        date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY(id),
        UNIQUE(class_name)
        ) Engine=INNODB");
    }

    public function read(): array
    {
        return SQL("SELECT class_name, IF(is_successful=0,2,3) AS status FROM ".$this->tableName."")->toMap("class_name", "status");
    }

    public function add(string $className, int $statusCode): void
    {
        $isSuccessful = ($statusCode==\Lucinda\Migration\Status::PASSED);
        $results = SQL("UPDATE ".$this->tableName." SET is_successful=:status, date=NOW() WHERE class_name=:name", [
            ":status"=>$isSuccessful,
            ":name"=>$className
        ])->getAffectedRows();
        if ($results == 0) {
            SQL("INSERT INTO ".$this->tableName." SET is_successful=:status, class_name=:name", [
                ":status"=>$isSuccessful,
                ":name"=>$className
            ]);
        }
    }

    public function remove(string $className): void
    {
        SQL("DELETE FROM ".$this->tableName." WHERE class_name=:name", [":name"=>$className]);
    }
}
```

Code for SQL function used above:

```
function SQL(string $query, array $boundParameters = array()): \Lucinda\SQL\StatementResults
{
    $preparedStatement = \Lucinda\SQL\ConnectionSingleton::getInstance()->createPreparedStatement();
    $preparedStatement->prepare($query);
    return $preparedStatement->execute($boundParameters);
}
```

### Execution

[](#execution)

Once a [Cache](#cache) is implemented, you can complete migration script and perform migrations. Example using the two classes in previous section:

```
require(__DIR__."/vendor/autoload.php");

// loads dependencies that bind Cache and Script to SQL Data Access API
require(__DIR__."/TableCache.php");
require(__DIR__."/SQL.php");

// defines folder to store migrations and creates it if not exists
$folder = "migrations";
if (!file_exists($folder)) {
  mkdir($folder);
}

// sets up Lucinda SQL Data Access API for current development environment based on XML
new Lucinda\SQL\Wrapper(simplexml_load_file("xml/servers.xml"), getenv("ENVIRONMENT"));

// run migrations based on console input, saving cache to SQL table "migrations"
$executor = new Lucinda\Migration\ConsoleExecutor($folder, new TableCache("migrations"));
$executor->execute((isset($argv[1])?$argv[1]:"migrate"), (isset($argv[2])?$argv[2]:""));
```

Above example will work if:

- an environment variable called "ENVIRONMENT" is set already, whose value is your development environment name
- mysql connection credentials are set for current development environment as required in [configuration](https://github.com/aherne/php-sql-data-access-api#configuration) section of SQL Data Access API
- current database user has full rights on target schema (incl. CREATE). If not, run code @ *create* method manually!

You can now go to folder where API was installed and execute *generate* commands, go to **migrations** folder and fill up/down methods accordingly. Once at least one migration [Script](#script) is filled, you will be able to execute migrations from console.

To maximize flexibility, developers that do not want to use console output provided by [ConsoleExecutor](#consoleexecutor) can work with [Wrapper](#wrapper) directly and handle output by themselves.

Console Commands
----------------

[](#console-commands)

API allows following console commands:

Argument#1Argument#2DescriptiongenerateGenerates a template [Script](#script) class in **migrations** folder outputs its namemigrate(default) Loops through all [Script](#script) classes in **migrations** folder and executes their *up* methodup(classname)Runs *up* method for [Script](#script) identified by its class namedown(classname)Runs *down* method for [Script](#script) identified by its class name### How does generate command work

[](#how-does-generate-command-work)

This will generate a migration [Script](#script) class in **migrations** folder. Open that class and fill *up* and *down* methods with queries, as in this example:

```
class Version20210205105634 implements \Lucinda\Migration\Script
{
  public function up(): void
  {
    SQL("
      CREATE TABLE test(
      id INT UNSIGNED NOT NULL AUTO_INCREMENT,
      primary key(id)
      ) ENGINE=INNODB
      ")
  }
  public function up(): void
  {
    SQL("DROP TABLE test");
  }
}
```

Example:

```
> php migrations.php generate
```

This will output name of [Script](#script) created in **migrations** folder whose up/down methods you must fill.

### How does migrate command work

[](#how-does-migrate-command-work)

When *migrate* is ran, API will locate all [Script](#script)s classes in **migrations** folder and match each to [Cache](#cache):

- if found in [Cache](#cache) and migration [Status](#status) is PASSED: *up* is skipped (since it ran already)
- otherwise: *up* is ran

If *up* throws no [Throwable](https://www.php.net/manual/en/class.throwable.php), migration is saved in [Cache](#cache) with PASSED status. Otherwise it's saved with FAILED status and [Throwable](https://www.php.net/manual/en/class.throwable.php) message will be shown to caller in results summary.

Example:

```
> php migrations.php migrate
```

The end will be a console table with following columns:

- [Script](#script) class name
- [Status](#status) of *up* method execution
- error message, if [Script](#script) threw a [Throwable](https://www.php.net/manual/en/class.throwable.php)

### How does up command work

[](#how-does-up-command-work)

When *up* (commit) command is ran, API will first check if [Script](#script) name received as 2nd argument exists on disk in **migrations** folder:

- if found on disk
    - if found in [Cache](#cache) and migration [Status](#status) is PASSED: *up* is skipped (since it ran already)
    - otherwise: *up* is ran
- otherwise: program exits with error

Example:

```
> php migrations.php up Version20210205105634
```

The end result will be a console table with following columns:

- [Status](#status) of [Script](#script)'s *up* method execution
- error message, if [Script](#script) threw a [Throwable](https://www.php.net/manual/en/class.throwable.php)

### How does down command work

[](#how-does-down-command-work)

When *down* (rollback) command is ran, API will first check if [Script](#script) name received as 2nd argument exists on disk in **migrations** folder:

- if found on disk
    - if found in [Cache](#cache) and migration [Status](#status) is PASSED: *down* is ran
    - otherwise: *down* is skipped
- otherwise: program exits with error

Example:

```
> php migrations.php down Version20210205105634
```

The end result will be a console table with following columns:

- [Status](#status) of [Script](#script)'s *up* method execution
- error message, if [Script](#script) threw a [Throwable](https://www.php.net/manual/en/class.throwable.php)

Reference Guide
---------------

[](#reference-guide)

This guide includes all classes, enums and interfaces used by API.

### Cache

[](#cache)

Interface [Lucinda\\Migration\\Cache](https://github.com/aherne/migration/blob/master/src/Cache.php) defines operations a cache that saves migration [Lucinda\\Migration\\Script](https://github.com/aherne/migration/blob/master/src/Script.php)s execution progress must implement. It defines following methods:

MethodArgumentsReturnsDescriptionexistsboolChecks cache exists physicallycreatevoidCreates cache if not existsreadstring\[[Status](#status)\]Gets [Script](#script) classes from cache by runtime [Status](#status)addstring, [Status](#status)voidInserts or updates [Script](#script) in cache by class name and runtime [Status](#status)removestringvoidRemoves [Script](#script) from cache by class name### Script

[](#script)

Interface [Lucinda\\Migration\\Script](https://github.com/aherne/migration/blob/master/src/Script.php) defines operations a migration script must implement, corresponding to following methods:

MethodArgumentsReturnsDescriptionupCommits migration to destinationdownRolls back migration from destinationIn case an error has occurred, methods are expected to bubble a [Throwable](https://www.php.net/manual/en/class.throwable.php), which will inform API that they ended with an error. Following recommendations apply:

- if you need multiple operations inside up/down, they MUST be transactioned (to insure data integrity)
- ideally, a migration SHOULD perform a single operation (to prevent conflicts)

### Result

[](#result)

Class [Lucinda\\Migration\\Result](https://github.com/aherne/migration/blob/master/src/Result.php) encapsulates results of a [Lucinda\\Migration\\Script](https://github.com/aherne/migration/blob/master/src/Script.php) execution. Following public methods are relevant for developers:

MethodArgumentsReturnsDescriptiongetClassNamestringGets [Script](#script) class namegetStatus[Status](#status)Gets status code associated with results of up/down commandgetThrowable[Throwable](https://www.php.net/manual/en/class.throwable.php)Gets throwable class name if [Status](#status) is FAILEDGenerally you won't need to use this class unless you're building your own results displayer on top of [Wrapper](#wrapper)!

### Status

[](#status)

Enum [Lucinda\\Migration\\Status](https://github.com/aherne/migration/blob/master/src/Status.php) contains list of migration execution [Result](#result) statuses:

CaseDescriptionPENDING[Script](#script) has been scheduled for executionFAILED[Script](#script) execution has failed, bubbling a [Throwable](https://www.php.net/manual/en/class.throwable.php)PASSED[Script](#script) execution was successful### Wrapper

[](#wrapper)

Class [Lucinda\\Migration\\Wrapper](https://github.com/aherne/migration/blob/master/src/Wrapper.php) performs API task of creating, locating and executing migration [Script](#script)s, updating [Cache](#cache) as a result. Following public methods are defined:

MethodArgumentsReturnsDescription\_\_constructstring $folder, [Cache](#cache) $cacheSets up API for migration based on folder to migration filesgeneratestringGenerates a migration class ([see more](#how-does-generate-command-work))migrate[Result](#result)\[\]Executes *up* command for all [Script](#script)s found ([see more](#how-does-migrate-command-work))upstring $className[Result](#result)Executes *up* command for script identified by classname ([see more](#how-does-up-command-work))
 Throws [Exception](https://github.com/aherne/migration/blob/master/src/Exception.php) if class not found or already in PASSED statedownstring $className[Result](#result)Executes *down* command for script identified by classname ([see more](#how-does-down-command-work))
 Throws [Exception](https://github.com/aherne/migration/blob/master/src/Exception.php) if class not found or not in PASSED stateTo increase reusability, API makes no assumptions how results will be displayed so this class is strictly a model on whom various displayers may be built!

### ConsoleExecutor

[](#consoleexecutor)

Class [Lucinda\\Migration\\ConsoleExecutor](https://github.com/aherne/migration/blob/master/src/ConsoleExecutor.php) makes the assumption you will like to envelop [Wrapper](#wrapper) methods so their results are displayed on console/terminal using commands that map them ([see more](#console-commands)). It comes with following public methods:

MethodArgumentsReturnsDescription\_\_constructstring $folder, [Cache](#cache) $cacheSets up [Wrapper](#wrapper) underneathexecutestring $operation, string $classNameExecutes a [Wrapper](#wrapper) method and displays results in consoleWhen *execute* method is used:

- $operation value MUST correspond to a [Wrapper](#wrapper) method name (minus constructor)
- $className value MUST be present only IF $operation is up / down

###  Health Score

34

—

LowBetter than 77% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity23

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity68

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~67 days

Recently: every ~87 days

Total

11

Last Release

1240d ago

Major Versions

v1.0.2 → v2.0.x-dev2022-01-01

v1.0.x-dev → v2.0.12022-05-01

PHP version history (3 changes)v1.0.0PHP ^7.1

v2.0.x-devPHP ^8.1

v1.0.3PHP ^7.1|^8.0

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/3382770?v=4)[Lucian Gabriel Popescu](/maintainers/aherne)[@aherne](https://github.com/aherne)

---

Top Contributors

[![aherne](https://avatars.githubusercontent.com/u/3382770?v=4)](https://github.com/aherne "aherne (12 commits)")

---

Tags

migrations

### Embed Badge

![Health badge](/badges/lucinda-migrations/health.svg)

```
[![Health](https://phpackages.com/badges/lucinda-migrations/health.svg)](https://phpackages.com/packages/lucinda-migrations)
```

###  Alternatives

[doctrine/migrations

PHP Doctrine Migrations project offer additional functionality on top of the database abstraction layer (DBAL) for versioning your database schema and easily deploying changes to it. It is a very easy to use and a powerful tool.

4.8k204.8M440](/packages/doctrine-migrations)[doctrine/doctrine-migrations-bundle

Symfony DoctrineMigrationsBundle

4.3k177.9M537](/packages/doctrine-doctrine-migrations-bundle)[robmorgan/phinx

Phinx makes it ridiculously easy to manage the database migrations for your PHP app.

4.5k46.2M405](/packages/robmorgan-phinx)[kitloong/laravel-migrations-generator

Generates Laravel Migrations from an existing database

2.9k7.4M24](/packages/kitloong-laravel-migrations-generator)[xethron/migrations-generator

Generates Laravel Migrations from an existing database

3.3k3.3M25](/packages/xethron-migrations-generator)[davedevelopment/phpmig

Simple migrations system for php

5782.3M17](/packages/davedevelopment-phpmig)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
