Griffin is a Graph-Oriented Migration Framework for PHP

Overview

griffin

Griffin is a Graph-Oriented Migration Framework for PHP

Build Status Latest Stable Version Codecov License

TL;DR

Griffin is a generic migration framework that uses graph theory to provision anything. It plans execution based on migration dependencies and runs them in the correct order.

use FooBar\Database\Driver;
use Griffin\Migration\Container;
use Griffin\Migration\Migration;
use Griffin\Planner\Planner;
use Griffin\Runner\Runner;

$driver = new Driver(); // Pseudo Database Driver

$orders = (new Migration())
    ->withName('orders')
    ->withAssert(fn() => $driver->table->has('orders'))
    ->withUp(fn() => $driver->table->create('orders'))
    ->withDown(fn() => $driver->table->drop('orders'));

$items = (new Migration())
    ->withName('items')
    ->withDependencies(['orders'])
    ->withAssert(fn() => $driver->table->has('items'))
    ->withUp(fn() => $driver->table->create('items'))
    ->withDown(fn() => $driver->table->drop('items'));

$container = (new Container())
    ->addMigration($orders)
    ->addMigration($items);

$planner = new Planner($container);
$runner  = new Runner($planner);

$runner->up(); // create everything
$runner->down(); // destroy everything

$runner->up('items'); // create orders and items
$runner->down('orders'); // destroy orders and items

// create orders and items
// regardless the order of elements informed
$runner->up('items', 'orders');

You might want to check more examples to learn how to define migrations using Griffin.

Installation

This package uses Composer as default repository. You can install it adding the name of package in require section of composer.json, pointing to the latest stable version.

{
  "require": {
    "griffin/griffin": "^1.0"
  }
}

CLI

This package includes the Griffin framework. If you want a CLI to run your migrations, please check Griffin CLI.

Introduction

Migrations are tools to change system current state, adding (or removing) features based on previous state. Generally, they are used to create database structures from scratch, provisioning tables or columns using a step-by-step approach. There are standalone tools to run migrations, like Phinx. Also, there are other ones embedded into frameworks, like Laravel or Doctrine.

If we inspect them, they use a linear approach, where next state must migrate from current state. Migrations can be rolled back, so if we want to revert some changes, we must rollback from current state to previous state. Each migration knows how to create and destroy itself.

For example, we have three migrations A, B and C created sequentially. If our current state is A and we must migrate to C, we must execute migrations B and C, in that order, respectively. If we want to rollback from C to A, we must execute them backwards, B and A. But if you want to execute migrations A and C, because they are dependent, and ignore B for some reason, you can't. Even, if you want to rollback C and A ignoring B, you are locked.

Bringing to the world of database migrations, you can create migration Orders that creates table into schema. Right after that, other developer creates a migration called Messages without any dependency from Orders. Next, you create a migration named Items with a foreign key to Orders. Everything works fine and you deploy them to stage environment on friday.

./migrations/001_Orders.php
./migrations/002_Messages.php
./migrations/003_Items.php

On monday you find a problem with your migrations and you want to rollback. But you don't want to remove Messages table because other developer is showing the newest features to Product Owner.

And here comes Griffin.

Description

Griffin is a migration framework based on directed graphs, where each migration can be migrated and rolled back independently. Also, you can define dependencies for each migration and Griffin is responsible to plan the execution priority.

Based on provisioning tools like Puppet and Terraform, Griffin can plan execution and run it using graph theory, where each migration works like a vertice and dependencies define directed paths. Griffin searches for circular dependencies on planning and can automatically rollback changes if errors were found.

Griffin is a generic migration framework and it is not database focused. You are free to use Griffin to provisioning what needed, like directory structures, packages, crawlers and even database schemas.

Usage

Each migration must be defined using Griffin\Migration\MigrationInterface. Migrations must return its name with getName method and dependencies with getDependencies. Each migration must check if resource is created using assert method, returning a boolean as result. Also, they are responsible to create the resource using up method and to destroy using down. Griffin uses these methods to plan and run migrations.

namespace FooBar\Database\Migration;

use FooBar\Database\Driver;
use Griffin\Migration\MigrationInterface;

class Items implements MigrationInterface
{
    public function __construct(
        private Driver $driver,
    ) {}

    public function getName(): string
    {
        return self::class;
    }

    /**
     * @return string[]
     */
    public function getDependencies(): array
    {
        return [
            Order::class,
            Product::class,
        ];
    }

    public function assert(): bool
    {
        return $this->driver->table->has('items');
    }

    public function up(): void
    {
        $this->driver->table->create('items');
    }

    public function down(): void
    {
        $this->driver->table->drop('items');
    }
}

You can create objects from class Griffin\Migration\Migration, that implements Griffin\Migration\MigrationInterface and behaviors can be defined using immutable methods.

use FooBar\Database\Driver;
use Griffin\Migration\Migration;

$driver = new Driver();

$migration = (new Migration())
    ->withName('items')
    ->withDependencies(['orders', 'products'])
    ->withAssert(fn() => $driver->table->has('items'))
    ->withUp(fn() => $driver->table->create('items'))
    ->withDown(fn() => $driver->table->drop('items'));

Planning

Griffin plans your migrations execution before running them using Griffin\Planner\Planner. Every migration must be added to Griffin\Migration\Container instances and attached to planner on construction.

use FooBar\Database\Migration;
use Griffin\Migration\Container;
use Griffin\Migration\Exception as MigrationException;
use Griffin\Planner\Exception as PlannerException;
use Griffin\Planner\Planner;

$container = new Container();
$planner   = new Planner($container);

$planner->getContainer()
    ->addMigration(new Migration\Orders())
    ->addMigration(new Migration\Items())
    ->addMigration(new Migration\Products());

/** @var Griffin\Migration\Container $migrations **/

try {
    // plan up execution for every migration
    $migrations = $planner->up();
    // plan up execution for Orders and Items
    $migrations = $planner->up(Migration\Items::class)
    // plan down execution
    $migrations = $planner->down();
} catch (PlannerException $e) {
    // PlannerException::DEPENDENCY_CIRCULAR (Circular Dependency Found)
    // PlannerException::DEPENDENCY_INVALID (Invalid Dependency Data Type)
} catch (MigrationException $e) {
    // MigrationException::NAME_UNKNOWN (Unknown Migration Name)
    // MigrationException::NAME_DUPLICATED (Duplicated Migration Name)
    // MigrationException::CALLABLE_UNKNOWN (Unknown Callable Function)
}

You can add migrations to container in any order, because dependencies are checked on planning stage. Migration names are unique and must not be duplicated. Using objects from Griffin\Migration\Migration immutable class can throw errors if callables were not defined.

This stage also searches for circular dependencies, where A depends of B and B depends of A. This type of requirement is not allowed and will rise an exception describing the problem.

Running

After planning, Griffin runs migrations using Griffin\Runner\Runner class. Internally, Griffin plans migrations execution first and after that it will execute running on second stage.

use FooBar\Database\Migration;
use Griffin\Migration\Container;
use Griffin\Migration\Exception as MigrationException;
use Griffin\Planner\Exception as PlannerException;
use Griffin\Planner\Planner;
use Griffin\Runner\Exception as RunnerException;
use Griffin\Runner\Runner;

$container = new Container();
$planner   = new Planner($container);
$runner    = new Runner($planner);

try {
    // run up for everything
    $runner->up();
    // run up for Orders and Items
    $runner->up(Migration\Items::class)
    // run complete down
    $runner->down();
} catch (RunnerException $e) {
    // RunnerException::ROLLBACK_CIRCULAR (Circular Rollback Found)
} catch (PlannerException $e) {
    // PlannerException::DEPENDENCY_CIRCULAR (Circular Dependency Found)
    // PlannerException::DEPENDENCY_INVALID (Invalid Dependency Data Type)
} catch (MigrationException $e) {
    // MigrationException::NAME_UNKNOWN (Unknown Migration Name)
    // MigrationException::NAME_DUPLICATED (Duplicated Migration Name)
    // MigrationException::CALLABLE_UNKNOWN (Unknown Callable Function)
}

For every planned migration Griffin\Runner\Runner will execute migration up method if assert returns false. During a migration execution, errors can be raised and Griffin will try to automatically rollback executed migrations. If during rollback from this migration Griffin finds another error, an exeception will be throw.

If you want to rollback migrations manually, Griffin will use migration assert method to check if resource was created and if this method returns true, migration method down will be called. As before, if Griffin finds an error it will try to recreate resources.

Event Dispatcher

Lastly, Griffin implements PSR-14 Event Dispatcher and triggers events after and before migrations up and down. You can use it to create a logger, as example.

use FooBar\Database\Migration;
use Griffin\Event;
use Griffin\Migration\Container;
use Griffin\Planner\Planner;
use Griffin\Runner\Runner;
use League\Event\EventDispatcher;

$container = new Container();
$planner   = new Planner($container);
$runner    = new Runner($planner);

$logger = fn($event)
    => printf("%s::%s\n", get_class($event), get_class($event->getMigration()));

$dispatcher = new EventDispatcher(); // PSR-14

$dispatcher->subscribeTo(Event\Migration\UpBefore::class, $logger);
$dispatcher->subscribeTo(Event\Migration\UpAfter::class, $logger);

$dispatcher->subscribeTo(Event\Migration\DownBefore::class, $logger);
$dispatcher->subscribeTo(Event\Migration\DownAfter::class, $logger);

$runner
    ->setEventDispatcher($dispatcher)
    ->addMigration(new Migration\Orders());

$runner->up();
$runner->down();

// Griffin\Event\Migration\UpBefore::Database\Migration\Table\Item
// Griffin\Event\Migration\UpAfter::Database\Migration\Table\Item
// Griffin\Event\Migration\DownBefore::Database\Migration\Table\Item
// Griffin\Event\Migration\DownAfter::Database\Migration\Table\Item

Development

You can use Docker Compose to build an image and run a container to develop and test this package.

docker-compose build
docker-compose run --rm php composer install
docker-compose run --rm php composer test

References

License

This package is opensource and available under MIT license described in LICENSE.

Icons made by Freepik from Flaticon.

You might also like...
This package provides a framework-agnostic database backup manager for dumping to and restoring databases from S3, Dropbox, FTP, SFTP, and Rackspace Cloud

Database Backup Manager This package provides a framework-agnostic database backup manager for dumping to and restoring databases from S3, Dropbox, FT

Async MySQL Framework for PocketMine-MP
Async MySQL Framework for PocketMine-MP

MyPigSQL Join my discord: https://discord.gg/2QAPHbqrny Async MySQL Framework for PocketMine-MP Known Issues There are some weird behaviors still, do

TO DO LIST WITH LOGIN AND SIGN UP and LOGOUT using PHP and MySQL please do edit the _dbconnect.php before viewing the website.

TO-DO-LIST-WITH-LOGIN-AND-SIGN-UP TO DO LIST WITH LOGIN AND SIGN UP and LOGOUT using PHP and MySQL please do edit the _dbconnect.php before viewing th

Propel2 is an open-source high-performance Object-Relational Mapping (ORM) for modern PHP

Propel2 Propel2 is an open-source Object-Relational Mapping (ORM) for PHP. Requirements Propel uses the following Symfony Components: Config Console F

Easy-to-use PDO wrapper for PHP projects.

EasyDB - Simple Database Abstraction Layer PDO lacks brevity and simplicity; EasyDB makes separating data from instructions easy (and aesthetically pl

A data mapper implementation for your persistence model in PHP.

Atlas.Orm Atlas is a data mapper implementation for persistence models (not domain models). As such, Atlas uses the term "record" to indicate that its

PHP Object Model Manager for Postgresql

POMM: The PHP Object Model Manager for Postgresql Note This is the 1,x version of Pomm. This package is not maintained anymore, the stable Pomm 2.0 is

[READ-ONLY] A flexible, lightweight and powerful Object-Relational Mapper for PHP, implemented using the DataMapper pattern. This repo is a split of the main code that can be found in https://github.com/cakephp/cakephp

CakePHP ORM The CakePHP ORM provides a powerful and flexible way to work with relational databases. Using a datamapper pattern the ORM allows you to m

PHP DataMapper, ORM
PHP DataMapper, ORM

Cycle ORM Cycle is PHP DataMapper, ORM and Data Modelling engine designed to safely work in classic and daemonized PHP applications (like RoadRunner).

Comments
  • Change FooBar

    Change FooBar

    Our examples were created with foobar placeholder names.

    https://en.wikipedia.org/wiki/Foobar

    But reading a thread on Twitter, there's a significant quantity of developers that doesn't like it. A replacement example is to use fruit names.

    Let's do it?

    opened by wandersonwhcr 0
  • PSR-14 Support

    PSR-14 Support

    Griffin has a direct dependency to psr/event-dispatcher.

    But we really need it? Can we run Griffin without PSR-14 installs even using Griffin\Event\DispatcherAwareTrait?

    How to test it?

    If it works, change docs.

    Also, suggests PSR-14 via Composer.

    documentation enhancement 
    opened by wandersonwhcr 1
  • Exceptions must Attach Problems

    Exceptions must Attach Problems

    Internal try..catch exception handlers must attach objects with errors to improve error checking.

    Per example, if a circular dependency was found, which migration generate it?

    enhancement 
    opened by wandersonwhcr 0
  • Use a Container Interface

    Use a Container Interface

    We can create a Griffin\Migration\ContainerInterface to define a contract. After that, we can implement other types of containers with new features, like lazy loading or dependency injection.

    enhancement good first issue 
    opened by wandersonwhcr 0
Releases(1.0.0)
  • 1.0.0(Apr 24, 2021)

    First release!

    This version provides everything you need to use Griffin, including the migration interface, container, planner and runner.

    Please check README.md for documentation.

    Source code(tar.gz)
    Source code(zip)
Owner
Griffin
Griffin is a Graph-Oriented Migration Framework for PHP
Griffin
The lightweight PHP database framework to accelerate development

The lightweight PHP database framework to accelerate development Features Lightweight - Less than 100 KB, portable with only one file Easy - Extremely

Angel Lai 4.6k Dec 28, 2022
Ouzo Framework - PHP MVC ORM

Ouzo is a PHP MVC framework with built-in ORM and util libraries. PHP 8.0 or later is required. We believe in clean code and simplicity. We value unit

Ouzo 69 Dec 27, 2022
a php client for distributed transaction framework dtm. 分布式事务管理器dtm的php客户端

a client for distributed transaction manager dtm dtmcli 是分布式事务管理器dtm的客户端sdk dtm分布式事务管理服务 DTM是一款跨语言的开源分布式事务管理器,优雅的解决了幂等、空补偿、悬挂等分布式事务难题。提供了简单易用、高性能、易水平扩

null 29 Jan 7, 2023
A complete, simple and powerful database framework written in PHP

BaseSQL BaseSQL is a complete database framework written in PHP. It was built to accelerate projects development by handle database connections and qu

Willian Pinheiro 2 Sep 21, 2021
The fastest pure PHP database framework with a powerful static code generator, supports horizontal scale up, designed for PHP7

Maghead 4.0.x IS CURRENTLY UNDER HEAVY DEVELOPMENT, API IS NOT STABLE Maghead is an open-source Object-Relational Mapping (ORM) designed for PHP7. Mag

Maghead 477 Dec 24, 2022
The Enobrev\ORM library is a small framework of classes meant to be used for simply mapping a mysql database to PHP classes, and for creating simply SQL statements using those classes.

The Enobrev\ORM library is a small framework of classes meant to be used for simply mapping a mysql database to PHP classes, and for creating simply SQL statements using those classes.

Mark Armendariz 0 Jan 7, 2022
[READ ONLY] Subtree split of the Illuminate Database component (see laravel/framework)

Illuminate Database The Illuminate Database component is a full database toolkit for PHP, providing an expressive query builder, ActiveRecord style OR

The Laravel Components 2.5k Dec 27, 2022
ATK Data - Data Access Framework for high-latency databases (Cloud SQL/NoSQL).

ATK Data - Data Model Abstraction for Agile Toolkit Agile Toolkit is a Low Code framework written in PHP. Agile UI implement server side rendering eng

Agile Toolkit 257 Dec 29, 2022
Adjacency List’ed Closure Table database design pattern implementation for the Laravel framework.

ClosureTable This is a database manipulation package for the Laravel 5.4+ framework. You may want to use it when you need to store and operate hierarc

Yan Ivanov 441 Dec 11, 2022
Phpstan-dba - database handling related class reflection extension for PHPStan & framework-specific rules

database handling class reflection extension for PHPStan This extension provides following features: PDO->query knows the array shape of the returned

Markus Staab 175 Dec 29, 2022