Skip to content

Commit 7238a67

Browse files
New make Stimulus controller
update test Coding standards Coding standards Make constants private Add targets and language Respeect Coding Standards Possibility to add values
1 parent b6b0afe commit 7238a67

File tree

7 files changed

+379
-0
lines changed

7 files changed

+379
-0
lines changed

src/Maker/MakeStimulusController.php

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
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\Maker;
13+
14+
use Symfony\Bundle\MakerBundle\ConsoleStyle;
15+
use Symfony\Bundle\MakerBundle\DependencyBuilder;
16+
use Symfony\Bundle\MakerBundle\Generator;
17+
use Symfony\Bundle\MakerBundle\InputConfiguration;
18+
use Symfony\Bundle\MakerBundle\Str;
19+
use Symfony\Component\Console\Command\Command;
20+
use Symfony\Component\Console\Input\InputArgument;
21+
use Symfony\Component\Console\Input\InputInterface;
22+
use Symfony\Component\Console\Question\Question;
23+
use Symfony\WebpackEncoreBundle\WebpackEncoreBundle;
24+
25+
/**
26+
* @author Abdelilah Jabri <[email protected]>
27+
*
28+
* @internal
29+
*/
30+
final class MakeStimulusController extends AbstractMaker
31+
{
32+
public static function getCommandName(): string
33+
{
34+
return 'make:stimulus-controller';
35+
}
36+
37+
public static function getCommandDescription(): string
38+
{
39+
return 'Creates a new Stimulus controller';
40+
}
41+
42+
public function configureCommand(Command $command, InputConfiguration $inputConfig)
43+
{
44+
$command
45+
->addArgument('name', InputArgument::REQUIRED, 'The name of the Stimulus controller (e.g. <fg=yellow>hello</>)')
46+
->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeStimulusController.txt'));
47+
}
48+
49+
public function interact(InputInterface $input, ConsoleStyle $io, Command $command)
50+
{
51+
$command->addArgument('extension', InputArgument::OPTIONAL);
52+
$command->addArgument('targets', InputArgument::OPTIONAL, '', []);
53+
$command->addArgument('values', InputArgument::OPTIONAL, '', []);
54+
55+
$chosenExtension = $io->choice(
56+
'Language (<fg=yellow>JavaScript</> or <fg=yellow>TypeScript</>)',
57+
[
58+
'js' => 'JavaScript',
59+
'ts' => 'TypeScript',
60+
]
61+
);
62+
63+
$input->setArgument('extension', $chosenExtension);
64+
65+
$includeTargets = $io->confirm('Do you want to include targets?');
66+
if ($includeTargets) {
67+
$targets = [];
68+
$isFirstTarget = true;
69+
while (true) {
70+
$newTarget = $this->askForNextTarget($io, $targets, $isFirstTarget);
71+
$isFirstTarget = false;
72+
73+
if (null === $newTarget) {
74+
break;
75+
}
76+
77+
$targets[] = $newTarget;
78+
}
79+
80+
$input->setArgument('targets', $targets);
81+
}
82+
83+
$includeValues = $io->confirm('Do you want to include values?');
84+
if ($includeValues) {
85+
$values = [];
86+
$isFirstValue = true;
87+
while (true) {
88+
$newValue = $this->askForNextValue($io, $values, $isFirstValue);
89+
$isFirstValue = false;
90+
91+
if (null === $newValue) {
92+
break;
93+
}
94+
95+
$values[$newValue['name']] = $newValue;
96+
}
97+
98+
$input->setArgument('values', $values);
99+
}
100+
101+
}
102+
103+
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
104+
{
105+
$controllerName = Str::asSnakeCase($input->getArgument('name'));
106+
$chosenExtension = $input->getArgument('extension');
107+
$targets = $input->getArgument('targets');
108+
$values = $input->getArgument('values');
109+
110+
$targets = empty($targets) ? $targets : sprintf("['%s']", implode("', '", $targets));
111+
112+
$fileName = sprintf('%s_controller.%s', $controllerName, $chosenExtension);
113+
$filePath = sprintf('assets/controllers/%s', $fileName);
114+
115+
$generator->generateFile(
116+
$filePath,
117+
'stimulus/Controller.tpl.php',
118+
[
119+
'targets' => $targets,
120+
'values' => $values,
121+
]
122+
);
123+
124+
$generator->writeChanges();
125+
126+
$this->writeSuccessMessage($io);
127+
128+
$io->text([
129+
'Next:',
130+
sprintf('- Open <info>%s</info> and add the code you need', $filePath),
131+
'Find the documentation at <fg=yellow>https://github.com/symfony/stimulus-bridge</>',
132+
]);
133+
}
134+
135+
private function askForNextTarget(ConsoleStyle $io, array $targets, bool $isFirstTarget)
136+
{
137+
if ($isFirstTarget) {
138+
$questionText = 'New target name (press <return> to stop adding targets)';
139+
} else {
140+
$questionText = 'Add another target? Enter the target name (or press <return> to stop adding targets)';
141+
}
142+
143+
$targetName = $io->ask($questionText, null, function ($name) use ($targets) {
144+
if (\in_array($name, $targets)) {
145+
throw new \InvalidArgumentException(sprintf('The "%s" target already exists.', $name));
146+
}
147+
148+
return $name;
149+
});
150+
151+
if (!$targetName) {
152+
return null;
153+
}
154+
155+
return $targetName;
156+
}
157+
158+
private function askForNextValue(ConsoleStyle $io, array $values, bool $isFirstValue)
159+
{
160+
if ($isFirstValue) {
161+
$questionText = 'New value name (press <return> to stop adding values)';
162+
} else {
163+
$questionText = 'Add another value? Enter the value name (or press <return> to stop adding values)';
164+
}
165+
166+
$valueName = $io->ask($questionText, null, function ($name) use ($values) {
167+
if (\array_key_exists($name, $values)) {
168+
throw new \InvalidArgumentException(sprintf('The "%s" value already exists.', $name));
169+
}
170+
171+
return $name;
172+
});
173+
174+
if (!$valueName) {
175+
return null;
176+
}
177+
178+
$defaultType = 'String';
179+
// try to guess the type by the value name prefix/suffix
180+
// convert to snake case for simplicity
181+
$snakeCasedField = Str::asSnakeCase($valueName);
182+
183+
if ('_id' === $suffix = substr($snakeCasedField, -3)) {
184+
$defaultType = 'Number';
185+
} elseif (0 === strpos($snakeCasedField, 'is_')) {
186+
$defaultType = 'Boolean';
187+
} elseif (0 === strpos($snakeCasedField, 'has_')) {
188+
$defaultType = 'Boolean';
189+
}
190+
191+
$type = null;
192+
$types = $this->getValuesTypes();
193+
while (null === $type) {
194+
$question = new Question('Value type (enter <comment>?</comment> to see all types)', $defaultType);
195+
$question->setAutocompleterValues($types);
196+
$type = $io->askQuestion($question);
197+
198+
if ('?' === $type) {
199+
$this->printAvailableTypes($io);
200+
$io->writeln('');
201+
202+
$type = null;
203+
} elseif (!\in_array($type, $types)) {
204+
$this->printAvailableTypes($io);
205+
$io->error(sprintf('Invalid type "%s".', $type));
206+
$io->writeln('');
207+
208+
$type = null;
209+
}
210+
}
211+
212+
return ['name' => $valueName, 'type' => $type];
213+
}
214+
215+
private function printAvailableTypes(ConsoleStyle $io)
216+
{
217+
$allTypes = $this->getValuesTypes();
218+
foreach ($allTypes as $type) {
219+
$io->writeln(sprintf('<info>%s</info>', $type));
220+
}
221+
}
222+
223+
private function getValuesTypes(): array
224+
{
225+
return [
226+
'Array',
227+
'Boolean',
228+
'Number',
229+
'Object',
230+
'String',
231+
];
232+
}
233+
234+
public function configureDependencies(DependencyBuilder $dependencies)
235+
{
236+
$dependencies->addClassDependency(
237+
WebpackEncoreBundle::class,
238+
'webpack-encore-bundle'
239+
);
240+
}
241+
}

src/Resources/config/makers.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,5 +129,9 @@
129129
<argument>%kernel.project_dir%</argument>
130130
<tag name="maker.command" />
131131
</service>
132+
133+
<service id="maker.maker.make_stimulus_controller" class="Symfony\Bundle\MakerBundle\Maker\MakeStimulusController">
134+
<tag name="maker.command" />
135+
</service>
132136
</services>
133137
</container>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
The <info>%command.name%</info> command generates new Stimulus Controller.
2+
3+
<info>php %command.full_name% hello</info>
4+
5+
If the argument is missing, the command will ask for the controller name interactively.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
3+
/*
4+
* The following line makes this controller "lazy": it won't be downloaded until needed
5+
* See https://github.com/symfony/stimulus-bridge#lazy-controllers
6+
*/
7+
/* stimulusFetch: 'lazy' */
8+
export default class extends Controller {
9+
<?= $targets ? " static targets = $targets\n" : "" ?>
10+
<?php if ($values) { ?>
11+
static values = {
12+
<?php foreach ($values as $value): ?>
13+
<?= $value['name'] ?>: <?= $value['type'] ?>,
14+
<?php endforeach; ?>
15+
}
16+
<?php } ?>
17+
// ...
18+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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\Tests\Maker;
13+
14+
use Symfony\Bundle\MakerBundle\Maker\MakeStimulusController;
15+
use Symfony\Bundle\MakerBundle\Test\MakerTestCase;
16+
use Symfony\Bundle\MakerBundle\Test\MakerTestRunner;
17+
18+
class MakeStimulusControllerTest extends MakerTestCase
19+
{
20+
protected function getMakerClass(): string
21+
{
22+
return MakeStimulusController::class;
23+
}
24+
25+
public function getTestDetails()
26+
{
27+
yield 'it_generates_stimulus_controller_with_targets' => [$this->createMakerTest()
28+
->run(function (MakerTestRunner $runner) {
29+
$runner->runMaker(
30+
[
31+
'with_targets', //controller name
32+
'js', //controller language
33+
'yes', //add targets
34+
'results', //first target
35+
'messages', //second target
36+
'errors', //third target
37+
'', //empty input to stop adding targets
38+
]);
39+
40+
$generatedFilePath = $runner->getPath('assets/controllers/with_targets_controller.js');
41+
42+
$this->assertFileExists($generatedFilePath);
43+
44+
$generatedFileContents = file_get_contents($generatedFilePath);
45+
$expectedContents = file_get_contents(__DIR__.'/../fixtures/make-stimulus-controller/with_targets.js');
46+
47+
$this->assertSame(
48+
$expectedContents,
49+
$generatedFileContents
50+
);
51+
}),
52+
];
53+
54+
yield 'it_generates_stimulus_controller_without_targets' => [$this->createMakerTest()
55+
->run(function (MakerTestRunner $runner) {
56+
$runner->runMaker(
57+
[
58+
'without_targets', //controller name
59+
'js', //controller language
60+
'no', //do not add targets
61+
]);
62+
63+
$generatedFilePath = $runner->getPath('assets/controllers/without_targets_controller.js');
64+
65+
$this->assertFileExists($generatedFilePath);
66+
67+
$generatedFileContents = file_get_contents($generatedFilePath);
68+
$expectedContents = file_get_contents(__DIR__.'/../fixtures/make-stimulus-controller/without_targets.js');
69+
70+
$this->assertSame(
71+
$expectedContents,
72+
$generatedFileContents
73+
);
74+
}),
75+
];
76+
77+
yield 'it_generates_typescript_stimulus_controller' => [$this->createMakerTest()
78+
->run(function (MakerTestRunner $runner) {
79+
$runner->runMaker(
80+
[
81+
'typescript', //controller name
82+
'ts', //controller language
83+
'no', //do not add targets
84+
]);
85+
86+
$this->assertFileExists($runner->getPath('assets/controllers/typescript_controller.ts'));
87+
}),
88+
];
89+
}
90+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
3+
/*
4+
* The following line makes this controller "lazy": it won't be downloaded until needed
5+
* See https://github.com/symfony/stimulus-bridge#lazy-controllers
6+
*/
7+
/* stimulusFetch: 'lazy' */
8+
export default class extends Controller {
9+
static targets = ['results', 'messages', 'errors']
10+
// ...
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
3+
/*
4+
* The following line makes this controller "lazy": it won't be downloaded until needed
5+
* See https://github.com/symfony/stimulus-bridge#lazy-controllers
6+
*/
7+
/* stimulusFetch: 'lazy' */
8+
export default class extends Controller {
9+
// ...
10+
}

0 commit comments

Comments
 (0)