Skip to content

Commit 4f08d17

Browse files
committed
bug #650 Fixing wrapping of doctrine:migrations:diff command (weaverryan)
This PR was squashed before being merged into the 1.0-dev branch. Discussion ---------- Fixing wrapping of doctrine:migrations:diff command This more selectively overrides output so that some output - including asking a confirmation question - can be shown to the user. Fixes #643 and fixes #642 Commits ------- 25c6902 skipping tests before DoctrineMigrationsBundle 3 bfda162 allowing php 7.1 + symfony 4 compat cb45af5 removing options that are gone in 3.x 885f001 Fixing wrapping of doctrine:migrations:diff command
2 parents e278901 + 25c6902 commit 4f08d17

File tree

8 files changed

+273
-10
lines changed

8 files changed

+273
-10
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"symfony/http-kernel": "^3.4|^4.0|^5.0"
2626
},
2727
"require-dev": {
28+
"composer/semver": "^3.0@dev",
2829
"doctrine/doctrine-bundle": "^1.8|^2.0",
2930
"doctrine/orm": "^2.3",
3031
"friendsofphp/php-cs-fixer": "^2.8",
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\MakerBundle\Console;
13+
14+
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
15+
use Symfony\Component\Console\Output\OutputInterface;
16+
17+
class MigrationDiffFilteredOutput implements OutputInterface
18+
{
19+
private $output;
20+
private $buffer = '';
21+
private $previousLineWasRemoved = false;
22+
23+
public function __construct(OutputInterface $output)
24+
{
25+
$this->output = $output;
26+
}
27+
28+
public function write($messages, $newline = false, $options = 0)
29+
{
30+
$messages = $this->filterMessages($messages, $newline);
31+
32+
$this->output->write($messages, $newline, $options);
33+
}
34+
35+
public function writeln($messages, $options = 0)
36+
{
37+
$messages = $this->filterMessages($messages, true);
38+
39+
$this->output->writeln($messages, $options);
40+
}
41+
42+
public function setVerbosity($level)
43+
{
44+
$this->output->setVerbosity($level);
45+
}
46+
47+
public function getVerbosity()
48+
{
49+
return $this->output->getVerbosity();
50+
}
51+
52+
public function isQuiet()
53+
{
54+
return $this->output->isQuiet();
55+
}
56+
57+
public function isVerbose()
58+
{
59+
return $this->output->isVerbose();
60+
}
61+
62+
public function isVeryVerbose()
63+
{
64+
return $this->output->isVeryVerbose();
65+
}
66+
67+
public function isDebug()
68+
{
69+
return $this->output->isDebug();
70+
}
71+
72+
public function setDecorated($decorated)
73+
{
74+
$this->output->setDecorated($decorated);
75+
}
76+
77+
public function isDecorated()
78+
{
79+
return $this->output->isDecorated();
80+
}
81+
82+
public function setFormatter(OutputFormatterInterface $formatter)
83+
{
84+
$this->output->setFormatter($formatter);
85+
}
86+
87+
public function getFormatter()
88+
{
89+
return $this->output->getFormatter();
90+
}
91+
92+
public function fetch(): string
93+
{
94+
return $this->buffer;
95+
}
96+
97+
private function filterMessages($messages, bool $newLine)
98+
{
99+
if (!is_iterable($messages)) {
100+
$messages = [$messages];
101+
}
102+
103+
$hiddenPhrases = [
104+
'Generated new migration class',
105+
'To run just this migration',
106+
'To revert the migration you',
107+
];
108+
109+
foreach ($messages as $key => $message) {
110+
$this->buffer .= $message;
111+
112+
if ($newLine) {
113+
$this->buffer .= PHP_EOL;
114+
}
115+
116+
if ($this->previousLineWasRemoved && !trim($message)) {
117+
// hide a blank line after a filtered line
118+
unset($messages[$key]);
119+
$this->previousLineWasRemoved = false;
120+
121+
continue;
122+
}
123+
124+
$this->previousLineWasRemoved = false;
125+
foreach ($hiddenPhrases as $hiddenPhrase) {
126+
if (false !== strpos($message, $hiddenPhrase)) {
127+
$this->previousLineWasRemoved = true;
128+
unset($messages[$key]);
129+
130+
break;
131+
}
132+
}
133+
}
134+
135+
return array_values($messages);
136+
}
137+
}

src/ConsoleStyle.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Bundle\MakerBundle;
1313

14+
use Symfony\Component\Console\Input\InputInterface;
15+
use Symfony\Component\Console\Output\OutputInterface;
1416
use Symfony\Component\Console\Style\SymfonyStyle;
1517

1618
/**
@@ -19,6 +21,15 @@
1921
*/
2022
final class ConsoleStyle extends SymfonyStyle
2123
{
24+
private $output;
25+
26+
public function __construct(InputInterface $input, OutputInterface $output)
27+
{
28+
$this->output = $output;
29+
30+
parent::__construct($input, $output);
31+
}
32+
2233
public function success($message)
2334
{
2435
$this->writeln('<fg=green;options=bold,underscore>OK</> '.$message);
@@ -28,4 +39,9 @@ public function comment($message)
2839
{
2940
$this->text($message);
3041
}
42+
43+
public function getOutput(): OutputInterface
44+
{
45+
return $this->output;
46+
}
3147
}

src/Maker/MakeMigration.php

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
namespace Symfony\Bundle\MakerBundle\Maker;
1313

14+
use Doctrine\Bundle\MigrationsBundle\Command\MigrationsDiffDoctrineCommand;
1415
use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle;
1516
use Symfony\Bundle\MakerBundle\ApplicationAwareMakerInterface;
17+
use Symfony\Bundle\MakerBundle\Console\MigrationDiffFilteredOutput;
1618
use Symfony\Bundle\MakerBundle\ConsoleStyle;
1719
use Symfony\Bundle\MakerBundle\DependencyBuilder;
1820
use Symfony\Bundle\MakerBundle\Generator;
@@ -22,7 +24,6 @@
2224
use Symfony\Component\Console\Input\ArgvInput;
2325
use Symfony\Component\Console\Input\InputInterface;
2426
use Symfony\Component\Console\Input\InputOption;
25-
use Symfony\Component\Console\Output\BufferedOutput;
2627

2728
/**
2829
* @author Amrouche Hamza <[email protected]>
@@ -56,31 +57,47 @@ public function configureCommand(Command $command, InputConfiguration $inputConf
5657
{
5758
$command
5859
->setDescription('Creates a new migration based on database changes')
59-
->addOption('db', null, InputOption::VALUE_REQUIRED, 'The database connection name')
60-
->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager name')
61-
->addOption('shard', null, InputOption::VALUE_REQUIRED, 'The shard connection name')
6260
->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeMigration.txt'))
6361
;
62+
63+
if (class_exists(MigrationsDiffDoctrineCommand::class)) {
64+
// support for DoctrineMigrationsBundle 2.x
65+
$command
66+
->addOption('db', null, InputOption::VALUE_REQUIRED, 'The database connection name')
67+
->addOption('em', null, InputOption::VALUE_OPTIONAL, 'The entity manager name')
68+
->addOption('shard', null, InputOption::VALUE_REQUIRED, 'The shard connection name')
69+
;
70+
}
6471
}
6572

6673
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
6774
{
6875
$options = ['doctrine:migrations:diff'];
69-
if (null !== $input->getOption('db')) {
76+
77+
// DoctrineMigrationsBundle 2.x support
78+
if ($input->hasOption('db') && null !== $input->getOption('db')) {
7079
$options[] = '--db='.$input->getOption('db');
7180
}
72-
if (null !== $input->getOption('em')) {
81+
if ($input->hasOption('em') && null !== $input->getOption('em')) {
7382
$options[] = '--em='.$input->getOption('em');
7483
}
75-
if (null !== $input->getOption('shard')) {
84+
if ($input->hasOption('shard') && null !== $input->getOption('shard')) {
7685
$options[] = '--shard='.$input->getOption('shard');
7786
}
87+
// end 2.x support
7888

7989
$generateMigrationCommand = $this->application->find('doctrine:migrations:diff');
8090

81-
$commandOutput = new BufferedOutput($io->getVerbosity());
91+
$commandOutput = new MigrationDiffFilteredOutput($io->getOutput());
8292
try {
83-
$generateMigrationCommand->run(new ArgvInput($options), $commandOutput);
93+
$returnCode = $generateMigrationCommand->run(new ArgvInput($options), $commandOutput);
94+
95+
// non-zero code would ideally mean the internal command has already printed an errror
96+
// this happens if you "decline" generating a migration when you already
97+
// have some available
98+
if (0 !== $returnCode) {
99+
return $returnCode;
100+
}
84101

85102
$migrationOutput = $commandOutput->fetch();
86103

src/Test/MakerTestCase.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Bundle\MakerBundle\Test;
1313

14+
use Composer\Semver\Semver;
1415
use PHPUnit\Framework\TestCase;
1516
use Symfony\Bundle\MakerBundle\MakerInterface;
1617
use Symfony\Bundle\MakerBundle\Str;
@@ -40,6 +41,10 @@ protected function executeMakerCommand(MakerTestDetails $testDetails)
4041
// prepare environment to test
4142
$testEnv->prepare();
4243

44+
if (!$this->hasRequiredDependencyVersions($testDetails, $testEnv)) {
45+
$this->markTestSkipped('Some dependencies versions are too low');
46+
}
47+
4348
// run tests
4449
$makerTestProcess = $testEnv->runMaker();
4550
$files = $testEnv->getGeneratedFilesFromOutputText();
@@ -95,4 +100,32 @@ protected function getMakerInstance(string $makerClass): MakerInterface
95100

96101
return $this->kernel->getContainer()->get($serviceId);
97102
}
103+
104+
private function hasRequiredDependencyVersions(MakerTestDetails $testDetails, MakerTestEnvironment $testEnv): bool
105+
{
106+
if (empty($testDetails->getRequiredPackageVersions())) {
107+
return true;
108+
}
109+
110+
$installedPackages = json_decode($testEnv->readFile('vendor/composer/installed.json'), true);
111+
$packageVersions = [];
112+
foreach ($installedPackages as $installedPackage) {
113+
$packageVersions[$installedPackage['name']] = $installedPackage['version_normalized'];
114+
}
115+
116+
foreach ($testDetails->getRequiredPackageVersions() as $requiredPackageData) {
117+
$name = $requiredPackageData['name'];
118+
$versionConstraint = $requiredPackageData['version_constraint'];
119+
120+
if (!isset($packageVersions[$name])) {
121+
throw new \Exception(sprintf('Package "%s" is required in the test project at version "%s" but it is not installed?', $name, $versionConstraint));
122+
}
123+
124+
if (!Semver::satisfies($packageVersions[$name], $versionConstraint)) {
125+
return false;
126+
}
127+
}
128+
129+
return true;
130+
}
98131
}

src/Test/MakerTestDetails.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ final class MakerTestDetails
4444

4545
private $requiredPhpVersion;
4646

47+
private $requiredPackageVersions = [];
48+
4749
private $guardAuthenticators = [];
4850

4951
/**
@@ -216,6 +218,13 @@ public function setRequiredPhpVersion(int $version): self
216218
return $this;
217219
}
218220

221+
public function addRequiredPackageVersion(string $packageName, string $versionConstraint): self
222+
{
223+
$this->requiredPackageVersions[] = ['name' => $packageName, 'version_constraint' => $versionConstraint];
224+
225+
return $this;
226+
}
227+
219228
public function setGuardAuthenticator(string $firewallName, string $id): self
220229
{
221230
$this->guardAuthenticators[$firewallName] = $id;
@@ -316,4 +325,9 @@ public function getGuardAuthenticators(): array
316325
{
317326
return $this->guardAuthenticators;
318327
}
328+
329+
public function getRequiredPackageVersions(): array
330+
{
331+
return $this->requiredPackageVersions;
332+
}
319333
}

src/Test/MakerTestEnvironment.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ public function getPath(): string
6969
return $this->path;
7070
}
7171

72+
public function readFile(string $path): string
73+
{
74+
if (!file_exists($this->path.'/'.$path)) {
75+
throw new \InvalidArgumentException(sprintf('Cannot find file "%s"', $path));
76+
}
77+
78+
return file_get_contents($this->path.'/'.$path);
79+
}
80+
7281
private function changeRootNamespaceIfNeeded()
7382
{
7483
if ('App' === ($rootNamespace = $this->testDetails->getRootNamespace())) {

0 commit comments

Comments
 (0)