Skip to content

Commit db5f435

Browse files
committed
import command
1 parent bd0b6c5 commit db5f435

File tree

3 files changed

+176
-2
lines changed

3 files changed

+176
-2
lines changed

src/Commands/ExportCommand.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function handle(): void
3333
);
3434
$path = $this->argument('path') ?: text(
3535
'Where do you want to save the file?',
36-
'E.g. storage/app/exports'
36+
'E.g. storage/app/exports',
3737
);
3838
$format = $this->option('format') ?: select(
3939
label: 'Which format do you want to export?',
@@ -121,7 +121,7 @@ class_exists($class)
121121
}
122122
});
123123

124-
return $exporters;
124+
return array_combine($exporters, $exporters);
125125
}
126126

127127
protected function resolveExporterNamespace(string $path): string

src/Commands/ImportCommand.php

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<?php
2+
3+
namespace Botble\DataSynchronize\Commands;
4+
5+
use Botble\DataSynchronize\Importer\Importer;
6+
use Illuminate\Console\Command;
7+
use Illuminate\Contracts\Console\PromptsForMissingInput;
8+
use Illuminate\Support\Facades\Storage;
9+
10+
use function Laravel\Prompts\search;
11+
use function Laravel\Prompts\text;
12+
13+
use SplFileInfo;
14+
15+
use Symfony\Component\Console\Attribute\AsCommand;
16+
use Symfony\Component\Console\Input\InputArgument;
17+
use Symfony\Component\Console\Input\InputOption;
18+
use Symfony\Component\Finder\Finder;
19+
20+
#[AsCommand(name: 'data-synchronize:import', description: 'Import data from Excel/CSV file')]
21+
class ImportCommand extends Command implements PromptsForMissingInput
22+
{
23+
public function handle(): void
24+
{
25+
$importer = $this->argument('importer') ?: search(
26+
label: 'Which importer do you want to use?',
27+
options: fn (string $value) => array_filter(
28+
$this->possibleImporters(),
29+
fn (string $importer) => str_contains(strtolower($importer), strtolower($value))
30+
),
31+
);
32+
$path = $this->argument('path') ?: text(
33+
'Where is the source Excel/CSV file?',
34+
);
35+
$limit = (int) $this->option('limit') ?: 100;
36+
37+
if (! file_exists($path)) {
38+
$this->components->error('File does not exist');
39+
40+
exit(self::FAILURE);
41+
}
42+
43+
if (! class_exists($importer)) {
44+
$this->components->error('Importer class does not exist');
45+
46+
exit(self::FAILURE);
47+
}
48+
49+
$importer = new $importer();
50+
51+
if (! $importer instanceof Importer) {
52+
$this->components->error('Importer class must be an instance of ' . Importer::class);
53+
54+
exit(self::FAILURE);
55+
}
56+
57+
$basename = basename($path);
58+
$storage = Storage::disk('local');
59+
$storagePath = config('packages.data-synchronize.data-synchronize.storage.path');
60+
$filePath = sprintf('%s/%s', $storagePath, $basename);
61+
62+
$storage->put($filePath, file_get_contents($path));
63+
64+
$this->validateData($importer, $basename, $limit);
65+
66+
$this->importData($importer, $basename, $limit);
67+
68+
$storage->delete($filePath);
69+
70+
exit(self::SUCCESS);
71+
}
72+
73+
protected function validateData(Importer $importer, string $basename, int $limit = 100): void
74+
{
75+
$offset = 0;
76+
77+
$this->components->info('Validating data...');
78+
79+
do {
80+
$response = $importer->validate($basename, $offset, $limit);
81+
$offset = $response->getNextOffset();
82+
83+
$this->components->info("Validated data from {$response->getFromOffset()} to {$response->getNextOffset()}");
84+
} while ($response->getNextOffset() < $response->total);
85+
86+
$this->components->info('Validated data successfully');
87+
}
88+
89+
protected function importData(Importer $importer, string $basename, int $limit = 100): void
90+
{
91+
$this->components->info('Importing data...');
92+
93+
$total = 0;
94+
$offset = 0;
95+
96+
do {
97+
$response = $importer->import($basename, $offset, $limit);
98+
$offset = $response->getNextOffset();
99+
$total += $response->imported;
100+
101+
$from = $response->getFromOffset();
102+
$to = $response->getNextOffset();
103+
104+
if ($from > $to) {
105+
if ($total > 0) {
106+
$this->components->info($importer->getDoneMessage($total));
107+
} else {
108+
$this->components->info('Your data is up to date');
109+
}
110+
} else {
111+
$this->components->info("Imported {$importer->getLabel()} from {$from} to {$to}");
112+
}
113+
} while ($from <= $to);
114+
}
115+
116+
protected function getOptions(): array
117+
{
118+
return [
119+
'limit' => ['limit', null, InputOption::VALUE_OPTIONAL, 'The limit of records to import'],
120+
];
121+
}
122+
123+
protected function getArguments(): array
124+
{
125+
return [
126+
['importer', InputArgument::OPTIONAL, 'The exporter class name'],
127+
['path', InputArgument::OPTIONAL, 'The path to the source Excel/CSV file'],
128+
];
129+
}
130+
131+
protected function promptForMissingArgumentsUsing(): array
132+
{
133+
return [
134+
'importer' => ['What is the importer class name?', 'E.g. Botble\Blog\Importers\PostImporter'],
135+
'path' => ['Where is the source Excel/CSV file?', 'E.g. ~/Downloads/posts.xlsx'],
136+
];
137+
}
138+
139+
protected function possibleImporters(): array
140+
{
141+
$importers = [];
142+
143+
collect(
144+
Finder::create()
145+
->files()
146+
->in(platform_path())
147+
->name('*.php')
148+
->contains(Importer::class)
149+
)
150+
->map(function (SplFileInfo $file) use (&$importers) {
151+
$class = $this->resolveExporterNamespace($file->getPathname());
152+
153+
if (
154+
class_exists($class)
155+
&& is_subclass_of($class, Importer::class)
156+
) {
157+
$importers[] = $class;
158+
}
159+
});
160+
161+
return array_combine($importers, $importers);
162+
}
163+
164+
protected function resolveExporterNamespace(string $path): string
165+
{
166+
$content = file_get_contents($path);
167+
$namespace = str($content)->after('namespace ')->before(';')->trim();
168+
$basename = basename($path, '.php');
169+
170+
return $namespace . '\\' . $basename;
171+
}
172+
}

src/Providers/DataSynchronizeServiceProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Botble\DataSynchronize\Commands\ExportCommand;
1111
use Botble\DataSynchronize\Commands\ExportControllerMakeCommand;
1212
use Botble\DataSynchronize\Commands\ExporterMakeCommand;
13+
use Botble\DataSynchronize\Commands\ImportCommand;
1314
use Botble\DataSynchronize\Commands\ImportControllerMakeCommand;
1415
use Botble\DataSynchronize\Commands\ImporterMakeCommand;
1516
use Botble\DataSynchronize\PanelSections\ExportPanelSection;
@@ -40,6 +41,7 @@ public function boot(): void
4041
ExportControllerMakeCommand::class,
4142
ClearChunksCommand::class,
4243
ExportCommand::class,
44+
ImportCommand::class,
4345
]);
4446

4547
$this->app->afterResolving(Schedule::class, function (Schedule $schedule) {

0 commit comments

Comments
 (0)