Skip to content
Open

Main #10

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,19 @@ Drops a PostgreSQL schema and all its objects:
```yaml
# config/services.yaml
services:
SharedServices\Command\Doctrine\DoctrineSchemaDropCommand: ~
SharedServices\Command\Doctrine\DoctrineSchemaDropCommand:
arguments:
- '@Doctrine\DBAL\Connection'
- ['public'] # Disallowed schema names for safety
```

Usage:
```bash
php bin/console doctrine:schema:delete <schema_name>
php bin/console doctrine:database:schema:drop <schema_name>
```

**Security Note:** You can specify disallowed schema names to prevent accidental deletion of critical schemas like `public`.

### Schema Migrations Command
Runs Doctrine migrations within a specific schema. Creates the schema if it doesn't exist:

Expand All @@ -129,13 +134,17 @@ services:
SharedServices\Command\Doctrine\DoctrineSchemaFixturesLoadCommand:
arguments:
- '@doctrine.fixtures_load_command'
- '@Doctrine\DBAL\Connection'
- ['public'] # Disallowed schema names for safety
```

Usage:
```bash
php bin/console doctrine:schema:fixtures:load <schema_name> [options]
```

**Security Note:** You can specify disallowed schema names to prevent accidental fixture loading into critical schemas like `public`.

**Note:** These commands are optional and should only be registered if you're using the corresponding Doctrine features (migrations and/or fixtures) in your project.

## Testing
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"doctrine/orm": "^2.17 || ^3.0",
"symfony/doctrine-bridge": "^6.4 || ^7.0",
"doctrine/dbal": "^3.4",
"macpaw/schema-context-bundle": "^1.1"
"macpaw/schema-context-bundle": "^2.0"
},
"require-dev": {
"doctrine/migrations": "^3.6",
Expand Down
3 changes: 3 additions & 0 deletions src/Command/Doctrine/AbstractDoctrineSchemaCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Doctrine\DBAL\Connection;
use Error;
use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
Expand All @@ -15,6 +16,7 @@ abstract class AbstractDoctrineSchemaCommand extends Command
public function __construct(
string $commandName,
protected readonly Connection $connection,
protected readonly BaggageSchemaResolver $schemaResolver,
) {
parent::__construct($commandName);
}
Expand Down Expand Up @@ -53,6 +55,7 @@ protected function isSchemaExist(string $schema): bool

protected function switchToSchema(string $schema): void
{
$this->schemaResolver->setSchema($schema);
$quotedSchema = $this->connection->quoteIdentifier($schema);

$this->connection->executeStatement("SET search_path TO {$quotedSchema}");
Expand Down
8 changes: 3 additions & 5 deletions src/Command/Doctrine/AbstractNestingDoctrineSchemaCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Doctrine\DBAL\Connection;
use Error;
use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -19,8 +20,9 @@ public function __construct(
string $commandName,
private readonly Command $parentCommand,
Connection $connection,
BaggageSchemaResolver $schemaResolver,
) {
parent::__construct($commandName, $connection);
parent::__construct($commandName, $connection, $schemaResolver);
}

protected function configure(): void
Expand Down Expand Up @@ -76,10 +78,6 @@ protected function runCommand(string $commandName, InputInterface $input, Output

$options = [];
foreach ($input->getOptions() as $name => $value) {
if ($value === null) {
continue;
}

if ($this->getDefinition()->getOptions()[$name]->getDefault() === $value) {
continue;
}
Expand Down
21 changes: 18 additions & 3 deletions src/Command/Doctrine/DoctrineSchemaDropCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@
namespace Macpaw\PostgresSchemaBundle\Command\Doctrine;

use Doctrine\DBAL\Connection;
use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class DoctrineSchemaDropCommand extends AbstractDoctrineSchemaCommand
{
public function __construct(Connection $connection)
{
parent::__construct('doctrine:schema:delete', $connection);
/**
* @param string[] $disallowedSchemaNames
*/
public function __construct(
Connection $connection,
BaggageSchemaResolver $schemaResolver,
private readonly array $disallowedSchemaNames = [],
) {
parent::__construct('doctrine:database:schema:drop', $connection, $schemaResolver);
}

protected function execute(
Expand All @@ -22,6 +29,14 @@ protected function execute(
): int {
$schema = $this->getSchemaFromInput($input);

if (in_array($schema, $this->disallowedSchemaNames, true)) {
$output->writeln(
"<error>Command is disallowed from being called for the '$schema' schema</error>"
);

return Command::FAILURE;
}

$output->writeln("<info>Drop schema '{$schema}'...<info>");

$quotedSchema = $this->connection->quoteIdentifier($schema);
Expand Down
16 changes: 15 additions & 1 deletion src/Command/Doctrine/DoctrineSchemaFixturesLoadCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,24 @@

use Doctrine\Bundle\FixturesBundle\Command\LoadDataFixturesDoctrineCommand;
use Doctrine\DBAL\Connection;
use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;

class DoctrineSchemaFixturesLoadCommand extends AbstractNestingDoctrineSchemaCommand
{
/**
* @param string[] $disallowedSchemaNames
*/
public function __construct(
LoadDataFixturesDoctrineCommand $parentCommand,
Connection $connection,
BaggageSchemaResolver $schemaResolver,
private readonly array $disallowedSchemaNames = [],
) {
parent::__construct('doctrine:schema:fixtures:load', $parentCommand, $connection);
parent::__construct('doctrine:schema:fixtures:load', $parentCommand, $connection, $schemaResolver);
}

protected function execute(
Expand All @@ -27,6 +33,14 @@ protected function execute(
try {
$schema = $this->getSchemaFromInput($input);

if (in_array($schema, $this->disallowedSchemaNames, true)) {
$output->writeln(
"<error>Command is disallowed from being called for the '$schema' schema</error>"
);

return Command::FAILURE;
}

if (!$this->isSchemaExist($schema)) {
$output->writeln("<error>Schema '{$schema}' doesn't exist</error>");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Doctrine\DBAL\Connection;
use Doctrine\Migrations\Tools\Console\Command\MigrateCommand;
use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
Expand All @@ -16,8 +17,9 @@ class DoctrineSchemaMigrationsMigrateCommand extends AbstractNestingDoctrineSche
public function __construct(
MigrateCommand $parentCommand,
Connection $connection,
BaggageSchemaResolver $schemaResolver,
) {
parent::__construct('doctrine:schema:migrations:migrate', $parentCommand, $connection);
parent::__construct('doctrine:schema:migrations:migrate', $parentCommand, $connection, $schemaResolver);
}

protected function execute(
Expand Down
8 changes: 8 additions & 0 deletions src/Doctrine/SchemaConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
class SchemaConnection extends DBALConnection
{
private static ?BaggageSchemaResolver $schemaResolver = null;
private ?string $currentSchema = null;

public static function setSchemaResolver(BaggageSchemaResolver $resolver): void
{
Expand All @@ -32,6 +33,11 @@ public function connect(): bool
return $connection;
}

if ($this->currentSchema === $schema) {
return $connection;
}
$this->currentSchema = $schema;

$this->ensurePostgreSql();
$this->applySearchPath($schema);

Expand All @@ -50,6 +56,8 @@ private function ensurePostgreSql(): void
private function applySearchPath(string $schema): void
{
if ($this->_conn !== null) {
$schema = $this->getDatabasePlatform()->quoteIdentifier($schema);

$this->_conn->exec('SET search_path TO ' . $schema);
}
}
Expand Down
18 changes: 17 additions & 1 deletion tests/Command/Doctrine/DoctrineSchemaDropCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Doctrine\DBAL\Connection;
use Macpaw\PostgresSchemaBundle\Command\Doctrine\DoctrineSchemaDropCommand;
use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Command\Command;
Expand All @@ -20,7 +21,8 @@ class DoctrineSchemaDropCommandTest extends TestCase
protected function setUp(): void
{
$this->connection = $this->createMock(Connection::class);
$this->command = new DoctrineSchemaDropCommand($this->connection);
$resolver = new BaggageSchemaResolver('public', 'development', ['development']);
$this->command = new DoctrineSchemaDropCommand($this->connection, $resolver, ['public']);
}

public function testSuccess(): void
Expand All @@ -41,4 +43,18 @@ public function testSuccess(): void

$this->assertEquals(Command::SUCCESS, $result);
}

public function testDisallowedSchemaNameFail(): void
{
$input = new ArrayInput(['schema' => 'public']);
$output = new BufferedOutput();

$result = $this->command->run($input, $output);

$this->assertStringContainsString(
"Command is disallowed from being called for the 'public' schema",
$output->fetch()
);
$this->assertEquals(Command::FAILURE, $result);
}
}
23 changes: 22 additions & 1 deletion tests/Command/Doctrine/DoctrineSchemaFixturesLoadCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Doctrine\Bundle\FixturesBundle\Command\LoadDataFixturesDoctrineCommand;
use Doctrine\DBAL\Connection;
use Macpaw\PostgresSchemaBundle\Command\Doctrine\DoctrineSchemaFixturesLoadCommand;
use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
Expand All @@ -33,8 +34,14 @@ protected function setUp(): void
->willReturn(new InputDefinition([
new InputOption('no-interaction'),
]));
$resolver = new BaggageSchemaResolver('public', 'development', ['development']);

$this->command = new DoctrineSchemaFixturesLoadCommand($this->parentCommand, $this->connection);
$this->command = new DoctrineSchemaFixturesLoadCommand(
$this->parentCommand,
$this->connection,
$resolver,
['public']
);
$this->command->setApplication($this->application);
}

Expand Down Expand Up @@ -72,4 +79,18 @@ public function testSuccess(): void
$this->assertEquals(Command::SUCCESS, $result);
$this->assertStringContainsString("Load fixtures for 'test_schema'...", $output->fetch());
}

public function testDisallowedSchemaNameFail(): void
{
$input = new ArrayInput(['schema' => 'public']);
$output = new BufferedOutput();

$result = $this->command->run($input, $output);

$this->assertStringContainsString(
"Command is disallowed from being called for the 'public' schema",
$output->fetch()
);
$this->assertEquals(Command::FAILURE, $result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Doctrine\DBAL\Connection;
use Doctrine\Migrations\Tools\Console\Command\MigrateCommand;
use Macpaw\PostgresSchemaBundle\Command\Doctrine\DoctrineSchemaMigrationsMigrateCommand;
use Macpaw\SchemaContextBundle\Service\BaggageSchemaResolver;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
Expand All @@ -32,8 +33,9 @@ protected function setUp(): void
->willReturn(new InputDefinition([
new InputOption('no-interaction'),
]));
$resolver = new BaggageSchemaResolver('public', 'development', ['development']);

$this->command = new DoctrineSchemaMigrationsMigrateCommand($this->parentCommand, $this->connection);
$this->command = new DoctrineSchemaMigrationsMigrateCommand($this->parentCommand, $this->connection, $resolver);
$this->application = $this->createMock(Application::class);
$this->command->setApplication($this->application);
}
Expand Down
Loading
Loading