diff --git a/config/data-sync.php b/config/data-sync.php index 15ce8b9..4630c3b 100644 --- a/config/data-sync.php +++ b/config/data-sync.php @@ -1,8 +1,8 @@ base_path('sync'), + 'path' => base_path('sync'), 'order' => [ // - ] + ], ]; diff --git a/readme.md b/readme.md index 4d2bbcc..de6787d 100644 --- a/readme.md +++ b/readme.md @@ -1,9 +1,30 @@ +
+ # Laravel Data Sync Laravel utility to keep records synced between environments through source control -## Installation & Usage -- Via composer: `composer require distinctm/laravel-data-sync` +## Installation +You can install this package via composer: +```bash +composer require distinctm/laravel-data-sync +``` + +Or add this line in your `composer.json`, inside of the `require` section: + +``` json +{ + "require": { + "distinctm/laravel-data-sync": "^1.0", + } +} +``` +then run ` composer install ` + +## Usage - Run `php artisan vendor:publish --provider="distinctm\LaravelDataSync\DataSyncBaseServiceProvider" --tag="data-sync-config"` to publish config file. Specify directory for sync data files (default is a new sync directory in the project root) - Create a JSON file for each model, using the model name as the filename. Example: Product.json would update the Product model - Use nested arrays in place of hardcoded IDs for relationships diff --git a/src/Console/Commands/Sync.php b/src/Console/Commands/Sync.php index a25b1c2..0594a6d 100644 --- a/src/Console/Commands/Sync.php +++ b/src/Console/Commands/Sync.php @@ -17,9 +17,9 @@ class Sync extends Command $model = $this->option('model'); $this->info('Updating Models with sync data files'); - + (new Updater($path, $model))->run(); - + $this->comment('Data sync completed'); } } diff --git a/src/DataSyncBaseServiceProvider.php b/src/DataSyncBaseServiceProvider.php index e6294d6..ecf89f0 100644 --- a/src/DataSyncBaseServiceProvider.php +++ b/src/DataSyncBaseServiceProvider.php @@ -23,7 +23,7 @@ class DataSyncBaseServiceProvider extends ServiceProvider protected function registerPublishing() { $this->publishes([ - __DIR__ . '/../config/data-sync.php' => config_path('data-sync.php'), + __DIR__.'/../config/data-sync.php' => config_path('data-sync.php'), ], 'data-sync-config'); } } diff --git a/src/Exceptions/ErrorUpdatingModelException.php b/src/Exceptions/ErrorUpdatingModelException.php index c974a89..2518171 100644 --- a/src/Exceptions/ErrorUpdatingModelException.php +++ b/src/Exceptions/ErrorUpdatingModelException.php @@ -7,10 +7,10 @@ use Throwable; class ErrorUpdatingModelException extends Exception { - public function __construct(string $message = "", int $code = 0, Throwable $previous = null) + public function __construct(string $message = '', int $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->message = "Error updating the {$message} model."; } -} \ No newline at end of file +} diff --git a/src/Exceptions/FileDirectoryNotFoundException.php b/src/Exceptions/FileDirectoryNotFoundException.php index ba47abd..ed60c87 100644 --- a/src/Exceptions/FileDirectoryNotFoundException.php +++ b/src/Exceptions/FileDirectoryNotFoundException.php @@ -7,4 +7,4 @@ use Exception; class FileDirectoryNotFoundException extends Exception { protected $message = 'Specified sync file directory does not exist'; -} \ No newline at end of file +} diff --git a/src/Exceptions/NoCriteriaException.php b/src/Exceptions/NoCriteriaException.php index e5368d7..dc6c44d 100644 --- a/src/Exceptions/NoCriteriaException.php +++ b/src/Exceptions/NoCriteriaException.php @@ -7,4 +7,4 @@ use Exception; class NoCriteriaException extends Exception { protected $message = 'No criteria/attributes detected'; -} \ No newline at end of file +} diff --git a/src/Exceptions/NoRecordsInvalidJSONException.php b/src/Exceptions/NoRecordsInvalidJSONException.php index a59aac8..f5ebb36 100644 --- a/src/Exceptions/NoRecordsInvalidJSONException.php +++ b/src/Exceptions/NoRecordsInvalidJSONException.php @@ -7,10 +7,10 @@ use Throwable; class NoRecordsInvalidJSONException extends Exception { - public function __construct(string $message = "", int $code = 0, Throwable $previous = null) + public function __construct(string $message = '', int $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->message = "No records or invalid JSON for {$message} model."; } -} \ No newline at end of file +} diff --git a/src/Updater.php b/src/Updater.php index 80948de..9f1cbf0 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -2,17 +2,17 @@ namespace distinctm\LaravelDataSync; +use distinctm\LaravelDataSync\Exceptions\ErrorUpdatingModelException; use distinctm\LaravelDataSync\Exceptions\FileDirectoryNotFoundException; use distinctm\LaravelDataSync\Exceptions\NoCriteriaException; use distinctm\LaravelDataSync\Exceptions\NoRecordsInvalidJSONException; use Illuminate\Support\Collection; use Illuminate\Support\Facades\File; -use distinctm\LaravelDataSync\Exceptions\ErrorUpdatingModelException; class Updater { /** - * Get files in sync directory + * Get files in sync directory. * * @param string|null $path * @param string|null $model @@ -26,7 +26,7 @@ class Updater } /** - * Execute syncModel for each file + * Execute syncModel for each file. * * @return mixed */ @@ -46,12 +46,13 @@ class Updater } /** - * Parse each record for criteria/values and update/create model + * Parse each record for criteria/values and update/create model. * * @param string $file * - * @return \Illuminate\Support\Collection * @throws \distinctm\LaravelDataSync\Exceptions\NoRecordsInvalidJSONException + * + * @return \Illuminate\Support\Collection */ protected function syncModel(string $file) { @@ -74,28 +75,29 @@ class Updater } /** - * Get directory path for sync files + * Get directory path for sync files. * * @param $path * - * @return string * @throws \distinctm\LaravelDataSync\Exceptions\FileDirectoryNotFoundException + * + * @return string */ protected function getDirectory($path) { $directory = $path ?? config('data-sync.path', base_path('sync')); if (!file_exists($directory)) { - throw new FileDirectoryNotFoundException; + throw new FileDirectoryNotFoundException(); } return $directory; } /** - * Get list of files in directory + * Get list of files in directory. * - * @param string $directory + * @param string $directory * @param string|null $model * * @return \Illuminate\Support\Collection @@ -103,11 +105,11 @@ class Updater protected function getFiles(string $directory, $model = null) { if ($model) { - return Collection::wrap($directory . '/' . $model . '.json'); + return Collection::wrap($directory.'/'.$model.'.json'); } return collect(File::files($directory)) - ->filter(function($file) { + ->filter(function ($file) { return pathinfo($file, PATHINFO_EXTENSION) == 'json'; })->map(function ($path) { return $path->getPathname(); @@ -115,36 +117,38 @@ class Updater } /** - * Sort Models by pre-configured order + * Sort Models by pre-configured order. * * @param \Illuminate\Support\Collection $files + * * @return \Illuminate\Support\Collection */ protected function sortModels(\Illuminate\Support\Collection $files) { - if(empty(config('data-sync.order'))) { + if (empty(config('data-sync.order'))) { return $files; } - - return $files->sortBy(function($file) use ($files) { + + return $files->sortBy(function ($file) use ($files) { $filename = pathinfo($file, PATHINFO_FILENAME); - + $order = array_search( - studly_case($filename), + studly_case($filename), config('data-sync.order') ); - + return $order !== false ? $order : (count($files) + 1); }); } /** - * Filter record criteria + * Filter record criteria. * * @param object $record * - * @return \Illuminate\Support\Collection * @throws \distinctm\LaravelDataSync\Exceptions\NoCriteriaException + * + * @return \Illuminate\Support\Collection */ protected function getCriteria(object $record) { @@ -153,7 +157,7 @@ class Updater }); if ($criteria->count() == 0) { - throw new NoCriteriaException; + throw new NoCriteriaException(); } return $criteria->mapWithKeys(function ($value, $key) { @@ -162,7 +166,7 @@ class Updater } /** - * Filter record values + * Filter record values. * * @param object $record * @@ -184,7 +188,7 @@ class Updater } /** - * Returns model name for file + * Returns model name for file. * * @param string $name * @@ -192,16 +196,17 @@ class Updater */ protected function getModel(string $name) { - return '\\App\\' . studly_case(pathinfo($name, PATHINFO_FILENAME)); + return '\\App\\'.studly_case(pathinfo($name, PATHINFO_FILENAME)); } /** - * Parses JSON from file and returns collection + * Parses JSON from file and returns collection. * * @param string $file * - * @return \Illuminate\Support\Collection * @throws \distinctm\LaravelDataSync\Exceptions\NoRecordsInvalidJSONException + * + * @return \Illuminate\Support\Collection */ protected function getRecords(string $file) { @@ -215,11 +220,11 @@ class Updater } /** - * Check if column is criteria for a condition match + * Check if column is criteria for a condition match. * * @param string $key * - * @return boolean + * @return bool */ protected function isCriteria($key) { @@ -227,7 +232,7 @@ class Updater } /** - * Return ID for nested key-value pairs + * Return ID for nested key-value pairs. * * @param string $key * @param object $values @@ -239,7 +244,6 @@ class Updater $model = $this->getModel($key); $values = collect($values)->mapWithKeys(function ($value, $column) { - if (is_object($value)) { return $this->resolveId($column, $value); } @@ -247,11 +251,11 @@ class Updater return [$column => $value]; })->toArray(); - return [$key . '_id' => $model::where($values)->first()->id]; + return [$key.'_id' => $model::where($values)->first()->id]; } /** - * Detect nested objects and resolve them + * Detect nested objects and resolve them. * * @param \Illuminate\Support\Collection $record * @@ -267,5 +271,4 @@ class Updater return [$key => $value]; })->toArray(); } - } diff --git a/tests/Roles.php b/tests/Roles.php index 2a876aa..87fbb73 100644 --- a/tests/Roles.php +++ b/tests/Roles.php @@ -14,4 +14,4 @@ class Roles extends Model { return $this->belongsTo(Supervisor::class); } -} \ No newline at end of file +} diff --git a/tests/Supervisor.php b/tests/Supervisor.php index 1272458..ed12a87 100644 --- a/tests/Supervisor.php +++ b/tests/Supervisor.php @@ -14,4 +14,4 @@ class Supervisor extends Model { return $this->hasMany(Roles::class); } -} \ No newline at end of file +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 9dfd32a..e2a6b04 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -11,8 +11,8 @@ class TestCase extends \Orchestra\Testbench\TestCase { $app['config']->set('database.default', 'testdb'); $app['config']->set('database.connections.testdb', [ - 'driver' => 'sqlite', - 'database' => ':memory:' + 'driver' => 'sqlite', + 'database' => ':memory:', ]); } @@ -32,4 +32,4 @@ class TestCase extends \Orchestra\Testbench\TestCase $table->string('category')->nullable(); }); } -} \ No newline at end of file +} diff --git a/tests/Unit/UpdaterTest.php b/tests/Unit/UpdaterTest.php index e4f41dc..9c5cb24 100644 --- a/tests/Unit/UpdaterTest.php +++ b/tests/Unit/UpdaterTest.php @@ -2,16 +2,16 @@ namespace distinctm\LaravelDataSync\Tests; +use distinctm\LaravelDataSync\Exceptions\ErrorUpdatingModelException; use distinctm\LaravelDataSync\Tests\Fakes\UpdaterFake; use Exception; -use distinctm\LaravelDataSync\Exceptions\ErrorUpdatingModelException; class UpdaterTest extends TestCase { /** @test */ public function it_adds_roles_to_the_database() { - $updater = new UpdaterFake(__DIR__ . '/../test-data', 'roles'); + $updater = new UpdaterFake(__DIR__.'/../test-data', 'roles'); $updater->run(); @@ -23,7 +23,7 @@ class UpdaterTest extends TestCase /** @test */ public function it_can_default_to_configuration() { - config()->set('data-sync.path', __DIR__ . '/../test-data'); + config()->set('data-sync.path', __DIR__.'/../test-data'); $updater = new UpdaterFake(); @@ -37,10 +37,10 @@ class UpdaterTest extends TestCase /** @test */ public function it_can_update_an_existing_record() { - config()->set('data-sync.path', __DIR__ . '/../test-data'); + config()->set('data-sync.path', __DIR__.'/../test-data'); (new UpdaterFake())->run(); - config()->set('data-sync.path', __DIR__ . '/../test-data/valid'); + config()->set('data-sync.path', __DIR__.'/../test-data/valid'); (new UpdaterFake())->run(); $this->assertDatabaseHas('roles', ['category' => 'changed']); @@ -55,7 +55,7 @@ class UpdaterTest extends TestCase 'name' => 'CEO', ]); - config()->set('data-sync.path', __DIR__ . '/../test-data/relationship', 'roles'); + config()->set('data-sync.path', __DIR__.'/../test-data/relationship', 'roles'); (new UpdaterFake())->run(); $this->assertEquals($supervisor->id, Roles::first()->supervisor_id); @@ -69,21 +69,19 @@ class UpdaterTest extends TestCase new UpdaterFake(); $this->fail('exception was thrown'); - } catch (Exception $e) { $this->assertEquals('Specified sync file directory does not exist', $e->getMessage()); } } - + /** @test */ public function invalid_json_throws_an_exception() { try { - $updater = new UpdaterFake(__DIR__ . '/../test-data/invalid-json'); + $updater = new UpdaterFake(__DIR__.'/../test-data/invalid-json'); $updater->run(); $this->fail('exception was thrown'); - } catch (Exception $e) { $this->assertContains('No records or invalid JSON for', $e->getMessage()); } @@ -93,13 +91,12 @@ class UpdaterTest extends TestCase public function the_json_must_contain_a_key_with_an_underscore() { try { - $updater = new UpdaterFake(__DIR__ . '/../test-data/no-criteria'); + $updater = new UpdaterFake(__DIR__.'/../test-data/no-criteria'); $updater->run(); $this->fail('exception was thrown'); - } catch (Exception $e) { - $this->assertEquals('No criteria/attributes detected', $e->getMessage()); + $this->assertEquals('No criteria/attributes detected', $e->getMessage()); } } @@ -108,10 +105,10 @@ class UpdaterTest extends TestCase { config()->set('data-sync.order', [ 'Supervisor', - 'Roles' + 'Roles', ]); - $updater = new UpdaterFake(__DIR__ . '/../test-data/ordered'); + $updater = new UpdaterFake(__DIR__.'/../test-data/ordered'); $updater->run(); $this->assertDatabaseHas('roles', ['slug' => 'update-student-records']); @@ -123,21 +120,21 @@ class UpdaterTest extends TestCase { config()->set('data-sync.order', [ 'Roles', - 'Supervisor' + 'Supervisor', ]); $this->expectException(ErrorUpdatingModelException::class); - - $updater = new UpdaterFake(__DIR__ . '/../test-data/ordered'); + + $updater = new UpdaterFake(__DIR__.'/../test-data/ordered'); $updater->run(); } /** @test */ public function it_ignores_non_json_files() { - $updater = new UpdaterFake(__DIR__ . '/../test-data/not-json'); + $updater = new UpdaterFake(__DIR__.'/../test-data/not-json'); $updater->run(); $this->assertDatabaseMissing('roles', ['slug' => 'update-student-records']); } -} \ No newline at end of file +} diff --git a/tests/fakes/UpdaterFake.php b/tests/fakes/UpdaterFake.php index 070616d..a3e3685 100644 --- a/tests/fakes/UpdaterFake.php +++ b/tests/fakes/UpdaterFake.php @@ -8,8 +8,8 @@ class UpdaterFake extends Updater { protected function getModel(string $name) { - return '\\distinctm\\LaravelDataSync\\Tests\\' . studly_case( + return '\\distinctm\\LaravelDataSync\\Tests\\'.studly_case( pathinfo($name, PATHINFO_FILENAME) ); } -} \ No newline at end of file +}