Skip to content

Commit d1443fb

Browse files
committed
Issue #178 - Laravel support
1 parent f904981 commit d1443fb

File tree

5 files changed

+640
-0
lines changed

5 files changed

+640
-0
lines changed

composer.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
"doctrine/doctrine-bundle": "^2.10.0",
2828
"doctrine/orm": "^2.15|^3.0",
2929
"friendsofphp/php-cs-fixer": "^3.34",
30+
"illuminate/console": "^11.0",
31+
"illuminate/database": "^11.0",
32+
"illuminate/support": "^11.0",
33+
"laravel/framework": "^11.0",
3034
"phpstan/phpstan": "^1.10",
3135
"phpunit/phpunit": "^10.4",
3236
"symfony/dependency-injection": "^6.0|^7.0",
@@ -71,5 +75,12 @@
7175
"@phpcs --dry-run",
7276
"@phpstan"
7377
]
78+
},
79+
"extra": {
80+
"laravel": {
81+
"providers": [
82+
"MakinaCorpus\\DbToolsBundle\\Bridge\\Laravel\\DbToolsServiceProvider"
83+
]
84+
}
7485
}
7586
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MakinaCorpus\DbToolsBundle\Anonymization\Config\Loader;
6+
7+
use MakinaCorpus\DbToolsBundle\Error\ConfigurationException;
8+
9+
class PhpFileLoader extends ArrayLoader
10+
{
11+
public function __construct(
12+
private string $file,
13+
string $connectionName = 'default',
14+
) {
15+
parent::__construct([], $connectionName);
16+
}
17+
18+
#[\Override]
19+
protected function getData(): array
20+
{
21+
try {
22+
$data = require $this->file;
23+
} catch (\Throwable $e) {
24+
throw new ConfigurationException(\sprintf(
25+
"An error occurred when loading \"%s\" configuration file: %s",
26+
$this->file,
27+
$e->getMessage()
28+
), 0, $e);
29+
}
30+
31+
if (!\is_array($data)) {
32+
throw new ConfigurationException(\sprintf(
33+
"File \"%s\" is not a valid PHP anonymization configuration file (must return an array).",
34+
$this->file
35+
));
36+
}
37+
38+
return $data;
39+
}
40+
}
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MakinaCorpus\DbToolsBundle\Bridge\Laravel;
6+
7+
use Illuminate\Contracts\Config\Repository;
8+
use Illuminate\Contracts\Foundation\Application;
9+
use Illuminate\Foundation\Console\AboutCommand;
10+
use Illuminate\Support\ServiceProvider;
11+
use MakinaCorpus\DbToolsBundle\Anonymization\AnonymizatorFactory;
12+
use MakinaCorpus\DbToolsBundle\Anonymization\Anonymizer\AnonymizerRegistry;
13+
use MakinaCorpus\DbToolsBundle\Anonymization\Config\Loader\ArrayLoader;
14+
use MakinaCorpus\DbToolsBundle\Anonymization\Config\Loader\PhpFileLoader;
15+
use MakinaCorpus\DbToolsBundle\Anonymization\Config\Loader\YamlLoader;
16+
use MakinaCorpus\DbToolsBundle\Bridge\Symfony\DependencyInjection\DbToolsConfiguration;
17+
use MakinaCorpus\DbToolsBundle\Command\Anonymization\AnonymizeCommand;
18+
use MakinaCorpus\DbToolsBundle\Command\Anonymization\AnonymizerListCommand;
19+
use MakinaCorpus\DbToolsBundle\Command\Anonymization\CleanCommand;
20+
use MakinaCorpus\DbToolsBundle\Command\Anonymization\ConfigDumpCommand;
21+
use MakinaCorpus\DbToolsBundle\Command\BackupCommand;
22+
use MakinaCorpus\DbToolsBundle\Command\CheckCommand;
23+
use MakinaCorpus\DbToolsBundle\Command\RestoreCommand;
24+
use MakinaCorpus\DbToolsBundle\Command\StatsCommand;
25+
use MakinaCorpus\DbToolsBundle\Configuration\Configuration;
26+
use MakinaCorpus\DbToolsBundle\Configuration\ConfigurationRegistry;
27+
use MakinaCorpus\DbToolsBundle\Database\DatabaseSessionRegistry;
28+
use MakinaCorpus\DbToolsBundle\Error\ConfigurationException;
29+
use MakinaCorpus\DbToolsBundle\Storage\DefaultFilenameStrategy;
30+
use MakinaCorpus\DbToolsBundle\Storage\FilenameStrategyInterface;
31+
use MakinaCorpus\DbToolsBundle\Storage\Storage;
32+
use Symfony\Component\Config\Definition\Processor;
33+
34+
class DbToolsServiceProvider extends ServiceProvider
35+
{
36+
#[\Override]
37+
public function register(): void
38+
{
39+
$this->app->extend('config', function (Repository $config, Application $app) {
40+
$dbToolsConfig = $config->get('db-tools', []);
41+
$dbToolsConfig = ['db_tools' => $dbToolsConfig];
42+
43+
$processor = new Processor();
44+
$configuration = new DbToolsConfiguration(false, true);
45+
46+
$dbToolsConfig = $processor->processConfiguration($configuration, $dbToolsConfig);
47+
$dbToolsConfig = DbToolsConfiguration::fixLegacyOptions($dbToolsConfig);
48+
$dbToolsConfig = DbToolsConfiguration::appendPostConfig($dbToolsConfig);
49+
50+
$config->set('db-tools', $dbToolsConfig);
51+
52+
return $config;
53+
});
54+
55+
$this->app->singleton(DatabaseSessionRegistry::class, function (Application $app) {
56+
return new IlluminateDatabaseSessionRegistry($app->make('db'));
57+
});
58+
59+
$this->app->singleton(ConfigurationRegistry::class, function (Application $app) {
60+
/** @var Repository $config */
61+
$config = $app->make('config');
62+
63+
$defaultConfig = new Configuration(
64+
backupBinary: $config->get('db-tools.backup_binary'),
65+
backupExcludedTables: $config->get('db-tools.backup_excluded_tables'),
66+
backupExpirationAge: $config->get('db-tools.backup_expiration_age'),
67+
backupOptions: $config->get('db-tools.backup_options'),
68+
backupTimeout: $config->get('db-tools.backup_timeout'),
69+
restoreBinary: $config->get('db-tools.restore_binary'),
70+
restoreOptions: $config->get('db-tools.restore_options'),
71+
restoreTimeout: $config->get('db-tools.restore_timeout'),
72+
storageDirectory: $config->get('db-tools.storage_directory', $app->storagePath('db_tools')),
73+
storageFilenameStrategy: $config->get('db-tools.storage_filename_strategy'),
74+
);
75+
76+
$connectionConfigs = [];
77+
foreach ($config->get('db-tools.connections', []) as $name => $data) {
78+
$connectionConfigs[$name] = new Configuration(
79+
backupBinary: $data['backup_binary'] ?? null,
80+
backupExcludedTables: $data['backup_excluded_tables'] ?? null,
81+
backupExpirationAge: $data['backup_expiration_age'] ?? null,
82+
backupOptions: $data['backup_options'] ?? null,
83+
backupTimeout: $data['backup_timeout'] ?? null,
84+
restoreBinary: $data['restore_binary'] ?? null,
85+
restoreOptions: $data['restore_options'] ?? null,
86+
restoreTimeout: $data['restore_timeout'] ?? null,
87+
parent: $defaultConfig,
88+
storageDirectory: $data['storage_directory'] ?? null,
89+
storageFilenameStrategy: $data['storage_filename_strategy'] ?? null,
90+
);
91+
}
92+
93+
return new ConfigurationRegistry(
94+
$defaultConfig,
95+
$connectionConfigs,
96+
$config->get('database.default')
97+
);
98+
});
99+
100+
$this->app->singleton(Storage::class, function (Application $app) {
101+
/** @var ConfigurationRegistry $configRegistry */
102+
$configRegistry = $app->make(ConfigurationRegistry::class);
103+
$connections = $configRegistry->getConnectionConfigAll();
104+
105+
// Register filename strategies.
106+
$strategies = [];
107+
foreach ($connections as $connectionName => $connection) {
108+
$strategyId = $connection->getStorageFilenameStrategy();
109+
110+
if ($strategyId === null || $strategyId === 'default' || $strategyId === 'datetime') {
111+
$strategy = new DefaultFilenameStrategy();
112+
} elseif ($app->bound($strategyId)) {
113+
$strategy = $app->make($strategyId);
114+
} elseif (\class_exists($strategyId)) {
115+
if (!\is_subclass_of($strategyId, FilenameStrategyInterface::class)) {
116+
throw new \InvalidArgumentException(\sprintf(
117+
'"db-tools.connections.%s.filename_strategy": class "%s" does not implement "%s"',
118+
$connectionName,
119+
$strategyId,
120+
FilenameStrategyInterface::class
121+
));
122+
}
123+
$strategy = $app->make($strategyId);
124+
} else {
125+
throw new \InvalidArgumentException(\sprintf(
126+
'"db-tools.connections.%s.filename_strategy": class or service "%s" does not exist or is not registered in container',
127+
$connectionName,
128+
$strategyId
129+
));
130+
}
131+
132+
$strategies[$connectionName] = $strategy;
133+
}
134+
135+
return new Storage($configRegistry, $strategies);
136+
});
137+
138+
$this->app->resolving(
139+
AnonymizatorFactory::class,
140+
function (AnonymizatorFactory $factory, Application $app): void {
141+
/** @var Repository $config */
142+
$config = $app->make('config');
143+
144+
foreach ($config->get('db-tools.anonymization_files', []) as $connectionName => $file) {
145+
// 0 is not a good index for extension, this fails for false and 0.
146+
if (!($pos = \strrpos($file, '.'))) {
147+
throw new ConfigurationException(\sprintf(
148+
"File extension cannot be guessed for \"%s\" file path.",
149+
$file
150+
));
151+
}
152+
153+
$ext = \substr($file, $pos + 1);
154+
$loader = match ($ext) {
155+
'php' => new PhpFileLoader($file, $connectionName),
156+
'yml', 'yaml' => new YamlLoader($file, $connectionName),
157+
default => throw new ConfigurationException(\sprintf(
158+
"File extension \"%s\" is unsupported (given path: \"%s\").",
159+
$ext,
160+
$file
161+
)),
162+
};
163+
164+
$factory->addConfigurationLoader($loader);
165+
}
166+
167+
foreach ($config->get('db-tools.anonymization', []) as $connectionName => $array) {
168+
$factory->addConfigurationLoader(new ArrayLoader($array, $connectionName));
169+
}
170+
}
171+
);
172+
173+
$this->app
174+
->when(AnonymizerRegistry::class)
175+
->needs('$paths')
176+
->give(function (Application $app) {
177+
// Validate user-given anonymizer paths.
178+
$anonymizerPaths = $app->make('config')->get('db-tools.anonymizer_paths');
179+
foreach ($anonymizerPaths as $path) {
180+
if (!\is_dir($path)) {
181+
throw new \InvalidArgumentException(\sprintf(
182+
'"db_tools.anonymizer_paths": path "%s" does not exist',
183+
$path
184+
));
185+
}
186+
}
187+
// Set the default anonymizer directory only if the folder
188+
// exists in order to avoid "directory does not exist" errors.
189+
$defaultDirectory = $app->basePath('app/Anonymizer');
190+
if (\is_dir($defaultDirectory)) {
191+
$anonymizerPaths[] = $defaultDirectory;
192+
}
193+
})
194+
;
195+
196+
/*$this->app->singleton(BackupperFactory::class, function (Application $app) {
197+
return new BackupperFactory(
198+
$app->make(DatabaseSessionRegistry::class),
199+
$app->make(ConfigurationRegistry::class),
200+
// Logger
201+
);
202+
});*/
203+
204+
/*$this->app->singleton(RestorerFactory::class, function (Application $app) {
205+
return new RestorerFactory(
206+
$app->make(DatabaseSessionRegistry::class),
207+
$app->make(ConfigurationRegistry::class),
208+
// Logger
209+
);
210+
});*/
211+
212+
/*$this->app->singleton(StatsProviderFactory::class, function (Application $app) {
213+
return new StatsProviderFactory($app->make(DatabaseSessionRegistry::class));
214+
});*/
215+
216+
/*$this->app
217+
->when([
218+
AnonymizatorFactory::class,
219+
BackupperFactory::class,
220+
RestorerFactory::class,
221+
StatsProviderFactory::class
222+
])
223+
->needs(LoggerInterface::class)
224+
->give(fn (Application $app) => $app->make('log'))
225+
;*/
226+
227+
$this->app
228+
->when([
229+
AnonymizeCommand::class,
230+
BackupCommand::class,
231+
RestoreCommand::class,
232+
])
233+
->needs('$connectionName')
234+
->giveConfig('database.default')
235+
;
236+
$this->app
237+
->when([
238+
CheckCommand::class,
239+
CleanCommand::class,
240+
StatsCommand::class,
241+
])
242+
->needs('$defaultConnectionName')
243+
->giveConfig('database.default')
244+
;
245+
}
246+
247+
/**
248+
* Bootstrap any package services.
249+
*/
250+
public function boot(): void
251+
{
252+
AboutCommand::add('DbToolsBundle', fn () => ['Version' => '2.0.0']);
253+
254+
$this->publishes([
255+
__DIR__ . '/Resources/config/db-tools.php' => $this->app->configPath('db-tools.php'),
256+
]);
257+
258+
if ($this->app->runningInConsole()) {
259+
$this->commands([
260+
AnonymizeCommand::class,
261+
AnonymizerListCommand::class,
262+
BackupCommand::class,
263+
CheckCommand::class,
264+
CleanCommand::class,
265+
ConfigDumpCommand::class,
266+
RestoreCommand::class,
267+
StatsCommand::class,
268+
]);
269+
}
270+
}
271+
}

0 commit comments

Comments
 (0)