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

add dbal related code from DoctrineBundle #1

Closed
wants to merge 36 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
99e4572
add dbal config & extension
dmaicher Nov 23, 2019
3a589e9
cleanup config: remove orm stuff
dmaicher Nov 23, 2019
dfca364
require doctrine/dbal
dmaicher Dec 10, 2019
193d4ec
move more dbal related things
dmaicher Dec 10, 2019
afcbb2c
add data collector + some fixes
dmaicher Dec 10, 2019
300d236
fix data collector
dmaicher Dec 10, 2019
5f7fec1
add ProfilerController
dmaicher Dec 10, 2019
6d75488
add ConnectionRegistry
dmaicher Dec 10, 2019
d7d6ce7
use registry
dmaicher Feb 29, 2020
20b3cb2
start adding tests
dmaicher Feb 29, 2020
ebe8d49
add some more tests
dmaicher Feb 29, 2020
7356d86
add some more tests
dmaicher Feb 29, 2020
c944ac6
adjust composer requirements
dmaicher Mar 7, 2020
af74eec
adjust composer requirements
dmaicher Mar 7, 2020
7630931
CS fixes
dmaicher Mar 7, 2020
4f5ec77
add more tests
dmaicher Mar 7, 2020
2c6f5bd
add schema + extension tests
dmaicher Mar 8, 2020
98fb67e
cleanup
dmaicher Mar 8, 2020
4f78716
more cleanup
dmaicher Mar 8, 2020
25dc5ad
address first review comments
dmaicher Mar 8, 2020
ded7eb8
add RunSqlDoctrineCommand proxy
dmaicher Mar 8, 2020
bd278bf
cs fix
dmaicher Mar 8, 2020
cdc3fd4
add 2 more commands + tests
dmaicher May 11, 2020
ea8b8be
define command services + cleanup
dmaicher May 11, 2020
ed66ca2
cleanup
dmaicher May 11, 2020
1a3d3e6
use correct version of factory from 2.0.x
dmaicher May 11, 2020
d05284d
fix CS
dmaicher May 11, 2020
479e43f
remove unused class
dmaicher May 11, 2020
fe82cd8
fix registry + add test
dmaicher May 11, 2020
4ccb226
drop support for phpunit 7
dmaicher May 11, 2020
1c46aa5
fix phpunit 7 compat
dmaicher May 11, 2020
4d595a3
fix phpunit warnings
dmaicher May 11, 2020
5436c29
fix CS
dmaicher May 11, 2020
4df8eea
fix deprecations for doctrine/dbal >= 2.11
dmaicher May 13, 2020
8d51194
fix CS
dmaicher May 13, 2020
a7361d8
fix comments
dmaicher May 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add 2 more commands + tests
dmaicher committed May 11, 2020
commit cdc3fd4d12918427705a43fc4b90398861c86849
120 changes: 120 additions & 0 deletions src/Command/CreateDatabaseCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

namespace Doctrine\Bundle\DBALBundle\Command;

use Doctrine\Bundle\DBALBundle\ConnectionRegistry;
use Doctrine\DBAL\DriverManager;
use Exception;
use InvalidArgumentException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

final class CreateDatabaseCommand extends Command
{
/** @var ConnectionRegistry */
private $registry;

public function __construct(ConnectionRegistry $registry)
{
parent::__construct();
$this->registry = $registry;
}

/**
* {@inheritDoc}
*/
protected function configure()
{
$this
->setName('doctrine:database:create')
->setDescription('Creates the configured database')
->addOption('shard', null, InputOption::VALUE_REQUIRED, 'The shard connection to use for this command')
->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command')
->addOption('if-not-exists', null, InputOption::VALUE_NONE, 'Don\'t trigger an error, when the database already exists')
->setHelp(<<<EOT
The <info>%command.name%</info> command creates the default connections database:

<info>php %command.full_name%</info>

You can also optionally specify the name of a connection to create the database for:

<info>php %command.full_name% --connection=default</info>
EOT
);
}

/**
* {@inheritDoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$connectionName = $input->getOption('connection');
if (empty($connectionName)) {
$connectionName = $this->registry->getDefaultConnectionName();
}
$connection = $this->registry->getConnection($connectionName);

$ifNotExists = $input->getOption('if-not-exists');

$params = $connection->getParams();
if (isset($params['master'])) {
$params = $params['master'];
}

// Cannot inject `shard` option in parent::getDoctrineConnection
// cause it will try to connect to a non-existing database
if (isset($params['shards'])) {
$shards = $params['shards'];
// Default select global
$params = array_merge($params, $params['global']);
unset($params['global']['dbname'], $params['global']['path'], $params['global']['url']);
if ($input->getOption('shard')) {
foreach ($shards as $i => $shard) {
if ($shard['id'] === (int) $input->getOption('shard')) {
// Select sharded database
$params = array_merge($params, $shard);
unset($params['shards'][$i]['dbname'], $params['shards'][$i]['path'], $params['shards'][$i]['url'], $params['id']);
break;
}
}
}
}

$hasPath = isset($params['path']);
$name = $hasPath ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
if (! $name) {
throw new InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be created.");
}
// Need to get rid of _every_ occurrence of dbname from connection configuration and we have already extracted all relevant info from url
unset($params['dbname'], $params['path'], $params['url']);

$tmpConnection = DriverManager::getConnection($params);
$tmpConnection->connect($input->getOption('shard'));
$shouldNotCreateDatabase = $ifNotExists && in_array($name, $tmpConnection->getSchemaManager()->listDatabases());

// Only quote if we don't have a path
if (! $hasPath) {
$name = $tmpConnection->getDatabasePlatform()->quoteSingleIdentifier($name);
}

$error = false;
try {
if ($shouldNotCreateDatabase) {
$output->writeln(sprintf('<info>Database <comment>%s</comment> for connection named <comment>%s</comment> already exists. Skipped.</info>', $name, $connectionName));
} else {
$tmpConnection->getSchemaManager()->createDatabase($name);
$output->writeln(sprintf('<info>Created database <comment>%s</comment> for connection named <comment>%s</comment></info>', $name, $connectionName));
}
} catch (Exception $e) {
$output->writeln(sprintf('<error>Could not create database <comment>%s</comment> for connection named <comment>%s</comment></error>', $name, $connectionName));
$output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
$error = true;
}

$tmpConnection->close();

return $error ? 1 : 0;
}
}
133 changes: 133 additions & 0 deletions src/Command/DropDatabaseCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php

namespace Doctrine\Bundle\DBALBundle\Command;

use Doctrine\Bundle\DBALBundle\ConnectionRegistry;
use Doctrine\DBAL\DriverManager;
use Exception;
use InvalidArgumentException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

final class DropDatabaseCommand extends Command
{
const RETURN_CODE_NOT_DROP = 1;
const RETURN_CODE_NO_FORCE = 2;

/** @var ConnectionRegistry */
private $registry;

public function __construct(ConnectionRegistry $registry)
{
parent::__construct();
$this->registry = $registry;
}

/**
* {@inheritDoc}
*/
protected function configure()
{
$this
->setName('doctrine:database:drop')
->setDescription('Drops the configured database')
->addOption('shard', null, InputOption::VALUE_REQUIRED, 'The shard connection to use for this command')
->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command')
->addOption('if-exists', null, InputOption::VALUE_NONE, 'Don\'t trigger an error, when the database doesn\'t exist')
->addOption('force', null, InputOption::VALUE_NONE, 'Set this parameter to execute this action')
->setHelp(<<<EOT
The <info>%command.name%</info> command drops the default connections database:

<info>php %command.full_name%</info>

The <info>--force</info> parameter has to be used to actually drop the database.

You can also optionally specify the name of a connection to drop the database for:

<info>php %command.full_name% --connection=default</info>

<error>Be careful: All data in a given database will be lost when executing this command.</error>
EOT
);
}

/**
* {@inheritDoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$connectionName = $input->getOption('connection');
if (empty($connectionName)) {
$connectionName = $this->registry->getDefaultConnectionName();
}
$connection = $this->registry->getConnection($connectionName);

$ifExists = $input->getOption('if-exists');

$params = $connection->getParams();
if (isset($params['master'])) {
$params = $params['master'];
}

if (isset($params['shards'])) {
$shards = $params['shards'];
// Default select global
$params = array_merge($params, $params['global']);
if ($input->getOption('shard')) {
foreach ($shards as $shard) {
if ($shard['id'] === (int) $input->getOption('shard')) {
// Select sharded database
$params = $shard;
unset($params['id']);
break;
}
}
}
}

$name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
if (! $name) {
throw new InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
}
unset($params['dbname'], $params['url']);

if (! $input->getOption('force')) {
$output->writeln('<error>ATTENTION:</error> This operation should not be executed in a production environment.');
$output->writeln('');
$output->writeln(sprintf('<info>Would drop the database <comment>%s</comment> for connection named <comment>%s</comment>.</info>', $name, $connectionName));
$output->writeln('Please run the operation with --force to execute');
$output->writeln('<error>All data will be lost!</error>');

return self::RETURN_CODE_NO_FORCE;
}

// Reopen connection without database name set
// as some vendors do not allow dropping the database connected to.
$connection->close();
$connection = DriverManager::getConnection($params);
$shouldDropDatabase = ! $ifExists || in_array($name, $connection->getSchemaManager()->listDatabases());

// Only quote if we don't have a path
if (! isset($params['path'])) {
$name = $connection->getDatabasePlatform()->quoteSingleIdentifier($name);
}

try {
if ($shouldDropDatabase) {
$connection->getSchemaManager()->dropDatabase($name);
$output->writeln(sprintf('<info>Dropped database <comment>%s</comment> for connection named <comment>%s</comment></info>', $name, $connectionName));
} else {
$output->writeln(sprintf('<info>Database <comment>%s</comment> for connection named <comment>%s</comment> doesn\'t exist. Skipped.</info>', $name, $connectionName));
}

return 0;
} catch (Exception $e) {
$output->writeln(sprintf('<error>Could not drop database <comment>%s</comment> for connection named <comment>%s</comment></error>', $name, $connectionName));
$output->writeln(sprintf('<error>%s</error>', $e->getMessage()));

return self::RETURN_CODE_NOT_DROP;
}
}
}
121 changes: 121 additions & 0 deletions tests/Command/CreateDatabaseCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

namespace Doctrine\Bundle\DBALBundle\Tests\Command;

use Doctrine\Bundle\DBALBundle\Command\CreateDatabaseCommand;
use Doctrine\Bundle\DBALBundle\ConnectionRegistry;
use Doctrine\DBAL\Connection;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;

class CreateDatabaseCommandTest extends TestCase
{
protected function tearDown() : void
{
@unlink(sys_get_temp_dir() . '/test');
@unlink(sys_get_temp_dir() . '/shard_1');
@unlink(sys_get_temp_dir() . '/shard_2');
}

public function testExecute()
{
$connectionName = 'default';
$dbName = 'test';
$params = [
'path' => sys_get_temp_dir() . '/' . $dbName,
'driver' => 'pdo_sqlite',
];

$registry = $this->createRegistry($connectionName, $params);

$application = new Application();
$application->add(new CreateDatabaseCommand($registry));

$command = $application->find('doctrine:database:create');

$commandTester = new CommandTester($command);
$commandTester->execute(
array_merge(['command' => $command->getName()])
);

$this->assertStringContainsString('Created database ' . sys_get_temp_dir() . '/' . $dbName . ' for connection named ' . $connectionName, $commandTester->getDisplay());
}

public function testExecuteWithShardOption()
{
$connectionName = 'foo';
$params = [
'dbname' => 'test',
'memory' => true,
'driver' => 'pdo_sqlite',
'global' => [
'driver' => 'pdo_sqlite',
'dbname' => 'test',
'path' => sys_get_temp_dir() . '/global',
],
'shards' => [
'foo' => [
'id' => 1,
'path' => sys_get_temp_dir() . '/shard_1',
'driver' => 'pdo_sqlite',
],
'bar' => [
'id' => 2,
'path' => sys_get_temp_dir() . '/shard_2',
'driver' => 'pdo_sqlite',
],
],
];

$registry = $this->createRegistry($connectionName, $params);

$application = new Application();
$application->add(new CreateDatabaseCommand($registry));

$command = $application->find('doctrine:database:create');

$commandTester = new CommandTester($command);
$commandTester->execute(['command' => $command->getName(), '--shard' => 1]);

$this->assertStringContainsString('Created database ' . sys_get_temp_dir() . '/shard_1 for connection named ' . $connectionName, $commandTester->getDisplay());

$commandTester = new CommandTester($command);
$commandTester->execute(['command' => $command->getName(), '--shard' => 2]);

$this->assertStringContainsString('Created database ' . sys_get_temp_dir() . '/shard_2 for connection named ' . $connectionName, $commandTester->getDisplay());
}

/**
* @param string $connectionName Connection name
* @param mixed[]|null $params Connection parameters
*
* @return MockObject&ConnectionRegistry
*/
private function createRegistry($connectionName, $params = null)
{
$registry = $this->createMock(ConnectionRegistry::class);

$registry->expects($this->any())
->method('getDefaultConnectionName')
->willReturn($connectionName);

$mockConnection = $this->getMockBuilder(Connection::class)
->disableOriginalConstructor()
->setMethods(['getParams'])
->getMockForAbstractClass();

$mockConnection->expects($this->any())
->method('getParams')
->withAnyParameters()
->willReturn($params);

$registry->expects($this->any())
->method('getConnection')
->withAnyParameters()
->willReturn($mockConnection);

return $registry;
}
}
110 changes: 110 additions & 0 deletions tests/Command/DropDatabaseCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

namespace Doctrine\Bundle\DBALBundle\Tests\Command;

use Doctrine\Bundle\DBALBundle\Command\DropDatabaseCommand;
use Doctrine\Bundle\DBALBundle\ConnectionRegistry;
use Doctrine\DBAL\Connection;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;

class DropDatabaseCommandTest extends TestCase
{
public function testExecute()
{
$connectionName = 'default';
$dbName = 'test';
$params = [
'url' => 'sqlite:///' . sys_get_temp_dir() . '/test.db',
'path' => sys_get_temp_dir() . '/' . $dbName,
'driver' => 'pdo_sqlite',
];

$registry = $this->createRegistry($connectionName, $params);

$application = new Application();
$application->add(new DropDatabaseCommand($registry));

$command = $application->find('doctrine:database:drop');

$commandTester = new CommandTester($command);
$commandTester->execute(
array_merge(['command' => $command->getName(), '--force' => true])
);

$this->assertStringContainsString(
sprintf(
'Dropped database %s for connection named %s',
sys_get_temp_dir() . '/' . $dbName,
$connectionName
),
$commandTester->getDisplay()
);
}

public function testExecuteWithoutOptionForceWillFailWithAttentionMessage()
{
$connectionName = 'default';
$dbName = 'test';
$params = [
'path' => sys_get_temp_dir() . '/' . $dbName,
'driver' => 'pdo_sqlite',
];

$registry = $this->createRegistry($connectionName, $params);

$application = new Application();
$application->add(new DropDatabaseCommand($registry));

$command = $application->find('doctrine:database:drop');

$commandTester = new CommandTester($command);
$commandTester->execute(
array_merge(['command' => $command->getName()])
);

$this->assertStringContainsString(
sprintf(
'Would drop the database %s for connection named %s.',
sys_get_temp_dir() . '/' . $dbName,
$connectionName
),
$commandTester->getDisplay()
);
$this->assertStringContainsString('Please run the operation with --force to execute', $commandTester->getDisplay());
}

/**
* @param string $connectionName Connection name
* @param mixed[]|null $params Connection parameters
*
* @return MockObject&ConnectionRegistry
*/
private function createRegistry($connectionName, $params = null)
{
$registry = $this->createMock(ConnectionRegistry::class);

$registry->expects($this->any())
->method('getDefaultConnectionName')
->willReturn($connectionName);

$mockConnection = $this->getMockBuilder(Connection::class)
->disableOriginalConstructor()
->setMethods(['getParams'])
->getMockForAbstractClass();

$mockConnection->expects($this->any())
->method('getParams')
->withAnyParameters()
->willReturn($params);

$registry->expects($this->any())
->method('getConnection')
->withAnyParameters()
->willReturn($mockConnection);

return $registry;
}
}
27 changes: 14 additions & 13 deletions tests/ContainerTestCase.php
Original file line number Diff line number Diff line change
@@ -31,22 +31,23 @@ public function createXmlBundleTestContainer()

$extension = new DoctrineDBALExtension();
$container->registerExtension($extension);
$extension->load([[
'connections' => [
'default' => [
'driver' => 'pdo_mysql',
'charset' => 'UTF8',
'platform-service' => 'my.platform',
$extension->load([
[
'connections' => [
'default' => [
'driver' => 'pdo_mysql',
'charset' => 'UTF8',
'platform-service' => 'my.platform',
],
],
],
'default_connection' => 'default',
'types' => [
'test' => [
'class' => TestType::class,
'commented' => false,
'default_connection' => 'default',
'types' => [
'test' => [
'class' => TestType::class,
'commented' => false,
],
],
],
],
], $container);

$container->setDefinition('my.platform', new Definition('Doctrine\DBAL\Platforms\MySqlPlatform'))->setPublic(true);