Skip to content

Commit 36d1d05

Browse files
committed
WIP
1 parent 5567618 commit 36d1d05

File tree

3 files changed

+212
-4
lines changed

3 files changed

+212
-4
lines changed

config/data-extractor.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,33 @@
11
<?php
22

33
// config for NaimSolong/DataExtractor
4+
5+
use Illuminate\Foundation\Auth\User;
6+
47
return [
8+
'allow_production' => env('DATA_EXTRACTOR_PRODUCTION', false),
9+
10+
'instructions' => [
11+
[
12+
'name' => 'Default',
13+
'description' => 'Extra all user data',
14+
'source' => 'default',
15+
'export' => [
16+
'format' => 'sql',
17+
'file_name' => 'data-extractor',
18+
'file_path' => 'data-extractor',
19+
'disk' => 'local',
20+
],
21+
]
22+
],
523

24+
'source' => [
25+
'default' => [
26+
'connection' => 'mysql',
27+
'model' => User::class,
28+
'relationships' => [
29+
'mainProfile',
30+
],
31+
]
32+
]
633
];

src/Commands/DataExtractorCommand.php

Lines changed: 185 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,199 @@
33
namespace NaimSolong\DataExtractor\Commands;
44

55
use Illuminate\Console\Command;
6+
use NaimSolong\DataExtractor\Dto\Export;
67

78
class DataExtractorCommand extends Command
89
{
9-
public $signature = 'data-extractor';
10+
public $signature = 'data:extract';
1011

11-
public $description = 'My command';
12+
public $description = 'Extract data based on predefined instructions';
13+
14+
public array $instructions = [];
15+
16+
public function __construct()
17+
{
18+
$this->instructions = config('data-extractor.instructions', []);
19+
20+
parent::__construct();
21+
}
1222

1323
public function handle(): int
1424
{
15-
$this->comment('All done');
25+
if (!config('data-extractor.allow_production') && app()->environment('production')) {
26+
$this->error('Data extraction is not allowed in production environment.');
27+
return self::FAILURE;
28+
}
29+
30+
if ($this->validateInstructions() === false) {
31+
return self::FAILURE;
32+
}
33+
34+
$selectedInstructions = $this->promptInstructions();
35+
36+
$id = $this->promptModelId($selectedInstructions);
37+
38+
if ($id <= 0) {
39+
return self::FAILURE;
40+
}
41+
42+
$selectedSource = $selectedInstructions['source'] ?? null;
43+
if (!$selectedSource) {
44+
$this->error('No source specified in the selected instruction.');
45+
return self::FAILURE;
46+
}
47+
$source = config("data-extractor.source.$selectedSource", []);
48+
49+
$query = app($source['model'])
50+
->setConnection($source['connection'] ?? 'mysql')
51+
->with($source['relationships'] ?? [])
52+
->where('id', $id);
53+
54+
55+
if (!$query->exists()) {
56+
$this->error("No record found with ID {$id} in the {$source['model']} model.");
57+
return self::FAILURE;
58+
}
59+
60+
$data = $query->first();
61+
62+
$insertSql = $this->generateInsertSql($data);
63+
64+
$this->line("<info>Generated SQL Insert Statement:</info>\n$insertSql");
65+
66+
// Get loaded relationships
67+
$loadedRelations = $data->getRelations();
68+
69+
// Debug: Show loaded relations context
70+
$this->line("<comment>Loaded Relations:</comment>");
71+
foreach ($loadedRelations as $relationName => $relationData) {
72+
$this->line(" - {$relationName}: " . (is_countable($relationData) ? count($relationData) . ' records' : 'single record'));
73+
74+
$this->line("<comment>Generated SQL for relation '{$relationName}':</comment>");
75+
if(is_countable($relationData)){
76+
foreach ($relationData as $relation) {
77+
$insertSql = $this->generateInsertSql($relation);
78+
$this->line("\n$insertSql");
79+
}
80+
} else {
81+
$insertSql = $this->generateInsertSql($relationData);
82+
$this->line("\n$insertSql");
83+
}
84+
}
1685

1786
return self::SUCCESS;
1887
}
88+
89+
protected function validateInstructions(): bool
90+
{
91+
if (empty($this->instructions)) {
92+
$this->error('No instructions found in the configuration.');
93+
94+
return false;
95+
}
96+
97+
$sourceConnections = array_keys(config('data-extractor.source'));
98+
99+
foreach ($this->instructions as $instruction) {
100+
if (empty($instruction['name']) || empty($instruction['source']) || empty($instruction['export'])) {
101+
$this->error('Invalid instruction format. Each instruction must have a name, source, and export configuration.');
102+
103+
return false;
104+
}
105+
106+
if (!in_array($instruction['export']['format'], Export::FORMATS)) {
107+
$this->error('Invalid export format in instruction: ' . $instruction['name']);
108+
109+
return false;
110+
}
111+
112+
if (!isset($instruction['source']) || !is_string($instruction['source']) || !in_array($instruction['source'], $sourceConnections)) {
113+
$this->error('Invalid source specified in instruction: ' . $instruction['name']);
114+
115+
return false;
116+
}
117+
118+
$selectedSource = config("data-extractor.source.{$instruction['source']}", []);
119+
120+
if (!isset($selectedSource['model'])) {
121+
$this->error('Invalid model configuration in source: ' . $instruction['source']);
122+
123+
return false;
124+
}
125+
}
126+
127+
return true;
128+
}
129+
130+
protected function promptInstructions(): array
131+
{
132+
$this->table(
133+
['Name', 'Description', 'Export Format'],
134+
array_map(function ($instruction) {
135+
return [
136+
$instruction['name'],
137+
$instruction['description'] ?? 'N/A',
138+
$instruction['export']['format'],
139+
];
140+
}, $this->instructions)
141+
);
142+
143+
$instructionNames = array_column($this->instructions, 'name');
144+
145+
$selectedKey = array_keys($this->choice(
146+
'Select an instruction to execute',
147+
$instructionNames,
148+
null,
149+
null,
150+
true
151+
));
152+
153+
return $this->instructions[$selectedKey[0]];
154+
}
155+
156+
protected function promptModelId($instruction): int
157+
{
158+
$source = $instruction['source'] ?? null;
159+
160+
if (!$source || !is_string($source)) {
161+
$this->error('Invalid source specified in the instruction.');
162+
163+
return 0;
164+
}
165+
166+
$modelClass = config("data-extractor.source.$source.model");
167+
168+
if (!$modelClass || !class_exists($modelClass)) {
169+
$this->error('Invalid model class specified in the instruction source.');
170+
171+
return 0;
172+
}
173+
174+
$modelInstance = new $modelClass;
175+
176+
$id = $this->ask("Enter the ID of the {$modelInstance->getTable()} to extract data from");
177+
178+
return (int) $id;
179+
}
180+
181+
protected function generateInsertSql($data): string
182+
{
183+
$insertString = [];
184+
$dataArray = $data->toArray();
185+
foreach ($dataArray as $key => $value) {
186+
if (is_array($value)) {
187+
$insertString[] = "`$key` = '" . json_encode($value, JSON_UNESCAPED_UNICODE) . "'";
188+
} elseif (is_null($value)) {
189+
$insertString[] = "`$key` = NULL";
190+
} elseif (is_numeric($value)) {
191+
$insertString[] = "`$key` = $value";
192+
} elseif (is_bool($value)) {
193+
$insertString[] = "`$key` = " . ($value ? '1' : '0');
194+
} else {
195+
$insertString[] = "`$key` = '" . addslashes($value) . "'";
196+
}
197+
}
198+
199+
return "INSERT INTO `{$data->getTable()}` VALUE (" . implode(', ', $insertString) . ');';
200+
}
19201
}

src/DataExtractorServiceProvider.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ public function configurePackage(Package $package): void
1818
$package
1919
->name('data-extractor')
2020
->hasConfigFile()
21-
->hasViews()
2221
->hasMigration('create_data_extractor_table')
2322
->hasCommand(DataExtractorCommand::class);
2423
}

0 commit comments

Comments
 (0)