Skip to content

Commit 0103842

Browse files
committed
introduce a final class config param
1 parent d47dce5 commit 0103842

File tree

9 files changed

+136
-1
lines changed

9 files changed

+136
-1
lines changed

src/DependencyInjection/Configuration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public function getConfigTreeBuilder(): TreeBuilder
2424
$rootNode
2525
->children()
2626
->scalarNode('root_namespace')->defaultValue('App')->end()
27+
->booleanNode('use_final_classes')->defaultFalse()->end()
2728
->end()
2829
;
2930

src/DependencyInjection/MakerExtension.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ 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->replaceArgument(0, $config['use_final_classes']);
50+
4851
$container->registerForAutoconfiguration(MakerInterface::class)
4952
->addTag(MakeCommandRegistrationPass::MAKER_TAG);
5053
}

src/Generator.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public function generateClass(string $className, string $templateName, array $va
6363
$variables = array_merge($variables, [
6464
'class_name' => Str::getShortClassName($className),
6565
'namespace' => Str::getNamespace($className),
66+
'final_class' => $this->templateComponentGenerator->getFinalDeclaration(),
6667
]);
6768

6869
$this->addOperation($targetPath, $templateName, $variables);

src/Resources/config/services.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
</service>
8181

8282
<service id="maker.template_component_generator" class="Symfony\Bundle\MakerBundle\Util\TemplateComponentGenerator">
83+
<argument /> <!-- use_final_classes -->
8384
</service>
8485
</services>
8586
</container>

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-
final class <?= $class_name ?> extends Voter
9+
<?= $final_class ?>class <?= $class_name ?> extends Voter
1010
{
1111
public const EDIT = 'POST_EDIT';
1212
public const VIEW = 'POST_VIEW';

src/Util/TemplateComponentGenerator.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
*/
1919
final class TemplateComponentGenerator
2020
{
21+
public function __construct(
22+
private bool $generateFinalClasses,
23+
) {
24+
}
25+
2126
public function generateRouteForControllerMethod(string $routePath, string $routeName, array $methods = [], bool $indent = true, bool $trailingNewLine = true): string
2227
{
2328
$attribute = sprintf('%s#[Route(\'%s\', name: \'%s\'', $indent ? ' ' : null, $routePath, $routeName);
@@ -43,4 +48,9 @@ public function getPropertyType(ClassNameDetails $classNameDetails): ?string
4348
{
4449
return sprintf('%s ', $classNameDetails->getShortName());
4550
}
51+
52+
public function getFinalDeclaration(): string
53+
{
54+
return $this->generateFinalClasses ? 'final ' : '';
55+
}
4656
}

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_final' => [$this->createMakerTest()
45+
->run(function (MakerTestRunner $runner) {
46+
$runner->writeFile(
47+
'config/packages/dev/maker.yaml',
48+
Yaml::dump(['maker' => ['use_final_classes' => true]])
49+
);
50+
51+
$runner->runMaker(
52+
[
53+
// voter class name
54+
'FooBar',
55+
]
56+
);
57+
58+
$expectedVoterPath = dirname(__DIR__).'/fixtures/make-voter/expected/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: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace App\Security\Voter;
4+
5+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
6+
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
7+
use Symfony\Component\Security\Core\User\UserInterface;
8+
9+
class FooBarVoter extends Voter
10+
{
11+
public const EDIT = 'POST_EDIT';
12+
public const VIEW = 'POST_VIEW';
13+
14+
protected function supports(string $attribute, mixed $subject): bool
15+
{
16+
// replace with your own logic
17+
// https://symfony.com/doc/current/security/voters.html
18+
return in_array($attribute, [self::EDIT, self::VIEW])
19+
&& $subject instanceof \App\Entity\FooBar;
20+
}
21+
22+
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
23+
{
24+
$user = $token->getUser();
25+
26+
// if the user is anonymous, do not grant access
27+
if (!$user instanceof UserInterface) {
28+
return false;
29+
}
30+
31+
// ... (check conditions and return true to grant permission) ...
32+
switch ($attribute) {
33+
case self::EDIT:
34+
// logic to determine if the user can EDIT
35+
// return true or false
36+
break;
37+
38+
case self::VIEW:
39+
// logic to determine if the user can VIEW
40+
// return true or false
41+
break;
42+
}
43+
44+
return false;
45+
}
46+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace App\Security\Voter;
4+
5+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
6+
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
7+
use Symfony\Component\Security\Core\User\UserInterface;
8+
9+
final class FooBarVoter extends Voter
10+
{
11+
public const EDIT = 'POST_EDIT';
12+
public const VIEW = 'POST_VIEW';
13+
14+
protected function supports(string $attribute, mixed $subject): bool
15+
{
16+
// replace with your own logic
17+
// https://symfony.com/doc/current/security/voters.html
18+
return in_array($attribute, [self::EDIT, self::VIEW])
19+
&& $subject instanceof \App\Entity\FooBar;
20+
}
21+
22+
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
23+
{
24+
$user = $token->getUser();
25+
26+
// if the user is anonymous, do not grant access
27+
if (!$user instanceof UserInterface) {
28+
return false;
29+
}
30+
31+
// ... (check conditions and return true to grant permission) ...
32+
switch ($attribute) {
33+
case self::EDIT:
34+
// logic to determine if the user can EDIT
35+
// return true or false
36+
break;
37+
38+
case self::VIEW:
39+
// logic to determine if the user can VIEW
40+
// return true or false
41+
break;
42+
}
43+
44+
return false;
45+
}
46+
}

0 commit comments

Comments
 (0)