{"id":23062475,"url":"https://github.com/aymdev/fregata","last_synced_at":"2025-07-30T09:08:13.734Z","repository":{"id":43219867,"uuid":"264428062","full_name":"AymDev/Fregata","owner":"AymDev","description":"Fregata - a PHP database migrator","archived":false,"fork":false,"pushed_at":"2022-03-12T15:41:26.000Z","size":214,"stargazers_count":24,"open_issues_count":3,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-20T09:49:08.300Z","etag":null,"topics":["database","doctrine","hacktoberfest","migration","migration-framework","migration-tool","php","php-database-migrator","relational-database","sql"],"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/AymDev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-16T12:06:15.000Z","updated_at":"2025-05-07T19:55:33.000Z","dependencies_parsed_at":"2022-09-14T06:51:19.219Z","dependency_job_id":null,"html_url":"https://github.com/AymDev/Fregata","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/AymDev/Fregata","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AymDev%2FFregata","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AymDev%2FFregata/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AymDev%2FFregata/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AymDev%2FFregata/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AymDev","download_url":"https://codeload.github.com/AymDev/Fregata/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AymDev%2FFregata/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267843005,"owners_count":24153135,"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","status":"online","status_checked_at":"2025-07-30T02:00:09.044Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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","doctrine","hacktoberfest","migration","migration-framework","migration-tool","php","php-database-migrator","relational-database","sql"],"created_at":"2024-12-16T03:27:15.590Z","updated_at":"2025-07-30T09:08:13.705Z","avatar_url":"https://github.com/AymDev.png","language":"PHP","readme":"# Fregata - PHP database migrator\n\n![](https://github.com/AymDev/Fregata/workflows/Unit%20Test%20Suite/badge.svg)\n[![Latest Stable Version](https://poser.pugx.org/aymdev/fregata/v)](//packagist.org/packages/aymdev/fregata)\n[![License](https://poser.pugx.org/aymdev/fregata/license)](//packagist.org/packages/aymdev/fregata)\n\n**Fregata** is a data migration framework. You can use it to migrate any kind of data, but it has features to help you\nmigrate between different DBMS or database structures.\n\n**Documentation**:\n\n1. [Introduction](#introduction)\n2. [Setup](#setup)\n    1. [Installation](#installation)\n    2. [Configuration](#configuration)\n        1. [Kernel and service container](#kernel-and-service-container)\n        2. [YAML configuration](#yaml-configuration)\n3. [Components](#components)\n    1. [Migration Registry](#migration-registry)\n    2. [Migration](#migration)\n        1. [Options](#options)\n        2. [Parent migration](#parent-migration)\n    3. [Task](#task)\n    4. [Migrator](#migrator)\n        1. [Puller](#puller)\n        2. [Pusher](#pusher)\n        3. [Executor](#executor)\n4. [Tools](#tools)\n    1. [Migration Context](#migration-context)\n5. [Features](#features)\n    1. [Dependent migrators](#dependent-migrators)\n    2. [Batch pulling](#batch-pulling)\n    3. [Foreign Key migrations](#foreign-key-migrations)\n6. [CLI usage](#cli-usage)\n    1. [List migrations](#list-migrations)\n    2. [Get details of a migration](#get-details-of-a-migration)\n    3. [Execute a migration](#execute-a-migration)\n7. [Contributing](#contributing)\n\n# Introduction\n**Fregata** is a data migration framework. It can probably be compared to an *ETL (Extract Transform - Load)* tool.\n\nYou can use it to migrate data from files, databases, or anything you want, it is completely agnostic on this part\n(some of its test migrate data between PHP arrays). But note that it was initially targeting databases, providing a way\nto migrate data between different DBMS, even with a different structure. Some included features are specifically built\nfor databases.\n\n\u003eWhy creating a framework for data migration ?\n\nWhile database migrations might not be your everyday task, I encountered it multiple times on different projects. That's\nwhy I created **Fregata** to have a migration workflow I could reuse.\n\n\u003eWhat are the use cases ?\n\nHere are some example use cases (from experience):\n\n - when you want to change from a DBMS to another\n - when you want to sync your staging database with the production one (useful for CMS-based projects)\n\n\n# Setup\n\n## Installation\n\nInstall with Composer:\n```shell\ncomposer require aymdev/fregata\n```\n\n## Configuration\n\n**Fregata** expects you to have a `config` and a `cache` directory at your project root by default.\n\n### Kernel and service container\n\nIf you need to use a different directory structure than the default one, you can extend the\n`Fregata\\Configuration\\AbstractFregataKernel` class.\nThen you will have to implement methods to specify your configuration and cache directory.\n\u003e**Important**: your kernel full qualified class name ***must*** be `App\\FregataKernel`.\n\nThe *kernel* holds a *service container*, built from **Symfony**'s **DependencyInjection** component.\nThis means you can define your own services as you would do it in a **Symfony** application, in a\n`services.yaml` file in your configuration directory.\n\nHere's a recommended minimal **services.yaml** to start your project:\n```yaml\nservices:\n    _defaults:\n        autowire: true\n\n    App\\:\n        resource: '../src/'\n```\n\n### YAML configuration\n\nTo configure **Fregata** itself, you will need a `fregata.yaml` file in your configuration directory.\n\nExample configuration file:\n```yaml\nfregata:\n    migrations:\n        # define any name for your migration\n        main_migration:\n            # define custom options for your migrations\n            options:\n                custom_opt: 'opt_value'\n                special_cfg:\n                    foo: bar\n            # load migrators from a directory\n            # use the %fregata.root_dir% parameter to define a relative path from the project root\n            migrators_directory: '%fregata.root_dir%/src/MainMigration'\n            # load individual migrators\n            # can be combined with migrators_directory\n            migrators:\n                - App\\MainMigration\\FirstMigrator\n            # load tasks to execute before or after the migrators\n            tasks:\n                before:\n                    - App\\MainMigration\\BeforeTask\n                after:\n                    - App\\MainMigration\\AfterTask\n            \n        other_migration:\n            # extend an other migration to inherit its options, tasks and migrators\n            parent: main_migration\n            # overwrite a part of the options\n            options:\n                custom_opt: 'another_value'\n            # load additional migrators or tasks\n            migrators:\n                - App\\OtherMigration\\Migrator\n```\n\n# Components\n\n## Migration Registry\n\nThe **migration registry** contains every defined migrations. You shouldn't have to interact with it.\n\n## Migration\n\nA **migration** project holds the steps of a migration. For example, data migration from your production\ndatabase to staging one.\nEach **migration** is created and saved into the registry based on your configuration. You don't need to\ninstantiate migration objects by yourself.\n\nMigrations contain **tasks** and **migrators**. When a migration is run, components are executed in the\nfollowing order:\n\n - before tasks\n - migrators\n - after tasks\n\n### Options\n\nYou may need to set specific configuration to your migration project, which could be used by **tasks**\nor **migrators**.\nWith the `options` key you can define your migration specific configuration, they will be accessible to\nthe components from the [migration context](#migration-context). \n\n### Parent migration\n\nWhen having multiple **migrations** for different environments, you probably want to avoid duplicating your\nwhole configuration.\nYou can extend a migration with the `parent` key. The *\"child\"* migration will inherit the parent's\n*options*, **tasks** and **migrators**. You can still add more tasks and migrators, and overwrite options.\n\n## Task\n\nA **task** can be executed *before* or *after* **migrators**. They can be useful to bootstrap your migration\n(before tasks) or to clean temporary data at the end (after tasks):\n\n```php\nuse Fregata\\Migration\\TaskInterface;\n\nclass MyTask implements TaskInterface\n{\n    public function execute() : ?string\n    {\n        // perform some verifications, delete temporary data, ...\n        return 'Optional result message';\n    }\n}\n```\n\n## Migrator\n\nThe **migrators** are the main components of the framework. A single migrator holds 3 components:\n\n - a **puller**\n - a **pusher**\n - an **executor**\n\nIt must return its components from getter methods by implementing\n`Fregata\\Migration\\Migrator\\MigratorInterface`.\nA **migrator** represents the migration of a data from a **source** to a **target**. For example, migrating data\nfrom a *MySQL* table to a *PostgreSQL* one.\n\n### Puller\n\nA **puller** is a **migrator** component responsible for *pulling data from a source*. It returns data\nand optionally the number of items to migrate:\n\n```php\nuse Doctrine\\DBAL\\Connection;\nuse Fregata\\Migration\\Migrator\\Component\\PullerInterface;\n\nclass Puller implements PullerInterface\n{\n    private Connection $connection;\n    \n    public function __construct(Connection $connection)\n    {\n        $this-\u003econnection = $connection;\n    }\n\n    public function pull()\n    {\n        return $this-\u003econnection\n            -\u003eexecuteQuery('SELECT * FROM my_table')\n            -\u003efetchAllAssociative();\n    }\n    \n    public function count() : ?int\n    {\n        return $this-\u003econnection\n            -\u003eexecuteQuery('SELECT COUNT(*) FROM my_table')\n            -\u003efetchColumn();\n    }\n}\n```\n\n### Pusher\n\nA **pusher** gets item fetched by the **puller** 1 by 1 and has to *push the data to a target*:\n\n```php\nuse Doctrine\\DBAL\\Connection;\nuse Fregata\\Migration\\Migrator\\Component\\PusherInterface;\n\nclass Pusher implements PusherInterface\n{\n    private Connection $connection;\n    \n    public function __construct(Connection $connection)\n    {\n        $this-\u003econnection = $connection;\n    }\n    \n    /**\n     * @return int number of items inserted\n     */\n    public function push($data): int\n    {\n        return $this-\u003econnection-\u003eexecuteStatement(\n            'INSERT INTO my_table VALUES (:foo, :bar, :baz)',\n            [\n                'foo' =\u003e $data['foo'],\n                'bar' =\u003e some_function($data['bar']),\n                'baz' =\u003e 'default value',\n            ]\n        );\n    }\n}\n```\nHere `$data` is a single item from the example **puller** returned value. The `push()` method is called\nmultiple times.\nThe separation of **pullers** and **pushers** allow you to migrate between different sources: pull from\na file and push to a database, etc.\n\n### Executor\n\nThe **executor** is the component which plugs a **puller** with a **pusher**. A default one is provided\nand should work for most cases: `Fregata\\Migration\\Migrator\\Component\\Executor`.\nExtend the default **executor** if you need a specific behaviour.\n\n# Tools\n\n## Migration Context\n\nYou can get some informations about the current **migration** by injecting the \n`Fregata\\Migration\\MigrationContext` service in a **task** or **migration**.\n\nIt provides:\n\n - current **migration** object\n - current migration **name**\n - migration **options**\n - **parent** migration name if applicable\n\n# Features\n\n## Dependent migrators\n\nIf your **migrators** need to be executed in a specific order you can define dependencies, and they will\nbe sorted automatically:\n\n```php\nuse Fregata\\Migration\\Migrator\\DependentMigratorInterface;\n\nclass DependentMigrator implements DependentMigratorInterface\n{\n    public function getDependencies() : array\n    {\n        return [\n            DependencyMigrator::class,\n        ];\n    }\n    \n    // other migrator methods ...\n}\n```\nHere, `DependencyMigrator` will be executed before `DependentMigrator`.\n\n## Batch pulling\n\nWhen a **puller** works with very large datasets you might want to pull the data by chunks:\n\n```php\nuse Doctrine\\DBAL\\Connection;\nuse Fregata\\Migration\\Migrator\\Component\\BatchPullerInterface;\n\nclass BatchPulling implements BatchPullerInterface\n{\n    private Connection $connection;\n    private ?int $count = null;\n    \n    public function __construct(Connection $connection)\n    {\n        $this-\u003econnection = $connection;\n    }\n\n    public function pull(): \\Generator\n    {\n        $limit = 50;\n        $offset = 0;\n        \n        while ($offset \u003c $this-\u003ecount()) {\n            yield $this-\u003econnection\n                -\u003eexecuteQuery(sprintf('SELECT * FROM my_table LIMIT %d, %d', $offset, $limit))\n                -\u003efetchAllAssociative();\n            \n            $offset += $limit;\n        }\n    }\n    \n    public function count() : ?int\n    {\n        if (null === $this-\u003ecount) {\n            $this-\u003ecount = $this-\u003econnection\n                -\u003eexecuteQuery('SELECT COUNT(*) FROM my_table')\n                -\u003efetchColumn();\n        }\n        \n        return $this-\u003ecount;\n    }\n}\n```\n\n## Foreign Key migrations\n\nOne of the most complex parts of a database migration is about **foreign keys**. There are multiple steps\nto follow to perform a valid foreign key migration. This is done using **Doctrine DBAL**.\n\nYou must add 2 tasks to your migration:\n\n - **before** task: `Fregata\\Adapter\\Doctrine\\DBAL\\ForeignKey\\Task\\ForeignKeyBeforeTask`\n - **after** task: `Fregata\\Adapter\\Doctrine\\DBAL\\ForeignKey\\Task\\ForeignKeyAfterTask`\n\nThe before task will create temporary columns in your target database to keep the original referenced and\nreferencing columns. It may also change referencing columns to allow `NULL` (only if you specify it).\nThe after task will set the real values in your original referencing columns and then drop the temporary\ncolumns.\n\nThen the migrators must provide the database connection and the list of foreign keys:\n\n```php\nuse Doctrine\\DBAL\\Connection;\nuse Doctrine\\DBAL\\Schema\\ForeignKeyConstraint;\nuse Fregata\\Adapter\\Doctrine\\DBAL\\ForeignKey\\ForeignKey;\nuse Fregata\\Adapter\\Doctrine\\DBAL\\ForeignKey\\Migrator\\HasForeignKeysInterface;\n\nclass ReferencingMigrator implements HasForeignKeysInterface\n{\n    private Connection $connection;\n    \n    public function __construct(Connection $connection)\n    {\n        $this-\u003econnection = $connection;\n    }\n    \n    public function getConnection() : Connection\n    {\n        return $this-\u003econnection;\n    }\n    \n    /**\n     * List the foreign keys constraints to keep\n     * @return ForeignKey[]\n     */\n    public function getForeignKeys() : array\n    {\n        $constraints = $this-\u003econnection-\u003egetSchemaManager()-\u003elistTableForeignKeys('my_table');\n        return array_map(\n            function (ForeignKeyConstraint $constraint) {\n                return new ForeignKey(\n                    $constraint,            // DBAL constraint object\n                    'target_referencing',   // name of the referencing table\n                    ['fk']                  // columns to change to allow NULL (will be set back to NOT NULL in the after task)\n                );\n            },\n            $constraints\n        );\n    }\n    \n    // other migrator methods ...\n}\n```\n\nThe migrators are responsible for the data migration, this means you need to fill the temporary columns\nwith original primary/foreign key from the source database.\nTo get the name of a temporary column, require the `CopyColumnHelper` service in your **pusher**:\n\n```php\nuse Doctrine\\DBAL\\Connection;\nuse Fregata\\Adapter\\Doctrine\\DBAL\\ForeignKey\\CopyColumnHelper;\nuse Fregata\\Migration\\Migrator\\Component\\PusherInterface;\n\nclass ReferencingForeignKeyPusher implements PusherInterface\n{\n    private Connection $connection;\n    private CopyColumnHelper $columnHelper;\n    \n    public function __construct(Connection $connection, CopyColumnHelper $columnHelper)\n    {\n        $this-\u003econnection = $connection;\n        $this-\u003ecolumnHelper = $columnHelper;\n    }\n    \n    /**\n     * @return int number of items inserted\n     */\n    public function push($data): int\n    {\n        return $this-\u003econnection-\u003eexecuteStatement(\n            sprintf(\n                'INSERT INTO my_table (column, %s) VALUES (:value, :old_fk)',\n                $this-\u003ecolumnHelper-\u003elocalColumn('my_table', 'fk_column')\n            ),\n            [\n                'value' =\u003e $data['value'],\n                'old_fk' =\u003e $data['fk_column'],\n            ]\n        );\n    }\n}\n```\nThis example show the *local* (or *referencing*) side but this need to be done for the *foreign* (or \n*referenced*) side too, using `CopyColumnHelper::foreignColumn()`.\n\n# CLI usage\n\n**Fregata** provides a simple program to run the migrations, you can launch it with:\n```shell\nphp vendor/bin/fregata\n```\n\n## List migrations\n\nTo list the migrations of your installation, run the `migration:list` command:\n```shell\n\u003e php vendor/bin/fregata migration:list\n\nRegistered migrations: 2\n========================\n\nmain_migration\nother_migration\n\n```\n\n## Get details of a migration\n\nTo get information about a single migration, run the `migration:show` command:\n```shell\n\u003e php vendor/bin/fregata migration:show main_migration\n\nmain_migration : 1 migrators\n============================\n\n --- --------------------------------- \n  #   Migrator Name                   \n --- --------------------------------- \n  0   App\\MainMigration\\FirstMigrator \n --- --------------------------------- \n\n```\n\n## Execute a migration\n\nAnd the most important one to run a migration: `migration:execute`.\n```shell\n\u003e php vendor/bin/fregata migration:execute main_migration\n\n Confirm execution of the \"main_migration\" migration ? (yes/no) [no]:\n \u003e yes\n\n                                                                                                                        \n [OK] Starting \"main_migration\" migration: 1 migrators                                                                  \n                                                                                                                        \n\nBefore tasks: 1\n===============\n\n App\\MainMigration\\BeforeTask : OK\n\nMigrators: 1\n============\n\n0 - Executing \"App\\MainMigration\\FirstMigrator\" [3 items] :\n===========================================================\n\n 3/3 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%\n\n\nAfter tasks: 1\n==============\n\n App\\MainMigration\\AfterTask : OK\n\n                                                                                                                        \n [OK] Migrated successfully !                                                                                           \n\n```\n\n# Contributing\n\nA **Docker** setup is available, providing a **MySQL 5.7** service.\n\nIf you want to test the implementation of the framework (using a **Composer**\n[path repository](https://getcomposer.org/doc/05-repositories.md#path)), install it in a `_implementation`\ndirectory at the root of the project, it is ignored by Git by default and will ensure you are using your\nimplementation autoloader.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faymdev%2Ffregata","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faymdev%2Ffregata","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faymdev%2Ffregata/lists"}