Skip to content

Commit 6ea6684

Browse files
committed
[make:voter|crud] generate all classes with final keyword
1 parent ba37c4f commit 6ea6684

File tree

18 files changed

+328
-19
lines changed

18 files changed

+328
-19
lines changed

src/DependencyInjection/Configuration.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public function getConfigTreeBuilder(): TreeBuilder
2424
$rootNode
2525
->children()
2626
->scalarNode('root_namespace')->defaultValue('App')->end()
27+
->booleanNode('generate_final_classes')->defaultTrue()->end()
28+
->booleanNode('generate_final_entities')->defaultFalse()->end()
2729
->end()
2830
;
2931

src/DependencyInjection/MakerExtension.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ public function load(array $configs, ContainerBuilder $container): void
4545
$doctrineHelperDefinition = $container->getDefinition('maker.doctrine_helper');
4646
$doctrineHelperDefinition->replaceArgument(0, $rootNamespace.'\\Entity');
4747

48+
$componentGeneratorDefinition = $container->getDefinition('maker.template_component_generator');
49+
$componentGeneratorDefinition
50+
->replaceArgument(0, $config['generate_final_classes'])
51+
->replaceArgument(1, $config['generate_final_entities'])
52+
;
53+
4854
$container->registerForAutoconfiguration(MakerInterface::class)
4955
->addTag(MakeCommandRegistrationPass::MAKER_TAG);
5056
}

src/Generator.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1515
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
1616
use Symfony\Bundle\MakerBundle\Util\ClassNameDetails;
17+
use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData;
1718
use Symfony\Bundle\MakerBundle\Util\PhpCompatUtil;
1819
use Symfony\Bundle\MakerBundle\Util\TemplateComponentGenerator;
1920

@@ -60,6 +61,10 @@ public function generateClass(string $className, string $templateName, array $va
6061
throw new \LogicException(\sprintf('Could not determine where to locate the new class "%s", maybe try with a full namespace like "\\My\\Full\\Namespace\\%s"', $className, Str::getShortClassName($className)));
6162
}
6263

64+
if (\array_key_exists('class_data', $variables) && $variables['class_data'] instanceof ClassData) {
65+
$this->templateComponentGenerator->configureClass($variables['class_data']);
66+
}
67+
6368
$variables = array_merge($variables, [
6469
'class_name' => Str::getShortClassName($className),
6570
'namespace' => Str::getNamespace($className),

src/Maker/MakeCrud.php

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Symfony\Bundle\MakerBundle\Maker\Common\CanGenerateTestsTrait;
2828
use Symfony\Bundle\MakerBundle\Renderer\FormTypeRenderer;
2929
use Symfony\Bundle\MakerBundle\Str;
30+
use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData;
3031
use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator;
3132
use Symfony\Bundle\MakerBundle\Validator;
3233
use Symfony\Bundle\TwigBundle\TwigBundle;
@@ -131,6 +132,11 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
131132
];
132133
}
133134

135+
$controllerClassData = ClassData::create(
136+
class: sprintf('App\Controller\%sController', $this->controllerClassName),
137+
extendsClass: AbstractController::class,
138+
);
139+
134140
$controllerClassDetails = $generator->createClassNameDetails(
135141
$this->controllerClassName,
136142
'Controller\\',
@@ -174,6 +180,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
174180
$controllerClassDetails->getFullName(),
175181
'crud/controller/Controller.tpl.php',
176182
array_merge([
183+
'class_data' => $controllerClassData,
177184
'use_statements' => $useStatements,
178185
'entity_class_name' => $entityClassDetails->getShortName(),
179186
'form_class_name' => $formClassDetails->getShortName(),
@@ -242,10 +249,9 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
242249
}
243250

244251
if ($this->shouldGenerateTests()) {
245-
$testClassDetails = $generator->createClassNameDetails(
246-
$entityClassDetails->getRelativeNameWithoutSuffix(),
247-
'Test\\Controller\\',
248-
'ControllerTest'
252+
$testClassData = ClassData::create(
253+
class: sprintf('App\Tests\Controller\%sControllerTest', $entityClassDetails->getRelativeNameWithoutSuffix()),
254+
extendsClass: WebTestCase::class,
249255
);
250256

251257
$useStatements = new UseStatementGenerator([
@@ -261,18 +267,17 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
261267
$useStatements->addUseStatement(EntityManagerInterface::class);
262268
}
263269

264-
$generator->generateFile(
265-
'tests/Controller/'.$testClassDetails->getShortName().'.php',
270+
$generator->generateClass(
271+
$testClassData->fullClassName,
266272
'crud/test/Test.EntityManager.tpl.php',
267273
[
274+
'class_data' => $testClassData,
268275
'use_statements' => $useStatements,
269276
'entity_full_class_name' => $entityClassDetails->getFullName(),
270277
'entity_class_name' => $entityClassDetails->getShortName(),
271278
'entity_var_singular' => $entityVarSingular,
272279
'route_path' => Str::asRoutePath($controllerClassDetails->getRelativeNameWithoutSuffix()),
273280
'route_name' => $routeName,
274-
'class_name' => Str::getShortClassName($testClassDetails->getFullName()),
275-
'namespace' => Str::getNamespace($testClassDetails->getFullName()),
276281
'form_fields' => $entityDoctrineDetails->getFormFields(),
277282
'repository_class_name' => EntityManagerInterface::class,
278283
'form_field_prefix' => strtolower(Str::asSnakeCase($entityTwigVarSingular)),

src/Maker/MakeVoter.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Bundle\MakerBundle\DependencyBuilder;
1616
use Symfony\Bundle\MakerBundle\Generator;
1717
use Symfony\Bundle\MakerBundle\InputConfiguration;
18+
use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData;
1819
use Symfony\Component\Console\Command\Command;
1920
use Symfony\Component\Console\Input\InputArgument;
2021
use Symfony\Component\Console\Input\InputInterface;
@@ -46,6 +47,11 @@ public function configureCommand(Command $command, InputConfiguration $inputConf
4647

4748
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
4849
{
50+
$classMetaData = ClassData::create(
51+
class: sprintf('App\Security\Voter\%sVoter', $input->getArgument('name')),
52+
extendsClass: Voter::class,
53+
);
54+
4955
$voterClassNameDetails = $generator->createClassNameDetails(
5056
$input->getArgument('name'),
5157
'Security\\Voter\\',
@@ -55,7 +61,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
5561
$generator->generateClass(
5662
$voterClassNameDetails->getFullName(),
5763
'security/Voter.tpl.php',
58-
[]
64+
['class_data' => $classMetaData]
5965
);
6066

6167
$generator->writeChanges();

src/Resources/config/services.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@
8080
</service>
8181

8282
<service id="maker.template_component_generator" class="Symfony\Bundle\MakerBundle\Util\TemplateComponentGenerator">
83+
<argument /> <!-- generate_final_classes -->
84+
<argument /> <!-- generate_final_entities -->
8385
</service>
8486
</services>
8587
</container>

src/Resources/skeleton/crud/controller/Controller.tpl.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<?= $use_statements; ?>
66

77
#[Route('<?= $route_path ?>')]
8-
class <?= $class_name ?> extends AbstractController
8+
<?= $class_data->getClassDeclaration() ?>
99
{
1010
<?= $generator->generateRouteForControllerMethod('/', sprintf('%s_index', $route_name), ['GET']) ?>
1111
<?php if (isset($repository_full_class_name)): ?>

src/Resources/skeleton/crud/test/Test.EntityManager.tpl.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<?= $use_statements; ?>
77

8-
class <?= $class_name ?> extends WebTestCase<?= "\n" ?>
8+
<?= $class_data->getClassDeclaration() ?>
99
{
1010
private KernelBrowser $client;
1111
private EntityManagerInterface $manager;

src/Resources/skeleton/security/Voter.tpl.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
77
use Symfony\Component\Security\Core\User\UserInterface;
88

9-
class <?= $class_name ?> extends Voter
9+
<?= $class_data->getClassDeclaration() ?>
1010
{
1111
public const EDIT = 'POST_EDIT';
1212
public const VIEW = 'POST_VIEW';
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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\Util\ClassSource\Model;
13+
14+
use Symfony\Bundle\MakerBundle\Str;
15+
16+
/**
17+
* @author Jesse Rushlow <[email protected]>
18+
*
19+
* @internal
20+
*/
21+
final class ClassData
22+
{
23+
private function __construct(
24+
public readonly string $className,
25+
public readonly string $namespace,
26+
public readonly string $fullClassName,
27+
public readonly ?string $extends,
28+
public readonly bool $isEntity,
29+
private bool $isFinal = true,
30+
) {
31+
}
32+
33+
public static function create(string $class, ?string $extendsClass = null, bool $isEntity = false): self
34+
{
35+
return new self(
36+
className: Str::getShortClassName($class),
37+
namespace: Str::getNamespace($class),
38+
fullClassName: $class,
39+
extends: null === $extendsClass ? null : Str::getShortClassName($extendsClass),
40+
isEntity: $isEntity,
41+
);
42+
}
43+
44+
public function getClassDeclaration(): string
45+
{
46+
$extendsDeclaration = '';
47+
48+
if (null !== $this->extends) {
49+
$extendsDeclaration = sprintf(' extends %s', $this->extends);
50+
}
51+
52+
return sprintf('%sclass %s%s',
53+
$this->isFinal ? 'final ' : '',
54+
$this->className,
55+
$extendsDeclaration,
56+
);
57+
}
58+
59+
public function setIsFinal(bool $isFinal): self
60+
{
61+
$this->isFinal = $isFinal;
62+
63+
return $this;
64+
}
65+
}

src/Util/TemplateComponentGenerator.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,21 @@
1111

1212
namespace Symfony\Bundle\MakerBundle\Util;
1313

14+
use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData;
15+
1416
/**
1517
* @author Jesse Rushlow <[email protected]>
1618
*
1719
* @internal
1820
*/
1921
final class TemplateComponentGenerator
2022
{
23+
public function __construct(
24+
private bool $generateFinalClasses,
25+
private bool $generateFinalEntities,
26+
) {
27+
}
28+
2129
public function generateRouteForControllerMethod(string $routePath, string $routeName, array $methods = [], bool $indent = true, bool $trailingNewLine = true): string
2230
{
2331
$attribute = \sprintf('%s#[Route(\'%s\', name: \'%s\'', $indent ? ' ' : null, $routePath, $routeName);
@@ -43,4 +51,13 @@ public function getPropertyType(ClassNameDetails $classNameDetails): ?string
4351
{
4452
return \sprintf('%s ', $classNameDetails->getShortName());
4553
}
54+
55+
public function configureClass(ClassData $classMetadata): ClassData
56+
{
57+
if ($classMetadata->isEntity) {
58+
return $classMetadata->setIsFinal($this->generateFinalEntities);
59+
}
60+
61+
return $classMetadata->setIsFinal($this->generateFinalClasses);
62+
}
4663
}

tests/Doctrine/EntityRegeneratorTest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Symfony\Bundle\MakerBundle\Generator;
2424
use Symfony\Bundle\MakerBundle\Util\AutoloaderUtil;
2525
use Symfony\Bundle\MakerBundle\Util\MakerFileLinkFormatter;
26+
use Symfony\Bundle\MakerBundle\Util\TemplateComponentGenerator;
2627
use Symfony\Component\Config\Loader\LoaderInterface;
2728
use Symfony\Component\DependencyInjection\ContainerBuilder;
2829
use Symfony\Component\Filesystem\Filesystem;
@@ -99,7 +100,8 @@ private function doTestRegeneration(string $sourceDir, Kernel $kernel, string $n
99100

100101
$fileManager = new FileManager($fs, $autoloaderUtil, new MakerFileLinkFormatter(null), $tmpDir);
101102
$doctrineHelper = new DoctrineHelper('App\\Entity', $container->get('doctrine'));
102-
$generator = new Generator($fileManager, 'App\\');
103+
$templateComponentGenerator = new TemplateComponentGenerator(false, false);
104+
$generator = new Generator(fileManager: $fileManager, namespacePrefix: 'App\\', templateComponentGenerator: $templateComponentGenerator);
103105
$entityClassGenerator = new EntityClassGenerator($generator, $doctrineHelper);
104106
$regenerator = new EntityRegenerator(
105107
$doctrineHelper,

tests/Maker/MakeVoterTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Bundle\MakerBundle\Maker\MakeVoter;
1515
use Symfony\Bundle\MakerBundle\Test\MakerTestCase;
1616
use Symfony\Bundle\MakerBundle\Test\MakerTestRunner;
17+
use Symfony\Component\Yaml\Yaml;
1718

1819
class MakeVoterTest extends MakerTestCase
1920
{
@@ -32,6 +33,32 @@ public function getTestDetails(): \Generator
3233
'FooBar',
3334
]
3435
);
36+
37+
$expectedVoterPath = \dirname(__DIR__).'/fixtures/make-voter/expected/FooBarVoter.php';
38+
$generatedVoter = $runner->getPath('src/Security/Voter/FooBarVoter.php');
39+
40+
self::assertSame(file_get_contents($expectedVoterPath), file_get_contents($generatedVoter));
41+
}),
42+
];
43+
44+
yield 'it_makes_voter_not_final' => [$this->createMakerTest()
45+
->run(function (MakerTestRunner $runner) {
46+
$runner->writeFile(
47+
'config/packages/dev/maker.yaml',
48+
Yaml::dump(['when@dev' => ['maker' => ['generate_final_classes' => false]]])
49+
);
50+
51+
$runner->runMaker(
52+
[
53+
// voter class name
54+
'FooBar',
55+
]
56+
);
57+
58+
$expectedVoterPath = \dirname(__DIR__).'/fixtures/make-voter/expected/not_final_FooBarVoter.php';
59+
$generatedVoter = $runner->getPath('src/Security/Voter/FooBarVoter.php');
60+
61+
self::assertSame(file_get_contents($expectedVoterPath), file_get_contents($generatedVoter));
3562
}),
3663
];
3764
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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\Util\ClassSource;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bundle\MakerBundle\MakerBundle;
16+
use Symfony\Bundle\MakerBundle\Test\MakerTestKernel;
17+
use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData;
18+
19+
class ClassDataTest extends TestCase
20+
{
21+
public function testStaticConstructor(): void
22+
{
23+
$meta = ClassData::create(MakerBundle::class);
24+
25+
// Sanity check in case Maker's NS changes
26+
self::assertSame('Symfony\Bundle\MakerBundle\MakerBundle', MakerBundle::class);
27+
28+
self::assertSame('MakerBundle', $meta->className);
29+
self::assertSame('Symfony\Bundle\MakerBundle', $meta->namespace);
30+
self::assertSame('Symfony\Bundle\MakerBundle\MakerBundle', $meta->fullClassName);
31+
}
32+
33+
public function testGetClassDeclaration(): void
34+
{
35+
$meta = ClassData::create(MakerBundle::class);
36+
37+
self::assertSame('final class MakerBundle', $meta->getClassDeclaration());
38+
}
39+
40+
public function testIsFinal(): void
41+
{
42+
$meta = ClassData::create(MakerBundle::class);
43+
44+
// Default - isFinal - true
45+
self::assertSame('final class MakerBundle', $meta->getClassDeclaration());
46+
47+
// Not Final - isFinal - false
48+
$meta->setIsFinal(false);
49+
self::assertSame('class MakerBundle', $meta->getClassDeclaration());
50+
}
51+
52+
public function testGetClassDeclarationWithExtends(): void
53+
{
54+
$meta = ClassData::create(class: MakerBundle::class, extendsClass: MakerTestKernel::class);
55+
56+
self::assertSame('final class MakerBundle extends MakerTestKernel', $meta->getClassDeclaration());
57+
}
58+
}

0 commit comments

Comments
 (0)