Skip to content

Commit 7c5db73

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

File tree

5 files changed

+628
-0
lines changed

5 files changed

+628
-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: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
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+
public function register(): void
37+
{
38+
$this->app->extend('config', function (Repository $config, Application $app) {
39+
$dbToolsConfig = $config->get('db-tools', []);
40+
$dbToolsConfig = ['db_tools' => $dbToolsConfig];
41+
42+
$processor = new Processor();
43+
$configuration = new DbToolsConfiguration(false, true);
44+
45+
$dbToolsConfig = $processor->processConfiguration($configuration, $dbToolsConfig);
46+
$dbToolsConfig = DbToolsConfiguration::fixLegacyOptions($dbToolsConfig);
47+
$dbToolsConfig = DbToolsConfiguration::appendPostConfig($dbToolsConfig);
48+
49+
$config->set('db-tools', $dbToolsConfig);
50+
51+
return $config;
52+
});
53+
54+
$this->app->singleton(DatabaseSessionRegistry::class, function (Application $app) {
55+
return new LaravelDatabaseSessionRegistry($app->make('db'));
56+
});
57+
58+
$this->app->singleton(ConfigurationRegistry::class, function (Application $app) {
59+
/** @var Repository $config */
60+
$config = $app->make('config');
61+
62+
$defaultConfig = new Configuration(
63+
backupBinary: $config->get('db-tools.backup_binary'),
64+
backupExcludedTables: $config->get('db-tools.backup_excluded_tables'),
65+
backupExpirationAge: $config->get('db-tools.backup_expiration_age'),
66+
backupOptions: $config->get('db-tools.backup_options'),
67+
backupTimeout: $config->get('db-tools.backup_timeout'),
68+
restoreBinary: $config->get('db-tools.restore_binary'),
69+
restoreOptions: $config->get('db-tools.restore_options'),
70+
restoreTimeout: $config->get('db-tools.restore_timeout'),
71+
storageDirectory: $config->get('db-tools.storage_directory', $app->storagePath('db_tools')),
72+
storageFilenameStrategy: $config->get('db-tools.storage_filename_strategy'),
73+
);
74+
75+
$connectionConfigs = [];
76+
foreach ($config->get('db-tools.connections', []) as $name => $data) {
77+
$connectionConfigs[$name] = new Configuration(
78+
backupBinary: $data['backup_binary'] ?? null,
79+
backupExcludedTables: $data['backup_excluded_tables'] ?? null,
80+
backupExpirationAge: $data['backup_expiration_age'] ?? null,
81+
backupOptions: $data['backup_options'] ?? null,
82+
backupTimeout: $data['backup_timeout'] ?? null,
83+
restoreBinary: $data['restore_binary'] ?? null,
84+
restoreOptions: $data['restore_options'] ?? null,
85+
restoreTimeout: $data['restore_timeout'] ?? null,
86+
parent: $defaultConfig,
87+
storageDirectory: $data['storage_directory'] ?? null,
88+
storageFilenameStrategy: $data['storage_filename_strategy'] ?? null,
89+
);
90+
}
91+
92+
return new ConfigurationRegistry(
93+
$defaultConfig,
94+
$connectionConfigs,
95+
$config->get('database.default')
96+
);
97+
});
98+
99+
$this->app->singleton(Storage::class, function (Application $app) {
100+
/** @var ConfigurationRegistry $configRegistry */
101+
$configRegistry = $app->make(ConfigurationRegistry::class);
102+
$connections = $configRegistry->getConnectionConfigAll();
103+
104+
// Register filename strategies.
105+
$strategies = [];
106+
foreach ($connections as $connectionName => $connection) {
107+
$strategyId = $connection->getStorageFilenameStrategy();
108+
109+
if ($strategyId === null || $strategyId === 'default' || $strategyId === 'datetime') {
110+
$strategy = new DefaultFilenameStrategy();
111+
} elseif ($app->bound($strategyId)) {
112+
$strategy = $app->make($strategyId);
113+
} elseif (\class_exists($strategyId)) {
114+
if (!\is_subclass_of($strategyId, FilenameStrategyInterface::class)) {
115+
throw new \InvalidArgumentException(\sprintf(
116+
'"db-tools.connections.%s.filename_strategy": class "%s" does not implement "%s"',
117+
$connectionName,
118+
$strategyId,
119+
FilenameStrategyInterface::class
120+
));
121+
}
122+
$strategy = $app->make($strategyId);
123+
} else {
124+
throw new \InvalidArgumentException(\sprintf(
125+
'"db-tools.connections.%s.filename_strategy": class or service "%s" does not exist or is not registered in container',
126+
$connectionName,
127+
$strategyId
128+
));
129+
}
130+
131+
$strategies[$connectionName] = $strategy;
132+
}
133+
134+
return new Storage($configRegistry, $strategies);
135+
});
136+
137+
$this->app->resolving(
138+
AnonymizatorFactory::class,
139+
function (AnonymizatorFactory $factory, Application $app): void {
140+
/** @var Repository $config */
141+
$config = $app->make('config');
142+
143+
foreach ($config->get('db-tools.anonymization_files', []) as $connectionName => $file) {
144+
// 0 is not a good index for extension, this fails for false and 0.
145+
if (!($pos = \strrpos($file, '.'))) {
146+
throw new ConfigurationException(\sprintf(
147+
"File extension cannot be guessed for \"%s\" file path.", $file
148+
));
149+
}
150+
151+
$ext = \substr($file, $pos + 1);
152+
$loader = match ($ext) {
153+
'php' => new PhpFileLoader($file, $connectionName),
154+
'yml', 'yaml' => new YamlLoader($file, $connectionName),
155+
default => throw new ConfigurationException(\sprintf(
156+
"File extension \"%s\" is unsupported (given path: \"%s\").", $ext, $file
157+
)),
158+
};
159+
160+
$factory->addConfigurationLoader($loader);
161+
}
162+
163+
foreach ($config->get('db-tools.anonymization', []) as $connectionName => $array) {
164+
$factory->addConfigurationLoader(new ArrayLoader($array, $connectionName));
165+
}
166+
}
167+
);
168+
169+
$this->app
170+
->when(AnonymizerRegistry::class)
171+
->needs('$paths')
172+
->give(function (Application $app) {
173+
// Validate user-given anonymizer paths.
174+
$anonymizerPaths = $app->make('config')->get('db-tools.anonymizer_paths');
175+
foreach ($anonymizerPaths as $path) {
176+
if (!\is_dir($path)) {
177+
throw new \InvalidArgumentException(\sprintf(
178+
'"db_tools.anonymizer_paths": path "%s" does not exist', $path
179+
));
180+
}
181+
}
182+
// Set the default anonymizer directory only if the folder
183+
// exists in order to avoid "directory does not exist" errors.
184+
$defaultDirectory = $app->basePath('app/Anonymizer');
185+
if (\is_dir($defaultDirectory)) {
186+
$anonymizerPaths[] = $defaultDirectory;
187+
}
188+
})
189+
;
190+
191+
/*$this->app->singleton(BackupperFactory::class, function (Application $app) {
192+
return new BackupperFactory(
193+
$app->make(DatabaseSessionRegistry::class),
194+
$app->make(ConfigurationRegistry::class),
195+
// Logger
196+
);
197+
});*/
198+
199+
/*$this->app->singleton(RestorerFactory::class, function (Application $app) {
200+
return new RestorerFactory(
201+
$app->make(DatabaseSessionRegistry::class),
202+
$app->make(ConfigurationRegistry::class),
203+
// Logger
204+
);
205+
});*/
206+
207+
/*$this->app->singleton(StatsProviderFactory::class, function (Application $app) {
208+
return new StatsProviderFactory($app->make(DatabaseSessionRegistry::class));
209+
});*/
210+
211+
/*$this->app
212+
->when([
213+
AnonymizatorFactory::class,
214+
BackupperFactory::class,
215+
RestorerFactory::class,
216+
StatsProviderFactory::class
217+
])
218+
->needs(LoggerInterface::class)
219+
->give(fn (Application $app) => $app->make('log'))
220+
;*/
221+
222+
$this->app
223+
->when([
224+
AnonymizeCommand::class,
225+
BackupCommand::class,
226+
RestoreCommand::class,
227+
])
228+
->needs('$connectionName')
229+
->giveConfig('database.default')
230+
;
231+
$this->app
232+
->when([
233+
CheckCommand::class,
234+
CleanCommand::class,
235+
StatsCommand::class,
236+
])
237+
->needs('$defaultConnectionName')
238+
->giveConfig('database.default')
239+
;
240+
}
241+
242+
/**
243+
* Bootstrap any package services.
244+
*/
245+
public function boot(): void
246+
{
247+
AboutCommand::add('DbToolsBundle', fn () => ['Version' => '2.0.0']);
248+
249+
$this->publishes([
250+
__DIR__ . '/Resources/config/db-tools.php' => $this->app->configPath('db-tools.php'),
251+
]);
252+
253+
if ($this->app->runningInConsole()) {
254+
$this->commands([
255+
AnonymizeCommand::class,
256+
AnonymizerListCommand::class,
257+
BackupCommand::class,
258+
CheckCommand::class,
259+
CleanCommand::class,
260+
ConfigDumpCommand::class,
261+
RestoreCommand::class,
262+
StatsCommand::class,
263+
]);
264+
}
265+
}
266+
}

0 commit comments

Comments
 (0)