1
|
|
|
<?php declare(strict_types = 1); |
2
|
|
|
|
3
|
|
|
namespace ApiClients\Tools\Installer; |
4
|
|
|
|
5
|
|
|
use Composer\Factory; |
6
|
|
|
use Composer\Json\JsonFile; |
7
|
|
|
use PhpParser\Node\Name; |
8
|
|
|
use PhpParser\Node\Stmt\Namespace_; |
9
|
|
|
use PhpParser\Node\Stmt\Use_; |
10
|
|
|
use PhpParser\Parser; |
11
|
|
|
use PhpParser\ParserFactory; |
12
|
|
|
use PhpParser\PrettyPrinter\Standard; |
13
|
|
|
use Symfony\Component\Console\Command\Command; |
14
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
15
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
16
|
|
|
use Symfony\Component\Console\Style\SymfonyStyle; |
17
|
|
|
use Symfony\CS\AbstractFixer; |
18
|
|
|
use Symfony\CS\Config\Config; |
19
|
|
|
use Symfony\CS\ConfigAwareInterface; |
20
|
|
|
use Symfony\CS\ConfigInterface; |
21
|
|
|
use Symfony\CS\Fixer; |
22
|
|
|
use Symfony\CS\FixerInterface; |
23
|
|
|
|
24
|
|
|
final class Install extends Command |
25
|
|
|
{ |
26
|
|
|
const COMMAND = 'install'; |
27
|
|
|
|
28
|
|
|
const NS_VENDOR = '__NS_VENDOR__'; |
29
|
|
|
const NS_TESTS_VENDOR = '__NS_TESTS_VENDOR__'; |
30
|
|
|
const NS_PROJECT = '__NS_PROJECT__'; |
31
|
|
|
const PACKAGE_NAME = '__PACKAGE_NAME__'; |
32
|
|
|
const AUTHOR = '__AUTHOR__'; |
33
|
|
|
const AUTHOR_NAME = '__AUTHOR_NAME__'; |
34
|
|
|
const AUTHOR_EMAIL = '__AUTHOR_EMAIL__'; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @var AbstractFixer[] |
38
|
|
|
*/ |
39
|
|
|
private $fixers; |
40
|
|
|
|
41
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) |
42
|
|
|
{ |
43
|
|
|
retry: |
44
|
|
|
$style = new SymfonyStyle( $input, $output ); |
45
|
|
|
|
46
|
|
|
$style->title('Welcome to the IceHawk installer.'); |
47
|
|
|
$style->section('Please answer the following questions.'); |
48
|
|
|
|
49
|
|
|
$replacements = []; |
50
|
|
|
$replacements[self::NS_VENDOR] = $style->ask('What is your vendor namespace?', 'MyVendor'); |
51
|
|
|
$replacements[self::NS_TESTS_VENDOR] = $style->ask('What is your vendor test namespace?', 'MyVendor\\Tests'); |
52
|
|
|
$replacements[self::NS_PROJECT] = $style->ask('What is your project namespace?', 'MyProject'); |
53
|
|
|
$replacements[self::PACKAGE_NAME] = $style->ask( |
54
|
|
|
'What is your package name?', |
55
|
|
|
strtolower($replacements[self::NS_VENDOR]) . '/' . strtolower($replacements[self::NS_PROJECT]) |
56
|
|
|
); |
57
|
|
|
$replacements[self::AUTHOR_NAME] = $style->ask('What is your name?'); |
58
|
|
|
$replacements[self::AUTHOR_EMAIL] = $style->ask('What is your email address?'); |
59
|
|
|
|
60
|
|
|
while ( false === filter_var( $replacements[self::AUTHOR_EMAIL], FILTER_VALIDATE_EMAIL ) ) |
61
|
|
|
{ |
62
|
|
|
$replacements[self::AUTHOR_EMAIL] = $style->ask('Invalid email address, try again.'); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
$replacements[self::AUTHOR] = "{$replacements[self::AUTHOR_NAME]} <{$replacements[self::AUTHOR_EMAIL]}>"; |
66
|
|
|
|
67
|
|
|
$style->section('Summary:'); |
68
|
|
|
|
69
|
|
|
$style->table( |
70
|
|
|
[], |
71
|
|
|
[ |
72
|
|
|
['Your namespace', $replacements[self::NS_VENDOR] . '\\' . $replacements[self::NS_PROJECT]], |
73
|
|
|
['Your test namespace', $replacements[self::NS_TESTS_VENDOR] . '\\' . $replacements[self::NS_PROJECT]], |
74
|
|
|
['Your package', $replacements[self::PACKAGE_NAME]], |
75
|
|
|
['Author name', $replacements[self::AUTHOR_NAME]], |
76
|
|
|
['Author email', $replacements[self::AUTHOR_EMAIL]], |
77
|
|
|
] |
78
|
|
|
); |
79
|
|
|
|
80
|
|
|
$installNow = $style->choice( |
81
|
|
|
'All settings correct?', |
82
|
|
|
['Yes', 'Change settings', 'Cancel installation'], |
83
|
|
|
'Yes' |
84
|
|
|
); |
85
|
|
|
|
86
|
|
|
switch ( $installNow ) |
87
|
|
|
{ |
88
|
|
|
case 'Yes': |
|
|
|
|
89
|
|
|
{ |
90
|
|
|
$style->text('Creating your middleware package now.'); |
91
|
|
|
$this->updateComposerJson($replacements, $style); |
92
|
|
|
$this->updatePHPFiles($replacements, $style); |
93
|
|
|
$this->removingOurSelfs($style); |
94
|
|
|
$style->section('Your middleware package creation has been successfully.'); |
95
|
|
|
|
96
|
|
|
break; |
97
|
|
|
} |
98
|
|
|
case 'Change settings': |
|
|
|
|
99
|
|
|
{ |
100
|
|
|
goto retry; |
101
|
|
|
break; |
|
|
|
|
102
|
|
|
} |
103
|
|
|
case 'Cancel installation': |
|
|
|
|
104
|
|
|
{ |
105
|
|
|
$style->error( 'Installation canceled.' ); |
106
|
|
|
|
107
|
|
|
return 9; |
108
|
|
|
} |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
return 0; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
private function updateComposerJson(array $replacements, SymfonyStyle $style) |
115
|
|
|
{ |
116
|
|
|
$style->section('Updating composer.json'); |
117
|
|
|
$json = new JsonFile(Factory::getComposerFile()); |
118
|
|
|
$style->text('Reading composer.json'); |
119
|
|
|
$composerJson = $json->read(); |
120
|
|
|
|
121
|
|
|
$style->text('Replacing package name'); |
122
|
|
|
$composerJson['name'] = $replacements[self::PACKAGE_NAME]; |
123
|
|
|
|
124
|
|
|
$style->text('Adding authors'); |
125
|
|
|
$composerJson['authors'] = [ |
126
|
|
|
[ |
127
|
|
|
'name' => $replacements[self::AUTHOR_NAME], |
128
|
|
|
'email' => $replacements[self::AUTHOR_EMAIL], |
129
|
|
|
], |
130
|
|
|
]; |
131
|
|
|
|
132
|
|
|
$style->text('Updating autoload'); |
133
|
|
|
$composerJson['autoload']['psr-4'][$replacements[self::NS_VENDOR] . '\\' . $replacements[self::NS_PROJECT] . '\\'] = 'src/'; |
134
|
|
|
$composerJson['autoload-dev']['psr-4'][$replacements[self::NS_TESTS_VENDOR] . '\\' . $replacements[self::NS_PROJECT] . '\\'] = 'tests/'; |
135
|
|
|
|
136
|
|
|
$style->text('Removing package needed for installation and post create script'); |
137
|
|
|
unset( |
138
|
|
|
$composerJson['autoload']['psr-4']['ApiClients\\Middleware\\Installer\\'], |
139
|
|
|
$composerJson['autoload']['psr-4']['ApiClients\\Middleware\\Skeleton\\'], |
140
|
|
|
$composerJson['autoload-dev']['psr-4']['ApiClients\\Tests\\Middleware\\Skeleton\\'], |
141
|
|
|
$composerJson['require']['composer/composer'], |
142
|
|
|
$composerJson['require']['friendsofphp/php-cs-fixer'], |
143
|
|
|
$composerJson['require']['nikic/php-parser'], |
144
|
|
|
$composerJson['require']['ocramius/package-versions'], |
145
|
|
|
$composerJson['require']['symfony/console'], |
146
|
|
|
$composerJson['scripts']['post-create-project-cmd'] |
147
|
|
|
); |
148
|
|
|
|
149
|
|
|
$style->text('Writing updated composer.json'); |
150
|
|
|
$json->write($composerJson); |
151
|
|
|
$style->success('Updated composer.json'); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
private function updatePHPFiles(array $replacements, SymfonyStyle $style) |
155
|
|
|
{ |
156
|
|
|
$style->section('Updating namespaces in PHP files'); |
157
|
|
|
$this->setUpFixers(); |
158
|
|
|
$path = dirname(__DIR__) . DIRECTORY_SEPARATOR; |
159
|
|
|
foreach([ |
160
|
|
|
'src' => self::NS_VENDOR, |
161
|
|
|
'tests' => self::NS_TESTS_VENDOR, |
162
|
|
|
] as $dir => $namespace) { |
163
|
|
|
$this->iterateDirectory( |
164
|
|
|
$path . $dir . DIRECTORY_SEPARATOR, |
165
|
|
|
$style, |
166
|
|
|
$replacements[$namespace] . '\\' . $replacements[self::NS_PROJECT], |
167
|
|
|
$replacements[self::NS_VENDOR] . '\\' . $replacements[self::NS_PROJECT] |
168
|
|
|
); |
169
|
|
|
} |
170
|
|
|
$style->success('Namespaces updated'); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
private function iterateDirectory(string $path, SymfonyStyle $style, string $namespace, string $namespaceSrc) |
174
|
|
|
{ |
175
|
|
|
$d = dir($path); |
176
|
|
|
while (false !== ($entry = $d->read())) { |
177
|
|
|
$entryPath = $path . $entry; |
178
|
|
|
if (!is_file($entryPath)) { |
179
|
|
|
continue; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$style->text('Updating ' . $entry . ' namespace'); |
183
|
|
|
$this->updateNamespaces($entryPath, $namespace, $namespaceSrc); |
184
|
|
|
} |
185
|
|
|
$d->close(); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
private function updateNamespaces(string $fileName, string $namespace, string $namespaceSrc) |
189
|
|
|
{ |
190
|
|
|
$md5 = md5_file($fileName); |
191
|
|
|
$stmts = (new ParserFactory())->create(ParserFactory::ONLY_PHP7)->parse(file_get_contents($fileName)); |
192
|
|
|
if ($stmts === null) { |
193
|
|
|
return; |
194
|
|
|
} |
195
|
|
|
foreach ($stmts as $index => $node) { |
196
|
|
|
if (!($node instanceof Namespace_)) { |
197
|
|
|
continue; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
$nodeStmts = $node->stmts; |
201
|
|
|
|
202
|
|
|
foreach ($nodeStmts as $nodeIndex => $nodeNode) { |
203
|
|
|
if (!($nodeNode instanceof Use_)) { |
204
|
|
|
continue; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
foreach ($nodeNode->uses as $useUse) { |
208
|
|
|
$nameString = $useUse->name->toString(); |
209
|
|
|
if (strpos($nameString, 'ApiClients\\Middleware\\Skeleton') !== 0) { |
210
|
|
|
continue; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
$useUse->name = new Name( |
214
|
|
|
$namespaceSrc . substr($nameString, strlen('ApiClients\\Middleware\\Skeleton')) |
215
|
|
|
); |
216
|
|
|
} |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
$stmts[$index] = new Namespace_( |
220
|
|
|
new Name( |
221
|
|
|
$namespace |
222
|
|
|
), |
223
|
|
|
$nodeStmts, |
224
|
|
|
$node->getAttributes() |
225
|
|
|
); |
226
|
|
|
break; |
227
|
|
|
} |
228
|
|
|
file_put_contents($fileName, (new Standard())->prettyPrintFile($stmts) . PHP_EOL); |
229
|
|
|
while($md5 === md5_file($fileName)) { |
230
|
|
|
usleep(500); |
231
|
|
|
} |
232
|
|
|
$this->applyPsr2($fileName); |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* @param string $fileName |
238
|
|
|
*/ |
239
|
|
|
protected function applyPsr2(string $fileName) |
240
|
|
|
{ |
241
|
|
|
$file = new \SplFileInfo($fileName); |
242
|
|
|
$new = file_get_contents($file->getRealPath()); |
243
|
|
|
|
244
|
|
|
foreach ($this->fixers as $fixer) { |
245
|
|
|
if (!$fixer->supports($file)) { |
246
|
|
|
continue; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
$new = $fixer->fix($file, $new); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
file_put_contents( |
253
|
|
|
$fileName, |
254
|
|
|
str_replace( |
255
|
|
|
'<?php', |
256
|
|
|
'<?php declare(strict_types=1);', |
257
|
|
|
$new |
258
|
|
|
) |
259
|
|
|
); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
protected function setUpFixers() |
263
|
|
|
{ |
264
|
|
|
$fixer = new Fixer(); |
265
|
|
|
$fixer->registerCustomFixers([ |
266
|
|
|
new Fixer\Symfony\ExtraEmptyLinesFixer(), |
267
|
|
|
new Fixer\Symfony\SingleBlankLineBeforeNamespaceFixer(), |
268
|
|
|
new Fixer\PSR0\Psr0Fixer(), |
269
|
|
|
new Fixer\PSR1\EncodingFixer(), |
270
|
|
|
new Fixer\PSR1\ShortTagFixer(), |
271
|
|
|
new Fixer\PSR2\BracesFixer(), |
272
|
|
|
new Fixer\PSR2\ElseifFixer(), |
273
|
|
|
new Fixer\PSR2\EofEndingFixer(), |
274
|
|
|
new Fixer\PSR2\FunctionCallSpaceFixer(), |
275
|
|
|
new Fixer\PSR2\FunctionDeclarationFixer(), |
276
|
|
|
new Fixer\PSR2\IndentationFixer(), |
277
|
|
|
new Fixer\PSR2\LineAfterNamespaceFixer(), |
278
|
|
|
new Fixer\PSR2\LinefeedFixer(), |
279
|
|
|
new Fixer\PSR2\LowercaseConstantsFixer(), |
280
|
|
|
new Fixer\PSR2\LowercaseKeywordsFixer(), |
281
|
|
|
new Fixer\PSR2\MethodArgumentSpaceFixer(), |
282
|
|
|
new Fixer\PSR2\MultipleUseFixer(), |
283
|
|
|
new Fixer\PSR2\ParenthesisFixer(), |
284
|
|
|
new Fixer\PSR2\PhpClosingTagFixer(), |
285
|
|
|
new Fixer\PSR2\SingleLineAfterImportsFixer(), |
286
|
|
|
new Fixer\PSR2\TrailingSpacesFixer(), |
287
|
|
|
new Fixer\PSR2\VisibilityFixer(), |
288
|
|
|
new Fixer\Contrib\NewlineAfterOpenTagFixer(), |
289
|
|
|
]); |
290
|
|
|
$config = Config::create()->fixers($fixer->getFixers()); |
291
|
|
|
$fixer->addConfig($config); |
292
|
|
|
$this->fixers = $this->prepareFixers($config); |
|
|
|
|
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
|
|
* @param ConfigInterface $config |
297
|
|
|
* |
298
|
|
|
* @return FixerInterface[] |
299
|
|
|
*/ |
300
|
|
|
private function prepareFixers(ConfigInterface $config): array |
301
|
|
|
{ |
302
|
|
|
$fixers = $config->getFixers(); |
303
|
|
|
|
304
|
|
|
foreach ($fixers as $fixer) { |
305
|
|
|
if ($fixer instanceof ConfigAwareInterface) { |
|
|
|
|
306
|
|
|
$fixer->setConfig($config); |
307
|
|
|
} |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
return $fixers; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
private function removingOurSelfs(SymfonyStyle $style) |
314
|
|
|
{ |
315
|
|
|
$style->section('Removing installer'); |
316
|
|
|
unlink(__DIR__ . DIRECTORY_SEPARATOR . 'Install.php'); |
317
|
|
|
$style->text('Removed Install.php'); |
318
|
|
|
unlink(__DIR__ . DIRECTORY_SEPARATOR . 'Installer.php'); |
319
|
|
|
$style->text('Removed Installer.php'); |
320
|
|
|
rmdir(__DIR__); |
321
|
|
|
$style->text('Removed installer directory'); |
322
|
|
|
$style->success('Installer removed'); |
323
|
|
|
} |
324
|
|
|
} |
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break
.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.