diff --git a/.gitignore b/.gitignore index 096382f..c1285db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ composer.lock vendor .idea -.DS_Store -Thumbs.db \ No newline at end of file +.phpunit.result.cache \ No newline at end of file diff --git a/.styleci.yml b/.styleci.yml index 413f1e4..0285f17 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -1 +1 @@ -preset: recommended +preset: laravel diff --git a/.travis.yml b/.travis.yml index b50999e..56ded61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,8 @@ language: php php: - - 5.5.9 - - 5.5 - - 5.6 - - 7.0 - - 7.1 + - 7.2 + - 7.3 env: matrix: @@ -17,4 +14,8 @@ before_script: - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source script: - - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover + - vendor/bin/phpunit + +cache: + directories: + - $HOME/.composer/cache diff --git a/CHANGELOG.md b/CHANGELOG.md index f1a24ff..4db161a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,23 @@ # Changelog + All notable changes to this project will be documented in this file. +## [2.0.0](https://github.com/pmatseykanets/artisan-io/releases/tag/v2.0.0) - 2019-10-25 + +### Changed + +- Support Laravel 6 + ## [1.0.0](https://github.com/pmatseykanets/artisan-io/releases/tag/v1.0.0) - 2017-09-05 + ### Added + - Added a service provider and Laravel 5.5 package auto discovery. ## [Unreleased](https://github.com/pmatseykanets/artisan-io/tree/HEAD) + ### Added + - Added CONTRIBUTING.md. - Added LICENSE.md. - Added CHANGELOG.md. @@ -14,16 +25,21 @@ All notable changes to this project will be documented in this file. - Added .gitattributes. ### Updated + - Updated README.md. - Updated dev dependencies. ## [0.2.0](https://github.com/pmatseykanets/artisan-io/releases/tag/v0.1.1) - 2015-09-22 + ### Added + - Added tests. - Added Travis-CI config. ### Updated + - Updated README.md. ## [0.1.0](https://github.com/pmatseykanets/artisan-io/releases/tag/v0.1.0) - 2015-07-22 -Initial release \ No newline at end of file + +Initial release. diff --git a/readme.md b/README.md similarity index 83% rename from readme.md rename to README.md index 62a28a6..3a83309 100644 --- a/readme.md +++ b/README.md @@ -1,31 +1,29 @@ # artisan-io -[![Laravel 5.1](https://img.shields.io/badge/Laravel-5.1-orange.svg)](http://laravel.com) [![StyleCI](https://styleci.io/repos/39307509/shield)](https://styleci.io/repos/39307509) [![Build Status](https://travis-ci.org/pmatseykanets/artisan-io.svg)](https://travis-ci.org/pmatseykanets/artisan-io) [![Latest Stable Version](https://poser.pugx.org/pmatseykanets/artisan-io/v/stable)](https://packagist.org/packages/pmatseykanets/artisan-io) [![License](https://poser.pugx.org/pmatseykanets/artisan-io/license)](https://packagist.org/packages/pmatseykanets/artisan-io) -This package adds data import capability to your [Laravel 5](http://laravel.com/docs/5.1) project. It contains an artisan command `import:delimited` which allows you, as the name implies, to import delimited data (CSV, TSV, etc) into your local or remote database. +This package adds data import capability to your [Laravel](http://laravel.com/) project. It contains an artisan command `import:delimited` which allows you, as the name implies, to import delimited data (CSV, TSV, etc) into your local or remote database. #### Main features: -- Supports multiple database connections (defined in [`config\database.php`](http://laravel.com/docs/5.1/database#introduction)). -- You can use either a table name or Eloquent model class to import your data. By using Eloquent model you can benefit from [mutators and accessors](http://laravel.com/docs/5.1/eloquent-mutators). -- Several import modes: +- Supports multiple database connections (defined in [`config\database.php`](http://laravel.com/docs/6.0/database#introduction)). +- You can use either a table name or Eloquent model class to import your data. By using Eloquent model you can benefit from [mutators and accessors](http://laravel.com/docs/6.0/eloquent-mutators). +- Import modes: - insert - insert-new - update - upsert - Row validation rules - ## Installation You can install the package via composer: ```bash -$ composer require pmatseykanets/artisan-io +composer require pmatseykanets/artisan-io ``` If you're using Laravel < 5.5 or if you have package auto-discovery turned off you have to manually register the service provider: @@ -50,8 +48,8 @@ protected $commands = [ ## Usage -``` -$ php artisan import:delimited --help +```bash +php artisan import:delimited --help Usage: import:delimited [options] [--] @@ -129,7 +127,7 @@ class Employee extends Model If `employees` table is empty and you'd like to populate it ```bash -$ php artisan import:delimited employee.csv "\App\Employee" -f email:0,firstname:1,lastname:2,phone:4,employed_on:3 -m insert +php artisan import:delimited employee.csv "\App\Employee" -f email:0,firstname:1,lastname:2,phone:4,employed_on:3 -m insert ``` Note: *The buity of using Eloquent model in this case is that timestamps `created_at` and `updated_at` will be populated by Eloquent automatically.* @@ -139,7 +137,7 @@ Note: *The buity of using Eloquent model in this case is that timestamps `create Now let's assume John's record is already present in the table. In order to update Jon's record and insert Jane's one you'd need to cnahge the mode and specify key field(s). ```bash -$ php artisan import:delimited employee.csv "\App\Employee" -f email:0,firstname:1,lastname:2,phone:4,employed_on:3 -m upsert -k email +php artisan import:delimited employee.csv "\App\Employee" -f email:0,firstname:1,lastname:2,phone:4,employed_on:3 -m upsert -k email ``` #### Update @@ -147,7 +145,7 @@ $ php artisan import:delimited employee.csv "\App\Employee" -f email:0,firstname If you want to just update phone numbers for existing records ```bash -$ php artisan import:delimited employee.csv "\App\Employee" -f email:0,phone:4 -m update -k email +php artisan import:delimited employee.csv "\App\Employee" -f email:0,phone:4 -m update -k email ``` ### Field definition file @@ -170,7 +168,7 @@ employed_on:3 ### Row validation rules file -A row validation rule file is simply a php file that returns an array of rules. You can any of the [available Laravel validation rules](http://laravel.com/docs/5.1/validation#available-validation-rules) +A row validation rule file is simply a php file that returns an array of rules. You can any of the [available Laravel validation rules](http://laravel.com/docs/6.0/validation#available-validation-rules) #### Example `employee.rule` @@ -186,7 +184,6 @@ return [ ]; ``` - -### License +## License The artisan-io is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) diff --git a/composer.json b/composer.json index 91c8e39..4129ce1 100644 --- a/composer.json +++ b/composer.json @@ -7,22 +7,21 @@ { "name": "Peter Matseykanets", "email": "pmatseykanets@gmail.com", - "homepage": "https://github.com/pmatseykanets", - "role": "Developer" + "homepage": "https://github.com/pmatseykanets" } ], "require": { - "php": ">=5.5.9", - "illuminate/support": "~5.1", - "illuminate/container": "~5.1", - "illuminate/database": "~5.1", - "illuminate/validation": "~5.1", - "illuminate/config": "~5.1", - "illuminate/console": "~5.1" + "php": "^7.2", + "illuminate/support": "~5.1|~6.0", + "illuminate/container": "~5.1|~6.0", + "illuminate/database": "~5.1|~6.0", + "illuminate/validation": "~5.1|~6.0", + "illuminate/config": "~5.1|~6.0", + "illuminate/console": "~5.1|~6.0" }, "require-dev": { - "phpunit/phpunit": "^4.8", - "mockery/mockery": "^0.9.5" + "phpunit/phpunit": "^8.0", + "mockery/mockery": "^1.0" }, "autoload": { "psr-4": { diff --git a/phpunit.xml b/phpunit.xml index 1586e2a..99c3125 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,7 +8,6 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - syntaxCheck="false" > diff --git a/src/ArtisanIoServiceProvider.php b/src/ArtisanIoServiceProvider.php index e99c5a4..fb16d66 100644 --- a/src/ArtisanIoServiceProvider.php +++ b/src/ArtisanIoServiceProvider.php @@ -2,8 +2,8 @@ namespace ArtisanIo; -use ArtisanIo\Console\ImportDelimitedCommand; use Illuminate\Support\ServiceProvider; +use ArtisanIo\Console\ImportDelimitedCommand; class ArtisanIoServiceProvider extends ServiceProvider { diff --git a/src/Console/ImportDelimitedCommand.php b/src/Console/ImportDelimitedCommand.php index f5a53ea..d7bb510 100644 --- a/src/Console/ImportDelimitedCommand.php +++ b/src/Console/ImportDelimitedCommand.php @@ -2,12 +2,12 @@ namespace ArtisanIo\Console; +use Illuminate\Support\Str; +use Illuminate\Console\Command; use ArtisanIo\Delimited\BaseImport; use ArtisanIo\Delimited\ModelImport; use ArtisanIo\Delimited\TableImport; -use Illuminate\Console\Command; use Illuminate\Console\ConfirmableTrait; -use Illuminate\Support\Str; class ImportDelimitedCommand extends Command { @@ -55,7 +55,7 @@ class ImportDelimitedCommand extends Command */ public function handle() { - if (!$this->confirmToProceed()) { + if (! $this->confirmToProceed()) { return; } @@ -151,7 +151,7 @@ protected function parseArguments() } // Are we going to display the progress bar? - $this->showProgress = !$this->option('no-progress'); + $this->showProgress = ! $this->option('no-progress'); } /** @@ -159,7 +159,7 @@ protected function parseArguments() */ protected function reportImported() { - if (!$this->import) { + if (! $this->import) { return; } @@ -185,7 +185,7 @@ protected function abort($message, $code = 1) $this->progressRemove(); } - if (!is_null($this->import)) { + if (! is_null($this->import)) { $this->reportImported(); if ($lastLine = $this->import->getCurrentFileLine()) { diff --git a/src/Delimited/BaseImport.php b/src/Delimited/BaseImport.php index 53a45cf..22528a8 100644 --- a/src/Delimited/BaseImport.php +++ b/src/Delimited/BaseImport.php @@ -2,10 +2,10 @@ namespace ArtisanIo\Delimited; +use Illuminate\Support\Str; use Illuminate\Config\Repository as Config; use Illuminate\Contracts\Container\Container; use Illuminate\Database\DatabaseManager as DB; -use Illuminate\Support\Str; use Illuminate\Validation\Factory as Validator; abstract class BaseImport @@ -80,7 +80,7 @@ public function import() $row = $this->mapValues($values, $this->getFields()); $key = $this->mapValues($values, $this->getKeyFields()); - if (!empty($rules)) { + if (! empty($rules)) { $this->validateRow($row, $rules); } @@ -98,7 +98,7 @@ public function import() $this->upsert($row, $key); } - ++$this->imported; + $this->imported++; $this->callEventHandler($this->importedHandler); } @@ -278,7 +278,7 @@ protected function parseFields($fieldDefinitions) foreach ($fieldDefinitions as $field) { if (Str::contains($field, ':')) { - list($field, $position) = array_map('trim', explode(':', $field)); + [$field, $position] = array_map('trim', explode(':', $field)); if (false === ($position = filter_var($position, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]))) { throw new \RuntimeException("Invalid position for the field '$field'."); @@ -306,7 +306,7 @@ protected function parseKeyFields($fieldDefinitions) foreach ($fieldDefinitions as $field) { // If an explicit position is given if (Str::contains($field, ':')) { - list($field, $position) = array_map('trim', explode(':', $field)); + [$field, $position] = array_map('trim', explode(':', $field)); if (false === ($position = filter_var($position, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]))) { throw new \RuntimeException("Invalid position for the field '$field'."); @@ -316,7 +316,7 @@ protected function parseKeyFields($fieldDefinitions) continue; } // If not try to get from the corresponding import field - if (!isset($this->fields[$field])) { + if (! isset($this->fields[$field])) { throw new \RuntimeException("Invalid definition for key field '$field'."); } $positions[$field] = $this->fields[$field]; @@ -463,7 +463,7 @@ public function setValidationRulesFromFile($path) $rules = @require $filePath; - if (empty($rules) || !is_array($rules)) { + if (empty($rules) || ! is_array($rules)) { throw new \RuntimeException('Invalid rules file.'); } @@ -477,7 +477,7 @@ public function setValidationRulesFromFile($path) */ public function getKeyFields() { - if (!$this->keyFields) { + if (! $this->keyFields) { $this->keyFields = $this->fields; } @@ -491,13 +491,13 @@ public function getKeyFields() */ public function setKeyFields($value) { - if (!is_array($value)) { + if (! is_array($value)) { $fields = explode(',', $value); } $fields = array_filter(array_map('trim', $fields)); - if (empty($fields) && !empty($value)) { + if (empty($fields) && ! empty($value)) { throw new \RuntimeException('Invalid key field definition'); } @@ -525,7 +525,7 @@ public function getFields() */ public function setFields($fields) { - if (!is_array($fields)) { + if (! is_array($fields)) { $fields = explode(',', $fields); } @@ -568,7 +568,7 @@ public function setMode($mode) { $mode = strtolower(trim($mode)); - if (!in_array($mode, [ + if (! in_array($mode, [ self::MODE_UPSERT, self::MODE_UPDATE, self::MODE_INSERT, @@ -679,7 +679,7 @@ public function getExecutionTime() */ public function getCurrentFileLine() { - if (!$this->getFileIterator()) { + if (! $this->getFileIterator()) { return 0; } @@ -695,7 +695,7 @@ public function getCurrentFileLine() */ public function getBytesRead() { - if (!$this->getFileIterator()) { + if (! $this->getFileIterator()) { return 0; } @@ -721,7 +721,7 @@ public function getImportFileSize() */ public function setBeforeHandler($callback) { - if (!is_null($callback) && !is_callable($callback)) { + if (! is_null($callback) && ! is_callable($callback)) { throw new \InvalidArgumentException('Before event handler must be a valid callable (callback or object with an __invoke method), '.var_export($callback, true).' given'); } @@ -737,7 +737,7 @@ public function setBeforeHandler($callback) */ public function setAfterHandler($callback) { - if (!is_null($callback) && !is_callable($callback)) { + if (! is_null($callback) && ! is_callable($callback)) { throw new \InvalidArgumentException('After event handler must be a valid callable (callback or object with an __invoke method), '.var_export($callback, true).' given'); } @@ -753,7 +753,7 @@ public function setAfterHandler($callback) */ public function setImportedHandler($callback) { - if (!is_null($callback) && !is_callable($callback)) { + if (! is_null($callback) && ! is_callable($callback)) { throw new \InvalidArgumentException('Imported event handler must be a valid callable (callback or object with an __invoke method), '.var_export($callback, true).' given'); } @@ -773,7 +773,7 @@ public function setImportedHandler($callback) */ protected function validateFile($filePath, $message = 'File') { - if (!file_exists($filePath) || !is_readable($filePath)) { + if (! file_exists($filePath) || ! is_readable($filePath)) { throw new \RuntimeException("$message '{$filePath}' doesn't exist or is not readable."); } @@ -791,7 +791,7 @@ protected function validateFile($filePath, $message = 'File') */ private function callEventHandler($callback) { - if (!is_null($callback)) { + if (! is_null($callback)) { call_user_func($callback); } } diff --git a/src/Delimited/ModelImport.php b/src/Delimited/ModelImport.php index f22f813..9c2a327 100644 --- a/src/Delimited/ModelImport.php +++ b/src/Delimited/ModelImport.php @@ -25,11 +25,11 @@ public function getModel() public function setModel($model) { if (is_string($model)) { - if (!$this->model = $this->makeModel($model)) { + if (! $this->model = $this->makeModel($model)) { throw new \RuntimeException("Model $model doesn't exist."); } } else { - if (!$model instanceof Model) { + if (! $model instanceof Model) { throw new \RuntimeException("Model should be of type '".Model::class."'."); } $this->model = $model; @@ -112,7 +112,7 @@ protected function insertNew($row, $key) return; } - if (!$instance->exists) { + if (! $instance->exists) { $this->updateInstances($row, [$instance]); } } diff --git a/src/Delimited/TableImport.php b/src/Delimited/TableImport.php index 5fa6203..a01534a 100644 --- a/src/Delimited/TableImport.php +++ b/src/Delimited/TableImport.php @@ -27,7 +27,7 @@ public function setTargetName($targetName) */ public function setTable($table) { - if (!$this->db->connection()->getSchemaBuilder()->hasTable($table)) { + if (! $this->db->connection()->getSchemaBuilder()->hasTable($table)) { throw new \RuntimeException("Table $table doesn't exist."); } @@ -125,7 +125,7 @@ protected function insert($row) protected function validateFields($fieldDefinitions) { foreach ($fieldDefinitions as $field => $position) { - if (!$this->db->connection()->getSchemaBuilder()->hasColumn($this->table, $field)) { + if (! $this->db->connection()->getSchemaBuilder()->hasColumn($this->table, $field)) { throw new \RuntimeException("Column '$field' doesn't exist."); } } diff --git a/tests/Delimited/BaseTest.php b/tests/Delimited/BaseTest.php index 46c9ae3..b111e85 100644 --- a/tests/Delimited/BaseTest.php +++ b/tests/Delimited/BaseTest.php @@ -2,19 +2,17 @@ namespace ArtisanIo\Delimited; -use ArtisanIo\TestCase; use Mockery as m; +use ArtisanIo\TestCase; abstract class BaseTest extends TestCase { - protected $abstract; - protected $emptyFile; protected $importFile; protected $fieldFile; protected $rulesFile; - public function setUp() + public function setUp(): void { parent::setUp(); @@ -24,7 +22,7 @@ public function setUp() $this->rulesFile = __DIR__.'/import.rules'; } - public function tearDown() + public function tearDown(): void { parent::tearDown(); diff --git a/tests/Delimited/ImportTest.php b/tests/Delimited/ImportTest.php index c0707c1..9d45915 100644 --- a/tests/Delimited/ImportTest.php +++ b/tests/Delimited/ImportTest.php @@ -23,7 +23,7 @@ public function testItThrowsExceptionIfImportFileDoesNotExist() { $import = $this->getInstance(); - $this->setExpectedException('RuntimeException'); + $this->expectException('RuntimeException'); $import->setImportFile('non_existing_file.csv'); } @@ -31,7 +31,7 @@ public function testItThrowsExceptionIfImportFileIsEmpty() { $import = $this->getInstance(); - $this->setExpectedException('RuntimeException'); + $this->expectException('RuntimeException'); file_put_contents($this->emptyFile, ''); @@ -62,7 +62,7 @@ public function testItThrowsExceptionForInvalidConnectionName() $import = $this->getInstance(['config' => $config, 'db' => $db]); - $this->setExpectedException('RuntimeException'); + $this->expectException('RuntimeException'); $import->setConnectionName('none'); } @@ -97,7 +97,7 @@ public function testItThrowsExceptionForInvalidMode() { $import = $this->getInstance(); - $this->setExpectedException('RuntimeException'); + $this->expectException('RuntimeException'); $import->setMode('invalid'); } @@ -202,7 +202,7 @@ public function testItThrowsExceptionIfFieldsStringIsEmpty() { $import = $this->getInstance(); - $this->setExpectedException('RuntimeException'); + $this->expectException('RuntimeException'); $import->setFields(''); } @@ -211,7 +211,7 @@ public function testItThrowsExceptionIfFieldsStringConsistsSolelyOfCommas() { $import = $this->getInstance(); - $this->setExpectedException('RuntimeException'); + $this->expectException('RuntimeException'); $import->setFields(',,'); } @@ -220,7 +220,7 @@ public function testItThrowsExceptionIfFieldFileDoesNotExist() { $import = $this->getInstance(); - $this->setExpectedException('RuntimeException'); + $this->expectException('RuntimeException'); $import->setFieldsFromFile('none.fields'); } @@ -229,7 +229,7 @@ public function testItThrowsExceptionIfFieldFileIsEmpty() { $import = $this->getInstance(); - $this->setExpectedException('RuntimeException'); + $this->expectException('RuntimeException'); $import->setFieldsFromFile($this->emptyFile); } @@ -240,7 +240,7 @@ public function testItThrowsExceptionIfFieldFileJustContainsEmptyLinesOrWhiteSpa file_put_contents($this->fieldFile, "\n\t\n \n"); - $this->setExpectedException('RuntimeException'); + $this->expectException('RuntimeException'); $import->setFieldsFromFile($this->fieldFile); } diff --git a/tests/Delimited/ModelImportTest.php b/tests/Delimited/ModelImportTest.php index e4e5a6a..ed63678 100644 --- a/tests/Delimited/ModelImportTest.php +++ b/tests/Delimited/ModelImportTest.php @@ -2,8 +2,8 @@ namespace ArtisanIo\Delimited; -use Illuminate\Database\Eloquent\Model; use Mockery as m; +use Illuminate\Database\Eloquent\Model; class ModelImportTest extends BaseTest { @@ -35,7 +35,7 @@ public function testItThrowsExceptionIfModelDoesNotExist() $import = $this->getInstance(compact('container')); - $this->setExpectedException('RuntimeException'); + $this->expectException('RuntimeException'); $import->setTargetName($modelClass); } diff --git a/tests/Delimited/TableImportTest.php b/tests/Delimited/TableImportTest.php index 9db3f65..ce00106 100644 --- a/tests/Delimited/TableImportTest.php +++ b/tests/Delimited/TableImportTest.php @@ -26,7 +26,7 @@ public function testItThrowsExceptionIfTableDoesNotExist() $import = $this->getInstance(['db' => $db]); - $this->setExpectedException('RuntimeException'); + $this->expectException('RuntimeException'); $import->setTargetName('none'); } @@ -38,7 +38,7 @@ public function testItThrowsExceptionIfFieldDoesNotExistFromString() $import = $this->getInstance(['db' => $db]); $import->setTargetName('table'); - $this->setExpectedException('RuntimeException'); + $this->expectException('RuntimeException'); $return = $import->setFields('foo,bar,baz'); } @@ -55,7 +55,7 @@ public function testItThrowsExceptionIfFildsDoesNotExistFromFile() file_put_contents($this->fieldFile, "foo\nbar\nbaz"); - $this->setExpectedException('RuntimeException'); + $this->expectException('RuntimeException'); $return = $import->setFieldsFromFile($this->fieldFile); } @@ -189,7 +189,7 @@ protected function runImport($db, $mode, $keyFields = null) ->setTable('table') ->setFields('foo,bar'); - if (!is_null($keyFields)) { + if (! is_null($keyFields)) { $import->setKeyFields($keyFields); } diff --git a/tests/TestCase.php b/tests/TestCase.php index a0aaeea..2d668f0 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,6 +2,17 @@ namespace ArtisanIo; -class TestCase extends \PHPUnit_Framework_TestCase +use Mockery; +use PHPUnit\Framework\TestCase as BaseTestCase; + +abstract class TestCase extends BaseTestCase { + public function tearDown(): void + { + // Prevent PHPUnit complaining about risky tests + // because Mockery expectations are not counted towards assertions + if ($container = Mockery::getContainer()) { + $this->addToAssertionCount($container->mockery_getExpectationCount()); + } + } }