1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
namespace EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Command; |
4
|
|
|
|
5
|
|
|
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\PostProcessor\FileOverrider; |
6
|
|
|
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException; |
7
|
|
|
use Symfony\Component\Console\Helper\Table; |
8
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
9
|
|
|
use Symfony\Component\Console\Input\InputOption; |
10
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
11
|
|
|
use Symfony\Component\Console\Question\ConfirmationQuestion; |
12
|
|
|
|
13
|
|
|
class OverridesUpdateCommand extends AbstractCommand |
14
|
|
|
{ |
15
|
|
|
public const OPT_OVERRIDE_ACTION = 'action'; |
16
|
|
|
public const OPT_OVERRIDE_ACTION_SHORT = 'a'; |
17
|
|
|
|
18
|
|
|
public const ACTION_TO_PROJECT = 'toProject'; |
19
|
|
|
public const ACTION_FROM_PROJECT = 'fromProject'; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @var FileOverrider |
23
|
|
|
*/ |
24
|
|
|
protected $fileOverrider; |
25
|
|
|
|
26
|
|
|
public function __construct(FileOverrider $fileOverrider, ?string $name = null) |
27
|
|
|
{ |
28
|
|
|
parent::__construct($name); |
29
|
|
|
$this->fileOverrider = $fileOverrider; |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
protected function execute(InputInterface $input, OutputInterface $output): void |
33
|
|
|
{ |
34
|
|
|
$this->checkOptions($input); |
35
|
|
|
$output->writeln( |
36
|
|
|
'<comment>Updating overrides ' . $input->getOption(self::OPT_OVERRIDE_ACTION) . '</comment>' |
37
|
|
|
); |
38
|
|
|
$this->checkOptions($input); |
39
|
|
|
$this->fileOverrider->setPathToProjectRoot($input->getOption(self::OPT_PROJECT_ROOT_PATH)); |
40
|
|
|
switch ($input->getOption(self::OPT_OVERRIDE_ACTION)) { |
41
|
|
|
case self::ACTION_TO_PROJECT: |
42
|
|
|
$invalidOverrides = $this->fileOverrider->getInvalidOverrides(); |
43
|
|
|
if ([] !== $invalidOverrides) { |
44
|
|
|
$output->writeln("<error>\n\n Some Overrides are Invalid\n</error>"); |
45
|
|
|
$this->renderInvalidOverrides($invalidOverrides, $input, $output); |
46
|
|
|
throw new \RuntimeException('Errors in applying overrides'); |
47
|
|
|
|
48
|
|
|
return; |
|
|
|
|
49
|
|
|
} |
50
|
|
|
$this->renderTableOfUpdatedFiles($this->fileOverrider->applyOverrides(), $output); |
51
|
|
|
$output->writeln('<info>Overrides have been applied to project</info>'); |
52
|
|
|
|
53
|
|
|
return; |
54
|
|
|
case self::ACTION_FROM_PROJECT: |
55
|
|
|
$this->renderTableOfUpdatedFiles($this->fileOverrider->updateOverrideFiles(), $output); |
56
|
|
|
$output->writeln('<info>Overrides have been updated from the project</info>'); |
57
|
|
|
|
58
|
|
|
return; |
59
|
|
|
default: |
60
|
|
|
throw new \InvalidArgumentException( |
61
|
|
|
' Invalid action ' . $input->getOption(self::OPT_OVERRIDE_ACTION) |
62
|
|
|
); |
63
|
|
|
} |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
private function renderInvalidOverrides( |
67
|
|
|
array $invalidOverrides, |
68
|
|
|
InputInterface $input, |
69
|
|
|
OutputInterface $output |
70
|
|
|
): void { |
71
|
|
|
foreach ($invalidOverrides as $pathToFileInOverrides => $details) { |
72
|
|
|
$this->processInvalidOverride($pathToFileInOverrides, $details, $input, $output); |
73
|
|
|
} |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
private function processInvalidOverride( |
77
|
|
|
string $relativePathToFileInOverrides, |
78
|
|
|
array $details, |
79
|
|
|
InputInterface $input, |
80
|
|
|
OutputInterface $output |
81
|
|
|
): void { |
82
|
|
|
$output->writeln('<comment>' . $relativePathToFileInOverrides . '</comment>'); |
83
|
|
|
$table = new Table($output); |
84
|
|
|
$table->setHeaders(['Key', 'Value']); |
85
|
|
|
$table->addRows( |
86
|
|
|
[ |
87
|
|
|
['Project File', $details['projectPath']], |
88
|
|
|
['Override File', $details['overridePath']], |
89
|
|
|
['New MD5', $details['new md5']], |
90
|
|
|
['Diff Size', substr_count($details['diff'], "\n")], |
91
|
|
|
] |
92
|
|
|
); |
93
|
|
|
$table->render(); |
94
|
|
|
$output->writeln('<info>Diff:</info>'); |
95
|
|
|
$output->write($details['diff']); |
96
|
|
|
$output->writeln("\n\n"); |
97
|
|
|
$output->writeln('<info>Fixing this</info>'); |
98
|
|
|
$output->writeln(<<<TEXT |
99
|
|
|
|
100
|
|
|
The suggested fix in this situation is: |
101
|
|
|
|
102
|
|
|
* Rename the current override |
103
|
|
|
* Make a new override from the newly generated file |
104
|
|
|
* Reapply your custom code to the new override |
105
|
|
|
* Finally delete the old override. |
106
|
|
|
|
107
|
|
|
TEXT |
108
|
|
|
); |
109
|
|
|
$questionHelper = $this->getHelper('question'); |
110
|
|
|
$question = new ConfirmationQuestion( |
111
|
|
|
'Would you like to move the current override and make a new one and then diff this? (y/n) ', |
112
|
|
|
true |
113
|
|
|
); |
114
|
|
|
if (!$questionHelper->ask($input, $output, $question)) { |
115
|
|
|
$this->writeln('<commment>Skipping ' . $relativePathToFileInOverrides . '</commment>'); |
|
|
|
|
116
|
|
|
|
117
|
|
|
return; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
$output->writeln('<comment>Recreating Override</comment>'); |
121
|
|
|
list($old, $new) = $this->fileOverrider->recreateOverride($relativePathToFileInOverrides); |
122
|
|
|
$table = new Table($output); |
123
|
|
|
$table->addRow(['old', $old]); |
124
|
|
|
$table->render(); |
125
|
|
|
$table = new Table($output); |
126
|
|
|
$table->addRow(['new', $new]); |
127
|
|
|
$table->render(); |
128
|
|
|
$output->writeln(<<<TEXT |
129
|
|
|
|
130
|
|
|
Now we have created a new override from your freshly generated file, you need to manually copy across all the changes from the old override into your project file. |
131
|
|
|
|
132
|
|
|
* Open the project file: {$details['projectPath']} |
133
|
|
|
|
134
|
|
|
* In PHPStorm, find the old file, right click it and select "compare with editor" |
135
|
|
|
|
136
|
|
|
TEXT |
137
|
|
|
); |
138
|
|
|
$question = new ConfirmationQuestion( |
139
|
|
|
'Confirm you have now copied all required changes from the old override to the new one? (y/n) ', |
140
|
|
|
false |
141
|
|
|
); |
142
|
|
|
while (false === $questionHelper->ask($input, $output, $question)) { |
143
|
|
|
$output->writeln('<error>You must now copy all required changes from the old override to the new one</error>'); |
144
|
|
|
} |
145
|
|
|
$output->writeln('<comment>Now updating override</comment>'); |
146
|
|
|
$this->fileOverrider->updateOverrideFiles(); |
147
|
|
|
$output->writeln("<info>\n\nCompleted override update for $relativePathToFileInOverrides\n</info>"); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
private function renderTableOfUpdatedFiles(array $files, OutputInterface $output): void |
151
|
|
|
{ |
152
|
|
|
list($updated, $same) = $files; |
153
|
|
|
if ([] !== $updated) { |
154
|
|
|
$output->writeln('Files Updated:'); |
155
|
|
|
$table = new Table($output); |
156
|
|
|
foreach ($updated as $file) { |
157
|
|
|
$table->addRow([$file]); |
158
|
|
|
} |
159
|
|
|
$table->render(); |
160
|
|
|
} |
161
|
|
|
if ([] !== $same) { |
162
|
|
|
$output->writeln('Files Same:'); |
163
|
|
|
$table = new Table($output); |
164
|
|
|
foreach ($same as $file) { |
165
|
|
|
$table->addRow([$file]); |
166
|
|
|
} |
167
|
|
|
$table->render(); |
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* @throws DoctrineStaticMetaException |
173
|
|
|
*/ |
174
|
|
|
protected function configure(): void |
175
|
|
|
{ |
176
|
|
|
try { |
177
|
|
|
$this |
178
|
|
|
->setName(AbstractCommand::COMMAND_PREFIX . 'overrides:update') |
179
|
|
|
->setDefinition( |
180
|
|
|
[ |
181
|
|
|
new InputOption( |
182
|
|
|
self::OPT_OVERRIDE_ACTION, |
183
|
|
|
self::OPT_OVERRIDE_ACTION_SHORT, |
184
|
|
|
InputOption::VALUE_REQUIRED, |
185
|
|
|
'One of [ fromProject, toProject ]' |
186
|
|
|
), |
187
|
|
|
$this->getProjectRootPathOption(), |
188
|
|
|
] |
189
|
|
|
)->setDescription( |
190
|
|
|
'Update project overrides' |
191
|
|
|
); |
192
|
|
|
} catch (\Exception $e) { |
193
|
|
|
throw new DoctrineStaticMetaException( |
194
|
|
|
'Exception in ' . __METHOD__ . ': ' . $e->getMessage(), |
195
|
|
|
$e->getCode(), |
196
|
|
|
$e |
197
|
|
|
); |
198
|
|
|
} |
199
|
|
|
} |
200
|
|
|
} |
201
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.