Skip to content

Commit f9d9d03

Browse files
jrushlowweaverryan
authored andcommitted
[make:user] user entities implement PasswordAuthenticatedUserInterface
1 parent a395a85 commit f9d9d03

21 files changed

+824
-39
lines changed

src/Doctrine/EntityClassGenerator.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use Symfony\Bundle\MakerBundle\Generator;
1616
use Symfony\Bundle\MakerBundle\Str;
1717
use Symfony\Bundle\MakerBundle\Util\ClassNameDetails;
18+
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
19+
use Symfony\Component\Security\Core\User\UserInterface;
1820

1921
/**
2022
* @internal
@@ -69,6 +71,15 @@ public function generateRepositoryClass(string $repositoryClass, string $entityC
6971
{
7072
$shortEntityClass = Str::getShortClassName($entityClass);
7173
$entityAlias = strtolower($shortEntityClass[0]);
74+
75+
$passwordUserInterfaceName = UserInterface::class;
76+
77+
if (interface_exists(PasswordAuthenticatedUserInterface::class)) {
78+
$passwordUserInterfaceName = PasswordAuthenticatedUserInterface::class;
79+
}
80+
81+
$interfaceClassNameDetails = new ClassNameDetails($passwordUserInterfaceName, 'Symfony\Component\Security\Core\User');
82+
7283
$this->generator->generateClass(
7384
$repositoryClass,
7485
'doctrine/Repository.tpl.php',
@@ -77,6 +88,7 @@ public function generateRepositoryClass(string $repositoryClass, string $entityC
7788
'entity_class_name' => $shortEntityClass,
7889
'entity_alias' => $entityAlias,
7990
'with_password_upgrade' => $withPasswordUpgrade,
91+
'password_upgrade_user_interface' => $interfaceClassNameDetails,
8092
'doctrine_registry_class' => $this->managerRegistryClassName,
8193
'include_example_comments' => $includeExampleComments,
8294
]

src/Maker/MakeUser.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,8 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
159159
true
160160
);
161161
$manipulator->setIo($io);
162-
$this->userClassBuilder->addUserInterfaceImplementation(
163-
$manipulator,
164-
$userClassConfiguration
165-
);
162+
163+
$this->userClassBuilder->addUserInterfaceImplementation($manipulator, $userClassConfiguration);
166164

167165
$generator->dumpFile($classPath, $manipulator->getSourceCode());
168166

src/Resources/skeleton/doctrine/Repository.tpl.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
77
use <?= $doctrine_registry_class; ?>;
88
<?= $with_password_upgrade ? "use Symfony\Component\Security\Core\Exception\UnsupportedUserException;\n" : '' ?>
9+
<?= ($with_password_upgrade && str_contains($password_upgrade_user_interface->getFullName(), 'Password')) ? sprintf("use %s;\n", $password_upgrade_user_interface->getFullName()) : null ?>
910
<?= $with_password_upgrade ? "use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;\n" : '' ?>
10-
<?= $with_password_upgrade ? "use Symfony\Component\Security\Core\User\UserInterface;\n" : '' ?>
11+
<?= ($with_password_upgrade && str_contains($password_upgrade_user_interface->getFullName(), '\UserInterface')) ? sprintf("use %s;\n", $password_upgrade_user_interface->getFullName()) : null ?>
1112

1213
/**
1314
* @method <?= $entity_class_name; ?>|null find($id, $lockMode = null, $lockVersion = null)
@@ -28,7 +29,7 @@ public function __construct(ManagerRegistry $registry)
2829
/**
2930
* Used to upgrade (rehash) the user's password automatically over time.
3031
*/
31-
public function upgradePassword(UserInterface $user, string $newEncodedPassword): void
32+
public function upgradePassword(<?= sprintf('%s ', $password_upgrade_user_interface->getShortName()); ?>$user, string $newEncodedPassword): void
3233
{
3334
if (!$user instanceof <?= $entity_class_name ?>) {
3435
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user)));

src/Security/UserClassBuilder.php

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
use PhpParser\Node;
1515
use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator;
16+
use Symfony\Component\HttpKernel\Kernel;
17+
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
1618
use Symfony\Component\Security\Core\User\UserInterface;
1719

1820
/**
@@ -22,21 +24,49 @@
2224
*/
2325
final class UserClassBuilder
2426
{
25-
public function addUserInterfaceImplementation(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig)
27+
public function addUserInterfaceImplementation(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig): void
2628
{
2729
$manipulator->addInterface(UserInterface::class);
2830

2931
$this->addGetUsername($manipulator, $userClassConfig);
3032

3133
$this->addGetRoles($manipulator, $userClassConfig);
3234

33-
$this->addGetPassword($manipulator, $userClassConfig);
34-
35-
$this->addGetSalt($manipulator, $userClassConfig);
35+
$this->addPasswordImplementation($manipulator, $userClassConfig);
3636

3737
$this->addEraseCredentials($manipulator, $userClassConfig);
3838
}
3939

40+
private function addPasswordImplementation(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig): void
41+
{
42+
if (60000 > Kernel::VERSION_ID) {
43+
// Add methods required to fulfill the UserInterface contract
44+
$this->addGetPassword($manipulator, $userClassConfig);
45+
$this->addGetSalt($manipulator, $userClassConfig);
46+
47+
// Symfony >=5.3 uses "@see PasswordAuthenticatedInterface" for getPassword()
48+
if (interface_exists(PasswordAuthenticatedUserInterface::class)) {
49+
$manipulator->addUseStatementIfNecessary(PasswordAuthenticatedUserInterface::class);
50+
}
51+
52+
// Future proof the entity for >= Symfony 6 && the entity will check passwords
53+
if ($userClassConfig->hasPassword() && interface_exists(PasswordAuthenticatedUserInterface::class)) {
54+
$manipulator->addInterface(PasswordAuthenticatedUserInterface::class);
55+
}
56+
57+
return;
58+
}
59+
60+
// Future proof >= Symfony 6.0
61+
if (!$userClassConfig->hasPassword()) {
62+
return;
63+
}
64+
65+
$manipulator->addInterface(PasswordAuthenticatedUserInterface::class);
66+
67+
$this->addGetPassword($manipulator, $userClassConfig);
68+
}
69+
4070
private function addGetUsername(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig)
4171
{
4272
if ($userClassConfig->isEntity()) {
@@ -161,16 +191,18 @@ private function addGetRoles(ClassSourceManipulator $manipulator, UserClassConfi
161191

162192
private function addGetPassword(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig)
163193
{
194+
$seeInterface = interface_exists(PasswordAuthenticatedUserInterface::class) ? '@see PasswordAuthenticatedUserInterface' : '@see UserInterface';
195+
164196
if (!$userClassConfig->hasPassword()) {
165197
// add an empty method only
166198
$builder = $manipulator->createMethodBuilder(
167199
'getPassword',
168200
'string',
169201
true,
170202
[
171-
'This method is not needed for apps that do not check user passwords.',
203+
'This method can be removed in Symfony 6.0 - is not needed for apps that do not check user passwords.',
172204
'',
173-
'@see UserInterface',
205+
$seeInterface,
174206
]
175207
);
176208

@@ -221,9 +253,8 @@ private function addGetPassword(ClassSourceManipulator $manipulator, UserClassCo
221253
'string',
222254
false,
223255
[
224-
'@see UserInterface',
225-
],
226-
true
256+
$seeInterface,
257+
]
227258
);
228259
}
229260

@@ -236,7 +267,7 @@ private function addGetSalt(ClassSourceManipulator $manipulator, UserClassConfig
236267
];
237268
} else {
238269
$methodDescription = [
239-
'This method is not needed for apps that do not check user passwords.',
270+
'This method can be removed in Symfony 6.0 - is not needed for apps that do not check user passwords.',
240271
];
241272
}
242273

tests/Maker/MakeUserTest.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function getTestDetails()
2727
'y', // entity
2828
'email', // identity property
2929
'y', // with password
30-
'y', // argon
30+
'y', // argon @TODO This should only be done in <5.0
3131
])
3232
->addExtraDependencies('doctrine')
3333
->setFixtureFilesPath(__DIR__.'/../fixtures/MakeUserEntityPassword')
@@ -37,6 +37,20 @@ public function getTestDetails()
3737
->updateSchemaAfterCommand(),
3838
];
3939

40+
yield 'user_security_entity_with_password_authenticated_user_interface' => [MakerTestDetails::createTest(
41+
$this->getMakerInstance(MakeUser::class),
42+
[
43+
// user class name
44+
'User',
45+
'y', // entity
46+
'email', // identity property
47+
'y', // with password
48+
])
49+
->addRequiredPackageVersion('symfony/security-bundle', '>=5.3')
50+
->addExtraDependencies('doctrine')
51+
->setFixtureFilesPath(__DIR__.'/../fixtures/MakeUserEntityPasswordAuthenticatedUserInterface'),
52+
];
53+
4054
yield 'user_security_model_no_password' => [MakerTestDetails::createTest(
4155
$this->getMakerInstance(MakeUser::class),
4256
[

tests/Security/UserClassBuilderTest.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Bundle\MakerBundle\Security\UserClassBuilder;
1616
use Symfony\Bundle\MakerBundle\Security\UserClassConfiguration;
1717
use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator;
18+
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
1819

1920
class UserClassBuilderTest extends TestCase
2021
{
@@ -33,7 +34,15 @@ public function testAddUserInterfaceImplementation(UserClassConfiguration $userC
3334
$classBuilder = new UserClassBuilder();
3435
$classBuilder->addUserInterfaceImplementation($manipulator, $userClassConfig);
3536

36-
$expectedPath = __DIR__.'/fixtures/expected/'.$expectedFilename;
37+
$expectedPath = __DIR__.'/fixtures/expected';
38+
39+
// Can be removed when < Symfony 6 support is dropped.
40+
if (!interface_exists(PasswordAuthenticatedUserInterface::class)) {
41+
$expectedPath = sprintf('%s/legacy', $expectedPath);
42+
}
43+
44+
$expectedPath = sprintf('%s/%s', $expectedPath, $expectedFilename);
45+
3746
if (!file_exists($expectedPath)) {
3847
throw new \Exception(sprintf('Expected file missing: "%s"', $expectedPath));
3948
}

tests/Security/fixtures/expected/UserEntityEmailWithPassword.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
namespace App\Entity;
44

55
use Doctrine\ORM\Mapping as ORM;
6+
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
67
use Symfony\Component\Security\Core\User\UserInterface;
78

89
/**
910
* @ORM\Entity()
1011
*/
11-
class User implements UserInterface
12+
class User implements UserInterface, PasswordAuthenticatedUserInterface
1213
{
1314
/**
1415
* @ORM\Id
@@ -80,11 +81,11 @@ public function setRoles(array $roles): self
8081
}
8182

8283
/**
83-
* @see UserInterface
84+
* @see PasswordAuthenticatedUserInterface
8485
*/
8586
public function getPassword(): string
8687
{
87-
return (string) $this->password;
88+
return $this->password;
8889
}
8990

9091
public function setPassword(string $password): self

tests/Security/fixtures/expected/UserEntityUser_nameWithPassword.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
namespace App\Entity;
44

55
use Doctrine\ORM\Mapping as ORM;
6+
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
67
use Symfony\Component\Security\Core\User\UserInterface;
78

89
/**
910
* @ORM\Entity()
1011
*/
11-
class User implements UserInterface
12+
class User implements UserInterface, PasswordAuthenticatedUserInterface
1213
{
1314
/**
1415
* @ORM\Id
@@ -75,11 +76,11 @@ public function setRoles(array $roles): self
7576
}
7677

7778
/**
78-
* @see UserInterface
79+
* @see PasswordAuthenticatedUserInterface
7980
*/
8081
public function getPassword(): string
8182
{
82-
return (string) $this->password;
83+
return $this->password;
8384
}
8485

8586
public function setPassword(string $password): self

tests/Security/fixtures/expected/UserEntityUsernameNoPassword.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace App\Entity;
44

55
use Doctrine\ORM\Mapping as ORM;
6+
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
67
use Symfony\Component\Security\Core\User\UserInterface;
78

89
/**
@@ -69,17 +70,17 @@ public function setRoles(array $roles): self
6970
}
7071

7172
/**
72-
* This method is not needed for apps that do not check user passwords.
73+
* This method can be removed in Symfony 6.0 - is not needed for apps that do not check user passwords.
7374
*
74-
* @see UserInterface
75+
* @see PasswordAuthenticatedUserInterface
7576
*/
7677
public function getPassword(): ?string
7778
{
7879
return null;
7980
}
8081

8182
/**
82-
* This method is not needed for apps that do not check user passwords.
83+
* This method can be removed in Symfony 6.0 - is not needed for apps that do not check user passwords.
8384
*
8485
* @see UserInterface
8586
*/

tests/Security/fixtures/expected/UserEntityUsernameWithPassword.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
namespace App\Entity;
44

55
use Doctrine\ORM\Mapping as ORM;
6+
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
67
use Symfony\Component\Security\Core\User\UserInterface;
78

89
/**
910
* @ORM\Entity()
1011
*/
11-
class User implements UserInterface
12+
class User implements UserInterface, PasswordAuthenticatedUserInterface
1213
{
1314
/**
1415
* @ORM\Id
@@ -75,11 +76,11 @@ public function setRoles(array $roles): self
7576
}
7677

7778
/**
78-
* @see UserInterface
79+
* @see PasswordAuthenticatedUserInterface
7980
*/
8081
public function getPassword(): string
8182
{
82-
return (string) $this->password;
83+
return $this->password;
8384
}
8485

8586
public function setPassword(string $password): self

tests/Security/fixtures/expected/UserModelEmailWithPassword.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
namespace App\Security;
44

5+
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
56
use Symfony\Component\Security\Core\User\UserInterface;
67

7-
class User implements UserInterface
8+
class User implements UserInterface, PasswordAuthenticatedUserInterface
89
{
910
private $email;
1011

@@ -57,11 +58,11 @@ public function setRoles(array $roles): self
5758
}
5859

5960
/**
60-
* @see UserInterface
61+
* @see PasswordAuthenticatedUserInterface
6162
*/
6263
public function getPassword(): string
6364
{
64-
return (string) $this->password;
65+
return $this->password;
6566
}
6667

6768
public function setPassword(string $password): self

tests/Security/fixtures/expected/UserModelUsernameNoPassword.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Security;
44

5+
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
56
use Symfony\Component\Security\Core\User\UserInterface;
67

78
class User implements UserInterface
@@ -47,17 +48,17 @@ public function setRoles(array $roles): self
4748
}
4849

4950
/**
50-
* This method is not needed for apps that do not check user passwords.
51+
* This method can be removed in Symfony 6.0 - is not needed for apps that do not check user passwords.
5152
*
52-
* @see UserInterface
53+
* @see PasswordAuthenticatedUserInterface
5354
*/
5455
public function getPassword(): ?string
5556
{
5657
return null;
5758
}
5859

5960
/**
60-
* This method is not needed for apps that do not check user passwords.
61+
* This method can be removed in Symfony 6.0 - is not needed for apps that do not check user passwords.
6162
*
6263
* @see UserInterface
6364
*/

0 commit comments

Comments
 (0)