Skip to content

Commit 7134fb1

Browse files
committed
feature #59 Adding a new MakerInterface, instead of extending AbstractCommand (weaverryan)
This PR was squashed before being merged into the 1.0-dev branch (closes #59). Discussion ---------- Adding a new MakerInterface, instead of extending AbstractCommand Hi guys! This diff looks big, but it makes no visible changes to the bundle. This only changes the way that custom maker commands are created: instead of extending from a base class and overriding methods, you'll implement the new `MakerInterface`. The reason behind this is simple: we need to tag a stable release and having a concrete interface gives us more flexibility going forward (i.e. we could add other optional interfaces for more functionality, deprecate this interface in favor of a new one, etc). There are 2 powerful functional tests that pretty much cover everything. One actually tries all of the Maker classes to make sure they work (that existed before) and another tests that all Makers are wired correctly in the container. The most important thing to look at is the new `MakerInterface`. Cheers! Commits ------- 8690210 adding public const explictly 34f8bbd adding phpcs rules and using short arrays e3f5fa8 README tweak 22b7555 fabbot 0af6178 adding interface docs c631196 updating README 1d22c8c Adding a new MakerInterface, instead of extending AbstractCommand
2 parents a011be1 + 8690210 commit 7134fb1

29 files changed

+748
-364
lines changed

.php_cs.dist

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
if (!file_exists(__DIR__.'/src')) {
4+
exit(0);
5+
}
6+
return PhpCsFixer\Config::create()
7+
->setRules(array(
8+
'@Symfony' => true,
9+
'@Symfony:risky' => true,
10+
'array_syntax' => array('syntax' => 'short'),
11+
'protected_to_private' => false,
12+
))
13+
->setRiskyAllowed(true)
14+
->setFinder(
15+
PhpCsFixer\Finder::create()
16+
->in(__DIR__.'/src')
17+
)
18+
;

README.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
1-
SymfonyMakerBundle
2-
==================
1+
The Symfony MakerBundle
2+
=======================
33

4-
Symfony Maker helps you creating empty commands, controllers, form classes,
5-
tests and more so you can forget about the required boilerplate code. This
6-
bundle is an alternative to [SensioGeneratorBundle][1] for modern Symfony
7-
applications and requires using Symfony 3.4 or newer and [Symfony Flex][2].
4+
The MakerBundle is the fastest way to generate the most common code you'll
5+
need in a Symfony app: commands, controllers, form classes, event susbcribers
6+
and more! This bundle is an alternative to [SensioGeneratorBundle][1] for modern
7+
Symfony applications and requires Symfony 3.4 or newer and [Symfony Flex][2].
88

9-
[Read the docs][3]
9+
[Read the documentation][3]
10+
11+
Backwards Compatibility Promise
12+
-------------------------------
13+
14+
This bundle shares the [backwards compatibility promise][4] from
15+
Symfony. But, with a few clarifications.
16+
17+
A) The input arguments or options to a command *may* change between
18+
minor releases. If you're using the commands in an automated,
19+
scripted way, be aware of this.
20+
21+
B) The generated code itself may change between minor releases. This
22+
will allow us to continuously improve the generated code!
1023

1124
[1]: https://github.com/sensiolabs/SensioGeneratorBundle
1225
[2]: https://symfony.com/doc/current/setup/flex.html
1326
[3]: src/Resources/doc/index.rst
27+
[4]: http://symfony.com/doc/current/contributing/code/bc.html

src/Command/AbstractCommand.php renamed to src/Command/MakerCommand.php

Lines changed: 25 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,79 +15,54 @@
1515
use Symfony\Bundle\MakerBundle\DependencyBuilder;
1616
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
1717
use Symfony\Bundle\MakerBundle\Generator;
18+
use Symfony\Bundle\MakerBundle\InputConfiguration;
19+
use Symfony\Bundle\MakerBundle\MakerInterface;
1820
use Symfony\Bundle\MakerBundle\Validator;
1921
use Symfony\Component\Console\Command\Command;
2022
use Symfony\Component\Console\Input\InputInterface;
2123
use Symfony\Component\Console\Output\OutputInterface;
22-
use Symfony\Flex\Recipe;
2324

2425
/**
25-
* @author Javier Eguiluz <[email protected]>
26-
* @author Ryan Weaver <[email protected]>
26+
* Used as the Command class for the makers.
27+
*
28+
* @internal
2729
*/
28-
abstract class AbstractCommand extends Command
30+
final class MakerCommand extends Command
2931
{
30-
/** @var ConsoleStyle */
31-
protected $io;
32-
/** @var InputInterface */
33-
protected $input;
32+
private $maker;
3433
private $generator;
34+
private $inputConfig;
35+
/** @var ConsoleStyle */
36+
private $io;
3537
private $checkDependencies = true;
36-
private $nonInteractiveArguments = [];
3738

38-
public function __construct(Generator $generator)
39+
public function __construct(MakerInterface $maker, Generator $generator)
3940
{
40-
parent::__construct();
41+
$this->maker = $maker;
4142
$this->generator = $generator;
42-
}
43-
44-
/**
45-
* Returns the values used to fill in the skeleton files of the generated
46-
* code and the success/error messages as pairs of param_name => param_value.
47-
* (e.g. ['user' => 'Jane', 'project_dir' => realpath(__DIR__.'/..')])
48-
*/
49-
abstract protected function getParameters(): array;
50-
51-
/**
52-
* Returns the list of files to generate as pairs of skeleton_filepath => generated_filepath
53-
* Skeleton files must be absolute paths and generated paths are relative to the app.
54-
* (e.g. [__DIR__.'/../Resources/skeleton/command/Command.php.txt' => 'src/Command/'.$params['command_class_name'].'.php'])
55-
*/
56-
abstract protected function getFiles(array $params): array;
57-
58-
/**
59-
* Optional information displayed to the user after all files have been generated.
60-
*/
61-
abstract protected function writeNextStepsMessage(array $params, ConsoleStyle $io);
43+
$this->inputConfig = new InputConfiguration();
6244

63-
/**
64-
* Defines the optional or required dependencies of the maker command, which are
65-
* checked before running the command and used to display actionable error messages.
66-
*/
67-
abstract protected function configureDependencies(DependencyBuilder $dependencies);
45+
parent::__construct();
46+
}
6847

69-
/**
70-
* Call in configure() to disable the automatic interactive prompt for an arg.
71-
*/
72-
protected function setArgumentAsNonInteractive(string $argumentName)
48+
protected function configure()
7349
{
74-
$this->nonInteractiveArguments[] = $argumentName;
50+
$this->maker->configureCommand($this, $this->inputConfig);
7551
}
7652

7753
protected function initialize(InputInterface $input, OutputInterface $output)
7854
{
7955
$this->io = new ConsoleStyle($input, $output);
80-
$this->input = $input;
8156

8257
if ($this->checkDependencies) {
8358
if (!class_exists(Recipe::class)) {
8459
throw new RuntimeCommandException(sprintf('The generator commands require your app to use Symfony Flex & a Flex directory structure. See https://symfony.com/doc/current/setup/flex.html'));
8560
}
8661

8762
$dependencies = new DependencyBuilder();
88-
$this->configureDependencies($dependencies);
63+
$this->maker->configureDependencies($dependencies);
8964
if ($missingPackages = $dependencies->getMissingDependencies()) {
90-
throw new RuntimeCommandException(sprintf("Missing package%s: to use the %s command, run: \n\ncomposer require %s", count($missingPackages) === 1 ? '' : 's', $this->getName(), implode(' ', $missingPackages)));
65+
throw new RuntimeCommandException(sprintf("Missing package%s: to use the %s command, run: \n\ncomposer require %s", 1 === count($missingPackages) ? '' : 's', $this->getName(), implode(' ', $missingPackages)));
9166
}
9267
}
9368
}
@@ -99,28 +74,30 @@ protected function interact(InputInterface $input, OutputInterface $output)
9974
continue;
10075
}
10176

102-
if (in_array($argument->getName(), $this->nonInteractiveArguments, true)) {
77+
if (in_array($argument->getName(), $this->inputConfig->getNonInteractiveArguments(), true)) {
10378
continue;
10479
}
10580

10681
$value = $this->io->ask($argument->getDescription(), $argument->getDefault(), [Validator::class, 'notBlank']);
10782
$input->setArgument($argument->getName(), $value);
10883
}
84+
85+
$this->maker->interact($input, $this->io, $this);
10986
}
11087

11188
protected function execute(InputInterface $input, OutputInterface $output)
11289
{
11390
$this->generator->setIO($this->io);
114-
$params = $this->getParameters();
115-
$this->generator->generate($params, $this->getFiles($params));
91+
$params = $this->maker->getParameters($input);
92+
$this->generator->generate($params, $this->maker->getFiles($params));
11693

11794
$this->io->newLine();
11895
$this->io->writeln(' <bg=green;fg=white> </>');
11996
$this->io->writeln(' <bg=green;fg=white> Success! </>');
12097
$this->io->writeln(' <bg=green;fg=white> </>');
12198
$this->io->newLine();
12299

123-
$this->writeNextStepsMessage($params, $this->io);
100+
$this->maker->writeNextStepsMessage($params, $this->io);
124101
}
125102

126103
/**
@@ -130,12 +107,4 @@ public function setCheckDependencies(bool $checkDeps)
130107
{
131108
$this->checkDependencies = $checkDeps;
132109
}
133-
134-
/**
135-
* @internal Used for testing commands
136-
*/
137-
public function setGenerator(Generator $generator)
138-
{
139-
$this->generator = $generator;
140-
}
141110
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass;
4+
5+
use Symfony\Bundle\MakerBundle\Command\MakerCommand;
6+
use Symfony\Bundle\MakerBundle\MakerInterface;
7+
use Symfony\Bundle\MakerBundle\Str;
8+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
9+
use Symfony\Component\DependencyInjection\ContainerBuilder;
10+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
11+
use Symfony\Component\DependencyInjection\Reference;
12+
13+
class MakeCommandRegistrationPass implements CompilerPassInterface
14+
{
15+
public const MAKER_TAG = 'maker.command';
16+
17+
public function process(ContainerBuilder $container)
18+
{
19+
foreach ($container->findTaggedServiceIds(self::MAKER_TAG) as $id => $tags) {
20+
$def = $container->getDefinition($id);
21+
$class = $container->getParameterBag()->resolveValue($def->getClass());
22+
if (!is_subclass_of($class, MakerInterface::class)) {
23+
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, MakerInterface::class));
24+
}
25+
26+
$container->register(
27+
sprintf('maker.auto_command.%s', Str::asTwigVariable($class::getCommandName())),
28+
MakerCommand::class
29+
)->setArguments([
30+
new Reference($id),
31+
new Reference('maker.generator'),
32+
])->addTag('console.command', ['command' => $class::getCommandName()]);
33+
}
34+
}
35+
}

src/DependencyInjection/MakerExtension.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Bundle\MakerBundle\DependencyInjection;
1313

14+
use Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass\MakeCommandRegistrationPass;
15+
use Symfony\Bundle\MakerBundle\MakerInterface;
1416
use Symfony\Component\Config\FileLocator;
1517
use Symfony\Component\DependencyInjection\ContainerBuilder;
1618
use Symfony\Component\DependencyInjection\Loader;
@@ -19,7 +21,7 @@
1921
/**
2022
* This is the class that loads and manages your bundle configuration.
2123
*
22-
* @link http://symfony.com/doc/current/cookbook/bundles/extension.html
24+
* @see http://symfony.com/doc/current/cookbook/bundles/extension.html
2325
*/
2426
class MakerExtension extends Extension
2527
{
@@ -30,5 +32,9 @@ public function load(array $configs, ContainerBuilder $container)
3032
{
3133
$loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
3234
$loader->load('services.xml');
35+
$loader->load('makers.xml');
36+
37+
$container->registerForAutoconfiguration(MakerInterface::class)
38+
->addTag(MakeCommandRegistrationPass::MAKER_TAG);
3339
}
3440
}

src/EventRegistry.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
2929
use Symfony\Component\Security\Http\Event\SwitchUserEvent;
3030

31+
/**
32+
* @internal
33+
*/
3134
class EventRegistry
3235
{
3336
// list of *known* events to always include (if they exist)

src/FileManager.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,20 @@
1717
/**
1818
* @author Javier Eguiluz <[email protected]>
1919
* @author Ryan Weaver <[email protected]>
20+
*
21+
* @internal
2022
*/
21-
class FileManager
23+
final class FileManager
2224
{
2325
private $fs;
2426
private $rootDirectory;
2527
/** @var SymfonyStyle */
2628
private $io;
2729

28-
public function __construct(Filesystem $fs, string $rootDirectory = null)
30+
public function __construct(Filesystem $fs, string $rootDirectory)
2931
{
3032
$this->fs = $fs;
31-
$this->rootDirectory = $rootDirectory ?: getcwd();
33+
$this->rootDirectory = $rootDirectory;
3234
}
3335

3436
public function setIO(SymfonyStyle $io): void

src/Generator.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
/**
1818
* @author Javier Eguiluz <[email protected]>
1919
* @author Ryan Weaver <[email protected]>
20+
*
21+
* @internal
2022
*/
21-
class Generator
23+
final class Generator
2224
{
2325
private $fileManager;
2426
private $io;

src/InputConfiguration.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\MakerBundle;
4+
5+
final class InputConfiguration
6+
{
7+
private $nonInteractiveArguments = [];
8+
9+
/**
10+
* Call in MakerInterface::configureCommand() to disable the automatic interactive
11+
* prompt for an argument.
12+
*
13+
* @param string $argumentName
14+
*/
15+
public function setArgumentAsNonInteractive(string $argumentName)
16+
{
17+
$this->nonInteractiveArguments[] = $argumentName;
18+
}
19+
20+
public function getNonInteractiveArguments(): array
21+
{
22+
return $this->nonInteractiveArguments;
23+
}
24+
}

0 commit comments

Comments
 (0)