Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow pre existing connections to be used #20

58 changes: 27 additions & 31 deletions src/App.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

namespace Quidco\DbSampler;

use Doctrine\DBAL\Connection;
Expand All @@ -7,6 +8,9 @@
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Quidco\DbSampler\DatabaseSchema\TablesList;
use Quidco\DbSampler\DatabaseSchema\ViewsList;
use Quidco\DbSampler\Configuration\MigrationConfigurationCollection;

/**
* Dependency container / app for DbSampler
Expand All @@ -27,6 +31,16 @@ class App extends Container implements DatabaseConnectionFactoryInterface, Logge
*/
protected $configuredMigrationNames = [];

/**
* @var MigrationConfigurationCollection
*/
private $databaseConfigurationCollection;

public function __construct(MigrationConfigurationCollection $databaseConfigurationCollection)
{
$this->databaseConfigurationCollection = $databaseConfigurationCollection;
}

/**
* Load common DB credentials file
*
Expand All @@ -46,44 +60,26 @@ public function loadCredentialsFile($filename)
$this['db.credentialsFile'] = $filename;
}

/**
* Load migration specification for a single database
*
* @param string $filename Path to specification file
*
* @return void
* @throws \RuntimeException If config is invalid
*/
public function loadDatabaseConfigFile($filename)
{
$config = json_decode(file_get_contents($filename));
$name = $config->name;
if (!$name) {
throw new \RuntimeException("Migration file '$filename' has no name field");
}

$this->configuredMigrationNames[] = $name;
$this["db.migrations.$name"] = $config;
}

/**
* Perform migrations from a named set
*
* @param string $name Migration name as specified in a .db.json migration file
*
* @return void
* @throws \RuntimeException If config is invalid
*/
public function performMigrationSet($name)
public function performMigrationSet($name): void
{
$migrator = new Migrator($name);
$migrator->setLogger($this->getLogger());
$migrator->setDatabaseConnectionFactory($this);
$configuration = $this->databaseConfigurationCollection->get($name);

$sourceConnection = $this->createSourceConnectionByDbName($configuration->getSourceDbName());
$destConnection = $this->createDestConnectionByDbName($configuration->getDestinationDbName());

$migrationConfigProcessor = new MigrationConfigProcessor();
$migrationConfigProcessor->configureMigratorFromConfig($migrator, $this["db.migrations.$name"]);
$migrator = new Migrator($sourceConnection, $destConnection, $this->getLogger());

$migrator->execute();
$tableCollection = TablesList::fromConfig($configuration);
$viewCollection = ViewsList::fromConfig($configuration);

$migrator->execute($name, $tableCollection, $viewCollection);
}

/**
Expand Down Expand Up @@ -117,14 +113,14 @@ public function createDestConnectionByDbName($name)
/**
* Create connection object for DB name / direction. Other credentials (host, password etc) must already be known
*
* @param string $name Database name
* @param string $name Database name
* @param string $direction Determines whether 'source' or 'dest' credentials used, must be one of those values
*
* @return Connection
* @throws \UnexpectedValueException If configuration is invalid
* @throws \Doctrine\DBAL\DBALException If connection cannot be made
*/
protected function createConnectionByDbName($name, $direction = self::CONNECTION_SOURCE)
protected function createConnectionByDbName($name, $direction): Connection
{
if (!isset($this["db.connections.$name"])) {
$dbcredentials = isset($this["db.credentials"]->$direction) ?
Expand Down Expand Up @@ -211,6 +207,6 @@ protected function getLogger()
*/
public function getConfiguredMigrationNames()
{
return $this->configuredMigrationNames;
return $this->databaseConfigurationCollection->listConfigurations();
}
}
53 changes: 53 additions & 0 deletions src/Configuration/MigrationConfiguration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Quidco\DbSampler\Configuration;

class MigrationConfiguration
{
private $config;

private function __construct(\stdClass $config)
{
$this->config = $config;
}

public static function fromJson(string $configJson): self
{
$config = \json_decode($configJson);

if (null === $config) {
throw new \RuntimeException("Migration JSON config was not valid");
}

if (!isset($config->name)) {
throw new \RuntimeException("Migration file has no name field");
}

return new self($config);
}

public function getName(): string
{
return $this->config->name;
}

public function getTables(): array
{
return (array)$this->config->tables;
}

public function getViews(): array
{
return $this->config->views ?? [];
}

public function getSourceDbName(): string
{
return $this->config->sourceDb;
}

public function getDestinationDbName(): string
{
return $this->config->destDb;
}
}
51 changes: 51 additions & 0 deletions src/Configuration/MigrationConfigurationCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Quidco\DbSampler\Configuration;

class MigrationConfigurationCollection
{
/**
* @var array
*/
private $configs;

private function __construct(array $configs)
{
$this->configs = $configs;
}

public static function fromFilePaths(array $filepaths): self
{
$configs = [];

foreach ($filepaths as $migrationFilePath) {
try {
$migrationFiles = [$migrationFilePath];

if (is_dir($migrationFilePath)) {
noondaysun marked this conversation as resolved.
Show resolved Hide resolved
$migrationFiles = glob(rtrim($migrationFilePath, '/') . '/*.json');
}

foreach ($migrationFiles as $file) {
$config = MigrationConfiguration::fromJson(file_get_contents($file));

$configs[$config->getName()] = $config;
}
} catch (\RuntimeException $e) {
print("Migration file '$migrationFilePath' is invalid " . $e->getMessage());
}
}

return new self($configs);
}

public function get(string $configName): MigrationConfiguration
{
return $this->configs[$configName];
}

public function listConfigurations(): array
{
return \array_keys($this->configs);
michaeljoseph marked this conversation as resolved.
Show resolved Hide resolved
}
}
20 changes: 5 additions & 15 deletions src/ConsoleRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Monolog\Logger;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Quidco\DbSampler\Configuration\MigrationConfigurationCollection;

/**
* Console Runner for Quidco\DbSampler\App
Expand Down Expand Up @@ -88,7 +89,10 @@ public function run()
exit($exitCode);
}

$app = new App();
$app = new App(
MigrationConfigurationCollection::fromFilePaths($this->migrationFilePaths)
);

$app->setLogger($this->getLogger());

try {
Expand All @@ -97,20 +101,6 @@ public function run()
print("Credentials file '{$this->credentialsFilePath}' is invalid " . $e->getMessage());
}

foreach ($this->migrationFilePaths as $migrationFilePath) {
try {
if (is_dir($migrationFilePath)) {
$migrationFiles = glob(rtrim($migrationFilePath, '/') . '/*.json');
} else {
$migrationFiles = [$migrationFilePath];
}
foreach ($migrationFiles as $file) {
$app->loadDatabaseConfigFile($file);
}
} catch (\RuntimeException $e) {
print("Migration file '$migrationFilePath' is invalid " . $e->getMessage());
}
}

if (!$this->databases) {
$this->databases = $app->getConfiguredMigrationNames();
Expand Down
73 changes: 73 additions & 0 deletions src/DatabaseSchema/TablesList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace Quidco\DbSampler\DatabaseSchema;

use Quidco\DbSampler\Configuration\MigrationConfiguration;
use Quidco\DbSampler\ReferenceStore;
use Quidco\DbSampler\SamplerInterface;
use Quidco\DbSampler\SamplerMap;

class TablesList
{
private $tables = [];

/**
* @var ReferenceStore
*/
private $referenceStore;
/**
* @var array
*/
private $rawTables;

private function __construct(array $rawTables)
{
$this->referenceStore = new ReferenceStore();
$this->rawTables = $rawTables;
}

public static function fromConfig(MigrationConfiguration $configuration): self
{
return new self((array)$configuration->getTables());
}

public function getTables(): array
{
// @todo we probably shouldn't be building the sampler in this getter. find another place to do it!
if ([] === $this->tables) {
foreach ($this->rawTables as $table => $migrationSpec) {
$this->tables[$table] = $this->buildTableSampler($migrationSpec);
}
}

return $this->tables;
}

/**
* Build a SamplerInterface object from configuration
*
* @throws \UnexpectedValueException If bad object created - should be impossible
* @throws \RuntimeException On invalid specification
*/
private function buildTableSampler(\stdClass $migrationSpec): SamplerInterface
{
$sampler = null;

// @todo: $migrationSpec should be an object with a getSampler() method
$samplerType = strtolower($migrationSpec->sampler);
if (array_key_exists($samplerType, SamplerMap::MAP)) {
$samplerClass = SamplerMap::MAP[$samplerType];
$sampler = new $samplerClass;
if (!$sampler instanceof SamplerInterface) {
throw new \UnexpectedValueException('Invalid sampler created');
}
/** @var SamplerInterface $sampler */
$sampler->loadConfig($migrationSpec);
$sampler->setReferenceStore($this->referenceStore);
} else {
throw new \RuntimeException("Unrecognised sampler type '$samplerType' required");
}

return $sampler;
martinmca marked this conversation as resolved.
Show resolved Hide resolved
}
}
25 changes: 25 additions & 0 deletions src/DatabaseSchema/ViewsList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Quidco\DbSampler\DatabaseSchema;

use Quidco\DbSampler\Configuration\MigrationConfiguration;

class ViewsList
{
private $views = [];

private function __construct(array $views)
{
$this->views = $views;
}

public static function fromConfig(MigrationConfiguration $configuration): self
{
return new self($configuration->getViews());
}

public function getViews(): array
{
return $this->views;
}
}
Loading