├── pint.json ├── CHANGELOG.md ├── src ├── Exceptions │ ├── KeyNotFoundException.php │ └── ClassNotFoundException.php ├── Facades │ └── DataMigrator.php ├── DataMigratorServiceProvider.php ├── Traits │ └── FieldTokenizer.php └── DataMigrator.php ├── LICENSE.md ├── composer.json └── README.md /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "rules": { 4 | "binary_operator_spaces": { 5 | "operators": { 6 | "=>": "align_single_space_minimal" 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `DataMigrator` will be documented in this file. 4 | 5 | ## 1.0.1 - 2023-03-01 6 | 7 | - Change querying from `all()` to `chunk()` in `transferAllDataFromModelToModel` for performance increasing 8 | 9 | ## 1.0.0 - 2023-02-28 10 | 11 | - initial release 12 | -------------------------------------------------------------------------------- /src/Exceptions/KeyNotFoundException.php: -------------------------------------------------------------------------------- 1 | name('datamigrator'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) oguzhankrcb 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Traits/FieldTokenizer.php: -------------------------------------------------------------------------------- 1 | addToken($tokens, $token); 23 | 24 | continue; 25 | } 26 | 27 | if ($string[$i] === ']') { 28 | $this->addToken($tokens, $token); 29 | 30 | continue; 31 | } 32 | 33 | if ($string[$i] === ' ') { 34 | $this->addToken($tokens, $token); 35 | $tokens[] = ' '; 36 | 37 | continue; 38 | } 39 | 40 | $token .= $string[$i]; 41 | } 42 | 43 | $this->addToken($tokens, $token); 44 | 45 | return $tokens; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oguzhankrcb/datamigrator", 3 | "description": "A simple package for data migration", 4 | "keywords": [ 5 | "oguzhankrcb", 6 | "laravel", 7 | "datamigrator" 8 | ], 9 | "homepage": "https://github.com/oguzhankrcb/datamigrator", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Oğuzhan KARACABAY", 14 | "email": "oguzhankrcb@gmail.com", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.0|^8.1|^8.2", 20 | "spatie/laravel-package-tools": "^1.14.0", 21 | "illuminate/contracts": "^8.0|^9.0|^10.0", 22 | "illuminate/support": "^8.0|^9.0|^10.0", 23 | "illuminate/database": "^8.0|^9.0|^10.0" 24 | }, 25 | "require-dev": { 26 | "laravel/pint": "^1.0", 27 | "nunomaduro/collision": "^6.4", 28 | "phpunit/phpunit": "^9.6.1", 29 | "orchestra/testbench": "^7.21|^8.0" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Oguzhankrcb\\DataMigrator\\": "src" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Oguzhankrcb\\DataMigrator\\Tests\\": "tests", 39 | "Oguzhankrcb\\DataMigrator\\Tests\\Database\\Factories\\": "tests/database/factories/" 40 | } 41 | }, 42 | "scripts": { 43 | "analyse": "vendor/bin/phpstan analyse", 44 | "test": "vendor/bin/phpunit", 45 | "format": "vendor/bin/pint" 46 | }, 47 | "config": { 48 | "sort-packages": true, 49 | "allow-plugins": { 50 | "pestphp/pest-plugin": true, 51 | "phpstan/extension-installer": true 52 | } 53 | }, 54 | "extra": { 55 | "laravel": { 56 | "providers": [ 57 | "Oguzhankrcb\\DataMigrator\\DataMigratorServiceProvider" 58 | ], 59 | "aliases": { 60 | "DataMigrator": "Oguzhankrcb\\DataMigrator\\Facades\\DataMigrator" 61 | } 62 | } 63 | }, 64 | "minimum-stability": "dev", 65 | "prefer-stable": true 66 | } 67 | -------------------------------------------------------------------------------- /src/DataMigrator.php: -------------------------------------------------------------------------------- 1 | $fromField) { 29 | if (is_array($fromField)) { 30 | $toModel[$newField] = $this->transformData($fromField, $fromModel); 31 | 32 | continue; 33 | } 34 | 35 | $fieldParts = $this->tokenizeField($fromField); 36 | 37 | foreach ($fieldParts as $fieldValue) { 38 | $fromValue = $fromModel; 39 | 40 | if (strpos($fieldValue, '.') === false && strpos($fieldValue, '->') === false) { 41 | if (isset($toModel[$newField])) { 42 | $toModel[$newField] .= $fromValue[$fieldValue] ?? $fieldValue; 43 | } else { 44 | $toModel[$newField] = $fromValue[$fieldValue] ?? $fieldValue; 45 | } 46 | 47 | continue; 48 | } 49 | 50 | $fromFieldParts = explode('->', $fieldValue); 51 | foreach ($fromFieldParts as $part) { 52 | if (strpos($part, '.') !== false) { 53 | $nestedParts = explode('.', $part); 54 | $nestedValue = ''; 55 | foreach ($nestedParts as $nestedPart) { 56 | if (! isset($fromValue[$nestedPart])) { 57 | throw new KeyNotFoundException($nestedPart); 58 | } 59 | $nestedValue .= $fromValue[$nestedPart]; 60 | } 61 | $fromValue = $nestedValue; 62 | 63 | continue; 64 | } 65 | 66 | if (! isset($fromValue[$part])) { 67 | throw new KeyNotFoundException($part); 68 | } 69 | 70 | $fromValue = $fromValue[$part]; 71 | } 72 | 73 | if ($fromValue !== null && $fromValue !== '') { 74 | if (isset($toModel[$newField])) { 75 | $toModel[$newField] .= $fromValue; 76 | } else { 77 | $toModel[$newField] = $fromValue; 78 | } 79 | } 80 | } 81 | } 82 | 83 | return $toModel; 84 | } 85 | 86 | /** 87 | * @throws \Oguzhankrcb\DataMigrator\Exceptions\ClassNotFoundException 88 | * 89 | * @see \Oguzhankrcb\DataMigrator\Tests\Unit\DataMigratorTest::it_transfers_data_from_model_to_model_with_concatenate_keys() 90 | * @see \Oguzhankrcb\DataMigrator\Tests\Unit\DataMigratorTest::it_transfers_data_from_model_to_model_with_nested_keys() 91 | * @see \Oguzhankrcb\DataMigrator\Tests\Unit\DataMigratorTest::it_transfers_data_from_model_to_model_with_static_keys() 92 | * @see \Oguzhankrcb\DataMigrator\Tests\Unit\DataMigratorTest::it_throws_exception_while_transfering_data_from_model_to_model_with_empty_model() 93 | */ 94 | public function transferDataModelToModel( 95 | string $transferToModel, 96 | array $toModelPrototype, 97 | Model|array $transferFromModel 98 | ): Model|null { 99 | if (! class_exists($transferToModel)) { 100 | throw new ClassNotFoundException($transferToModel); 101 | } 102 | 103 | try { 104 | DB::beginTransaction(); 105 | 106 | if ($transferFromModel instanceof Model) { 107 | $transferFromModel = $transferFromModel->toArray(); 108 | } 109 | 110 | $toModel = $this->transformData($toModelPrototype, $transferFromModel); 111 | 112 | $createdModel = $transferToModel::create($toModel); 113 | 114 | DB::commit(); 115 | } catch (Throwable $e) { 116 | DB::rollBack(); 117 | throw new Exception($e->getMessage()); 118 | } 119 | 120 | return $createdModel; 121 | } 122 | 123 | /** 124 | * @throws \Oguzhankrcb\DataMigrator\Exceptions\ClassNotFoundException 125 | * 126 | * @see \Oguzhankrcb\DataMigrator\Tests\Unit\DataMigratorTest::it_transfers_all_data_from_model_to_model_with_concatenate_keys() 127 | * @see \Oguzhankrcb\DataMigrator\Tests\Unit\DataMigratorTest::it_transfers_all_data_from_model_to_model_with_nested_keys() 128 | * @see \Oguzhankrcb\DataMigrator\Tests\Unit\DataMigratorTest::it_transfers_all_data_from_model_to_model_with_static_keys() 129 | * @see \Oguzhankrcb\DataMigrator\Tests\Unit\DataMigratorTest::it_throws_exception_while_transfering_all_data_from_model_to_model_with_empty_model() 130 | */ 131 | public function transferAllDataFromModelToModel( 132 | string $transferToModel, 133 | array $toModelPrototype, 134 | string $transferFromModel, 135 | int $chunkQuerySize = 1000 136 | ): void { 137 | if (! class_exists($transferFromModel)) { 138 | throw new ClassNotFoundException($transferFromModel); 139 | } 140 | 141 | if (! class_exists($transferToModel)) { 142 | throw new ClassNotFoundException($transferToModel); 143 | } 144 | 145 | try { 146 | DB::beginTransaction(); 147 | 148 | /** @var Model $transferFromModel */ 149 | $transferFromModel::query()->chunk($chunkQuerySize, function ($models) use ($toModelPrototype, $transferToModel) { 150 | foreach ($models as $model) { 151 | $toModel = $this->transformData($toModelPrototype, $model->toArray()); 152 | 153 | $transferToModel::create($toModel); 154 | } 155 | }); 156 | 157 | DB::commit(); 158 | } catch (Throwable $e) { 159 | DB::rollBack(); 160 | throw new Exception($e->getMessage()); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Data Migrator 4 | 5 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/oguzhankrcb/datamigrator.svg?style=flat-square)](https://packagist.org/packages/oguzhankrcb/datamigrator) 6 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/oguzhankrcb/datamigrator/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/oguzhankrcb/datamigrator/actions?query=workflow%3Arun-tests+branch%3Amain) 7 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/oguzhankrcb/datamigrator/fix-php-code-style-issues.yml?branch=main&label=code%20style&style=flat-square)](https://github.com/oguzhankrcb/datamigrator/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) 8 | [![Total Downloads](https://img.shields.io/packagist/dt/oguzhankrcb/datamigrator.svg?style=flat-square)](https://packagist.org/packages/oguzhankrcb/datamigrator) 9 | 10 | Data Migrator is a PHP/Laravel package that helps you migrate data from one model to another, even if they have 11 | different 12 | structures. 13 | It's especially useful when you're migrating data between models with different database schemas. 14 | 15 | ## Installation 16 | 17 | You can install the package via composer: 18 | 19 | ```bash 20 | composer require oguzhankrcb/datamigrator 21 | ``` 22 | 23 | ## Usage 24 | 25 | ### Transforming Data 26 | 27 | To transform data from one model to another, use the `transformData` method. This method takes two arrays: 28 | `$toModelPrototype` and `$fromModel`. 29 | 30 | `$toModelPrototype` should be an array that describes the structure of the new model, with the keys being the names of 31 | the 32 | new fields, and the values being the names of the fields from the old model that the new fields should be based on. For 33 | example: 34 | 35 | ```php 36 | $toModelPrototype = [ 37 | 'id' => '[id]', 38 | 'unique_id' => '[unique_number.id]', 39 | 'name' => '[data->name]', 40 | 'categories' => [ 41 | 'first_category' => '[data->categories->category_2]', 42 | 'second_category' => '[data->categories->category_3]', 43 | ], 44 | 'alias_with_item_code' => '[data->alias][data->item->code]', 45 | 'alias' => '[data->alias]', 46 | 'item_code' => '[data->item->code]', 47 | 'status' => '[data->status]', 48 | ]; 49 | ``` 50 | 51 | `$fromModel` should be an array that represents a single row of data from the old model, with the keys being the names 52 | of the fields from the old model, and the values being the actual values. 53 | For example: 54 | 55 | ```php 56 | $fromModel = [ 57 | 'id' => 1, 58 | 'unique_number' => 'lxAxmUlkfc', 59 | 'data' => [ 60 | 'name' => 'John Doe', 61 | 'alias' => 'JD', 62 | 'categories' => [ 63 | 'category_1' => 'Bronze', 64 | 'category_2' => 'Silver', 65 | 'category_3' => 'Gold', 66 | ], 67 | 'item' => [ 68 | 'code' => 196854, 69 | ], 70 | 'status' => true, 71 | ], 72 | ]; 73 | ``` 74 | 75 | Here's an example of how to use `transformData`: 76 | 77 | ```php 78 | use Oguzhankrcb\DataMigrator\Facades\DataMigrator; 79 | 80 | $newData = DataMigrator::transformData($toModelPrototype, $fromModel); 81 | ``` 82 | 83 | The `$newData` array will contain the transformed data, with the keys being the names of the new fields, and the values 84 | being the corresponding values from the old model. 85 | 86 | Output Example: 87 | 88 | ```php 89 | [ 90 | 'id' => 1, 91 | 'unique_id' => 'lxAxmUlkfc1', 92 | 'name' => 'John Doe', 93 | 'categories' => [ 94 | 'first_category' => 'Silver', 95 | 'second_category' => 'Gold', 96 | ], 97 | 'alias_with_item_code' => 'JD196854', 98 | 'alias' => 'JD', 99 | 'item_code' => '196854', 100 | 'status' => true, 101 | ] 102 | ``` 103 | 104 | ### Transferring Data 105 | 106 | To transfer all data from one model to another, use the `transferAllDataFromModelToModel` method. This method takes 107 | three 108 | arguments: `$transferToModel`, `$toModelPrototype`, and `$transferFromModel`. 109 | 110 | `$transferToModel` should be the fully qualified class name of the model you want to transfer the data to. For example: 111 | 112 | ```php 113 | $transferToModel = \App\Models\User::class; 114 | ``` 115 | 116 | `$toModelPrototype` should be the same array you used with `transformData`. 117 | 118 | `$transferFromModel` should be the fully qualified class name of the model you want to transfer the data from. For 119 | example: 120 | 121 | ```php 122 | $transferFromModel = \App\Models\LegacyUser::class; 123 | ``` 124 | 125 | Here's an example of how to use `transferAllDataFromModelToModel`: 126 | 127 | ```php 128 | use App\Models\Order; 129 | use App\Models\Invoice; 130 | use Oguzhankrcb\DataMigrator\Facades\DataMigrator; 131 | 132 | // Define the fields to transfer from Order to Invoice 133 | $toModelPrototype = [ 134 | 'invoice_number' => '[order_number]', 135 | 'customer_name' => '[customer->name]', 136 | 'customer_email' => '[customer->email]', 137 | 'total_amount' => '[amount]', 138 | 'total_amount_with_currency' => '[amount]€', 139 | ]; 140 | 141 | // Transfer the data from Order to Invoice 142 | DataMigrator::transferAllDataFromModelToModel(Invoice::class, $toModelPrototype, Order::class); 143 | ``` 144 | 145 | In this example, we define the fields we want to transfer from the `Order` model to the `Invoice` model using the 146 | `$toModelPrototype` array. Then we call the 147 | `transferAllDataFromModelToModel` method, passing in the `Invoice` and `Order` models and the `$toModelPrototype` array. 148 | 149 | This method will transfer all the data from the `Order` model to the `Invoice` model, creating a new `Invoice` model for 150 | each 151 | `Order` model in the database. 152 | 153 | If you want to transfer only one model data to another model you can use `transferDataModelToModel` method 154 | only difference from the `transferAllDataFromModelToModel` method is this method only transfers one model not all 155 | models. 156 | 157 | Here's an example of how to use `transferDataModelToModel`: 158 | 159 | ```php 160 | use App\Models\Order; 161 | use App\Models\Invoice; 162 | use Oguzhankrcb\DataMigrator\Facades\DataMigrator; 163 | 164 | // Define the fields to transfer from Order to Invoice 165 | $toModelPrototype = [ 166 | 'invoice_number' => '[order_number]', 167 | 'customer_name' => '[customer->name]', 168 | 'customer_email' => '[customer->email]', 169 | 'total_amount' => '[amount]', 170 | 'total_amount_with_currency' => '[amount]€', 171 | ]; 172 | 173 | $orderInstance = Order::find(1); 174 | 175 | // Transfer the data from Order to Invoice 176 | $transferedModel = DataMigrator::transferDataModelToModel(Invoice::class, $toModelPrototype, $orderInstance); 177 | ``` 178 | 179 | ## Testing 180 | 181 | ```bash 182 | composer test 183 | ``` 184 | 185 | ## Changelog 186 | 187 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 188 | 189 | ## Contributing 190 | 191 | Contributions are welcome! If you find any bugs or issues, 192 | please [open a new issue](https://github.com/oguzhankrcb/DataMigrator/issues/new) or submit a pull request. 193 | 194 | ## Security Vulnerabilities 195 | 196 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 197 | 198 | ## Credits 199 | 200 | - [Oğuzhan KARACABAY](https://github.com/oguzhankrcb) 201 | - [All Contributors](../../contributors) 202 | 203 | ## License 204 | 205 | The DataMigrator package is open-source software licensed under the [MIT license](https://opensource.org/licenses/MIT). 206 | --------------------------------------------------------------------------------