Skip to content

Commit 4b94fc1

Browse files
committed
feature #862 [make:user] implement getUserIdentifier if required (jrushlow)
This PR was squashed before being merged into the 1.0-dev branch. Discussion ---------- [make:user] implement getUserIdentifier if required handle `getUsername()` -> `getUserIdentifier()` in Symfony 5.3 symfony/symfony#40403 Commits ------- 1d83924 [make:user] implement getUserIdentifier if required
2 parents 02386e1 + 1d83924 commit 4b94fc1

19 files changed

+268
-222
lines changed

src/Maker/MakeUser.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use Symfony\Component\Console\Input\InputOption;
3434
use Symfony\Component\Security\Core\Encoder\Argon2iPasswordEncoder;
3535
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
36+
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
3637
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
3738
use Symfony\Component\Yaml\Yaml;
3839

@@ -171,6 +172,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
171172
$userClassConfiguration->getUserProviderClass(),
172173
'security/UserProvider.tpl.php',
173174
[
175+
'uses_user_identifier' => class_exists(UserNotFoundException::class),
174176
'user_short_name' => $userClassNameDetails->getShortName(),
175177
]
176178
);

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace <?= $namespace; ?>;
44

55
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
6-
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
6+
use Symfony\Component\Security\Core\Exception\<?= $uses_user_identifier ? 'UserNotFoundException' : 'UsernameNotFoundException' ?>;
77
<?= ($password_upgrader = interface_exists('Symfony\Component\Security\Core\User\PasswordUpgraderInterface')) ? "use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;\n" : '' ?>
88
use Symfony\Component\Security\Core\User\UserInterface;
99
use Symfony\Component\Security\Core\User\UserProviderInterface;
@@ -17,17 +17,15 @@ class <?= $class_name ?> implements UserProviderInterface<?= $password_upgrader
1717
* If you're not using these features, you do not need to implement
1818
* this method.
1919
*
20-
* @return UserInterface
21-
*
22-
* @throws UsernameNotFoundException if the user is not found
20+
* @throws <?= $uses_user_identifier ? 'UserNotFoundException' : 'UsernameNotFoundException' ?> if the user is not found
2321
*/
24-
public function loadUserByUsername($username)
22+
public function <?= $uses_user_identifier ? 'loadUserByIdentifier($identifier)' : 'loadUserByUsername($username)' ?>: UserInterface
2523
{
26-
// Load a User object from your data source or throw UsernameNotFoundException.
27-
// The $username argument may not actually be a username:
28-
// it is whatever value is being returned by the getUsername()
24+
// Load a User object from your data source or throw <?= $uses_user_identifier ? 'UserNotFoundException' : 'UsernameNotFoundException' ?>.
25+
// The <?= $uses_user_identifier ? '$identifier' : '$username' ?> argument may not actually be a username:
26+
// it is whatever value is being returned by the <?= $uses_user_identifier ? 'getUserIdentifier' : 'getUsername' ?>()
2927
// method in your User class.
30-
throw new \Exception('TODO: fill in loadUserByUsername() inside '.__FILE__);
28+
throw new \Exception('TODO: fill in <?= $uses_user_identifier ? 'loadUserByIdentifier()' : 'loadUserByUsername()' ?> inside '.__FILE__);
3129
}
3230

3331
/**

src/Security/UserClassBuilder.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PhpParser\Node;
1515
use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator;
1616
use Symfony\Component\HttpKernel\Kernel;
17+
use Symfony\Component\Security\Core\User\InMemoryUser;
1718
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
1819
use Symfony\Component\Security\Core\User\UserInterface;
1920

@@ -67,7 +68,7 @@ private function addPasswordImplementation(ClassSourceManipulator $manipulator,
6768
$this->addGetPassword($manipulator, $userClassConfig);
6869
}
6970

70-
private function addGetUsername(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig)
71+
private function addGetUsername(ClassSourceManipulator $manipulator, UserClassConfiguration $userClassConfig): void
7172
{
7273
if ($userClassConfig->isEntity()) {
7374
// add entity property
@@ -97,10 +98,17 @@ private function addGetUsername(ClassSourceManipulator $manipulator, UserClassCo
9798
);
9899
}
99100

101+
$getterIdentifierName = 'getUsername';
102+
103+
// Check if we're using Symfony 5.3+ - UserInterface::getUsername() was replaced with UserInterface::getUserIdentifier()
104+
if (class_exists(InMemoryUser::class)) {
105+
$getterIdentifierName = 'getUserIdentifier';
106+
}
107+
100108
// define getUsername (if it was defined above, this will override)
101109
$manipulator->addAccessorMethod(
102110
$userClassConfig->getIdentityPropertyName(),
103-
'getUsername',
111+
$getterIdentifierName,
104112
'string',
105113
false,
106114
[

tests/Security/UserClassBuilderTest.php

Lines changed: 135 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,75 +16,175 @@
1616
use Symfony\Bundle\MakerBundle\Security\UserClassConfiguration;
1717
use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator;
1818
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
19+
use Symfony\Component\Security\Core\User\User;
1920

2021
class UserClassBuilderTest extends TestCase
2122
{
2223
/**
2324
* @dataProvider getUserInterfaceTests
2425
*/
25-
public function testAddUserInterfaceImplementation(UserClassConfiguration $userClassConfig, string $expectedFilename)
26+
public function testAddUserInterfaceImplementation(UserClassConfiguration $userClassConfig, string $expectedFilename): void
2627
{
27-
$sourceFilename = __DIR__.'/fixtures/source/'.($userClassConfig->isEntity() ? 'UserEntity.php' : 'UserModel.php');
28+
if (!interface_exists(PasswordAuthenticatedUserInterface::class)) {
29+
self::markTestSkipped('Requires Symfony >= 5.3');
30+
}
2831

29-
$manipulator = new ClassSourceManipulator(
30-
file_get_contents($sourceFilename),
31-
true
32-
);
32+
$manipulator = $this->getClassSourceManipulator($userClassConfig);
3333

3434
$classBuilder = new UserClassBuilder();
3535
$classBuilder->addUserInterfaceImplementation($manipulator, $userClassConfig);
3636

37-
$expectedPath = __DIR__.'/fixtures/expected';
37+
$expectedPath = $this->getExpectedPath($expectedFilename);
3838

39-
// Can be removed when < Symfony 6 support is dropped.
40-
if (!interface_exists(PasswordAuthenticatedUserInterface::class)) {
41-
$expectedPath = sprintf('%s/legacy', $expectedPath);
42-
}
39+
self::assertStringEqualsFile($expectedPath, $manipulator->getSourceCode());
40+
}
4341

44-
$expectedPath = sprintf('%s/%s', $expectedPath, $expectedFilename);
42+
public function getUserInterfaceTests(): \Generator
43+
{
44+
yield 'entity_with_email_as_identifier' => [
45+
new UserClassConfiguration(true, 'email', true),
46+
'UserEntityWithEmailAsIdentifier.php',
47+
];
4548

46-
if (!file_exists($expectedPath)) {
47-
throw new \Exception(sprintf('Expected file missing: "%s"', $expectedPath));
49+
yield 'entity_with_password' => [
50+
new UserClassConfiguration(true, 'userIdentifier', true),
51+
'UserEntityWithPassword.php',
52+
];
53+
54+
yield 'entity_with_user_identifier_as_identifier' => [
55+
new UserClassConfiguration(true, 'user_identifier', true),
56+
'UserEntityWithUser_IdentifierAsIdentifier.php',
57+
];
58+
59+
yield 'entity_without_password' => [
60+
new UserClassConfiguration(true, 'userIdentifier', false),
61+
'UserEntityWithoutPassword.php',
62+
];
63+
64+
yield 'model_with_email_as_identifier' => [
65+
new UserClassConfiguration(false, 'email', true),
66+
'UserModelWithEmailAsIdentifier.php',
67+
];
68+
69+
yield 'model_with_password' => [
70+
new UserClassConfiguration(false, 'userIdentifier', true),
71+
'UserModelWithPassword.php',
72+
];
73+
74+
yield 'model_without_password' => [
75+
new UserClassConfiguration(false, 'userIdentifier', false),
76+
'UserModelWithoutPassword.php',
77+
];
78+
}
79+
80+
/**
81+
* Covers Symfony <= 5.2 UserInterface::getUsername().
82+
*
83+
* In Symfony 5.3, getUsername was replaced with getUserIdentifier()
84+
*
85+
* @dataProvider legacyUserInterfaceGetUsernameDataProvider
86+
*/
87+
public function testLegacyUserInterfaceGetUsername(UserClassConfiguration $userClassConfig, string $expectedFilename): void
88+
{
89+
if (method_exists(User::class, 'getUserIdentifier')) {
90+
self::markTestSkipped();
4891
}
4992

50-
$this->assertSame(file_get_contents($expectedPath), $manipulator->getSourceCode());
93+
$manipulator = $this->getClassSourceManipulator($userClassConfig);
94+
95+
$classBuilder = new UserClassBuilder();
96+
$classBuilder->addUserInterfaceImplementation($manipulator, $userClassConfig);
97+
98+
$expectedPath = $this->getExpectedPath($expectedFilename, 'legacy_get_username');
99+
100+
self::assertStringEqualsFile($expectedPath, $manipulator->getSourceCode());
51101
}
52102

53-
public function getUserInterfaceTests()
103+
public function legacyUserInterfaceGetUsernameDataProvider(): \Generator
54104
{
55-
yield 'entity_email_password' => [
56-
new UserClassConfiguration(true, 'email', true),
57-
'UserEntityEmailWithPassword.php',
105+
yield 'entity_with_get_username' => [
106+
new UserClassConfiguration(true, 'username', false),
107+
'UserEntityGetUsername.php',
58108
];
59109

60-
yield 'entity_username_password' => [
61-
new UserClassConfiguration(true, 'username', true),
62-
'UserEntityUsernameWithPassword.php',
110+
yield 'model_with_get_username' => [
111+
new UserClassConfiguration(false, 'username', false),
112+
'UserModelGetUsername.php',
63113
];
64114

65-
yield 'entity_user_name_password' => [
66-
new UserClassConfiguration(true, 'user_name', true),
67-
'UserEntityUser_nameWithPassword.php',
115+
yield 'model_with_email_as_username' => [
116+
new UserClassConfiguration(false, 'email', false),
117+
'UserModelWithEmailAsUsername.php',
68118
];
119+
}
69120

70-
yield 'entity_username_no_password' => [
71-
new UserClassConfiguration(true, 'username', false),
72-
'UserEntityUsernameNoPassword.php',
121+
/**
122+
* Covers Symfony <= 5.2 UserInterface::getPassword().
123+
*
124+
* In Symfony 5.3, getPassword was moved from UserInterface::class to the
125+
* new PasswordAuthenticatedUserInterface::class.
126+
*
127+
* @dataProvider legacyUserInterfaceGetPasswordDataProvider
128+
*/
129+
public function testLegacyUserInterfaceGetPassword(UserClassConfiguration $userClassConfig, string $expectedFilename): void
130+
{
131+
if (interface_exists(PasswordAuthenticatedUserInterface::class)) {
132+
self::markTestSkipped();
133+
}
134+
135+
$manipulator = $this->getClassSourceManipulator($userClassConfig);
136+
137+
$classBuilder = new UserClassBuilder();
138+
$classBuilder->addUserInterfaceImplementation($manipulator, $userClassConfig);
139+
140+
$expectedPath = $this->getExpectedPath($expectedFilename, 'legacy_get_password');
141+
142+
self::assertStringEqualsFile($expectedPath, $manipulator->getSourceCode());
143+
}
144+
145+
public function legacyUserInterfaceGetPasswordDataProvider(): \Generator
146+
{
147+
yield 'entity_with_password' => [
148+
new UserClassConfiguration(true, 'username', true),
149+
'UserEntityWithPassword.php',
73150
];
74151

75-
yield 'model_email_password' => [
76-
new UserClassConfiguration(false, 'email', true),
77-
'UserModelEmailWithPassword.php',
152+
yield 'entity_without_password' => [
153+
new UserClassConfiguration(true, 'username', false),
154+
'UserEntityNoPassword.php',
78155
];
79156

80-
yield 'model_username_password' => [
157+
yield 'model_with_password' => [
81158
new UserClassConfiguration(false, 'username', true),
82-
'UserModelUsernameWithPassword.php',
159+
'UserModelWithPassword.php',
83160
];
84161

85-
yield 'model_username_no_password' => [
162+
yield 'model_without_password' => [
86163
new UserClassConfiguration(false, 'username', false),
87-
'UserModelUsernameNoPassword.php',
164+
'UserModelNoPassword.php',
88165
];
89166
}
167+
168+
private function getClassSourceManipulator(UserClassConfiguration $userClassConfiguration): ClassSourceManipulator
169+
{
170+
$sourceFilename = __DIR__.'/fixtures/source/'.($userClassConfiguration->isEntity() ? 'UserEntity.php' : 'UserModel.php');
171+
172+
return new ClassSourceManipulator(
173+
file_get_contents($sourceFilename),
174+
true
175+
);
176+
}
177+
178+
private function getExpectedPath(string $expectedFilename, string $subDirectory = null): string
179+
{
180+
$basePath = __DIR__.'/fixtures/expected';
181+
182+
$expectedPath = null === $subDirectory ? sprintf('%s/%s', $basePath, $expectedFilename) : sprintf('%s/%s/%s', $basePath, $subDirectory, $expectedFilename);
183+
184+
if (!file_exists($expectedPath)) {
185+
throw new \Exception(sprintf('Expected file missing: "%s"', $expectedPath));
186+
}
187+
188+
return $expectedPath;
189+
}
90190
}

tests/Security/fixtures/expected/UserEntityEmailWithPassword.php renamed to tests/Security/fixtures/expected/UserEntityWithEmailAsIdentifier.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public function setEmail(string $email): self
5656
*
5757
* @see UserInterface
5858
*/
59-
public function getUsername(): string
59+
public function getUserIdentifier(): string
6060
{
6161
return (string) $this->email;
6262
}

tests/Security/fixtures/expected/UserEntityUser_nameWithPassword.php renamed to tests/Security/fixtures/expected/UserEntityWithPassword.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
2121
/**
2222
* @ORM\Column(type="string", length=180, unique=true)
2323
*/
24-
private $user_name;
24+
private $userIdentifier;
2525

2626
/**
2727
* @ORM\Column(type="json")
@@ -44,14 +44,14 @@ public function getId(): ?int
4444
*
4545
* @see UserInterface
4646
*/
47-
public function getUsername(): string
47+
public function getUserIdentifier(): string
4848
{
49-
return (string) $this->user_name;
49+
return (string) $this->userIdentifier;
5050
}
5151

52-
public function setUserName(string $user_name): self
52+
public function setUserIdentifier(string $userIdentifier): self
5353
{
54-
$this->user_name = $user_name;
54+
$this->userIdentifier = $userIdentifier;
5555

5656
return $this;
5757
}

tests/Security/fixtures/expected/UserEntityUsernameWithPassword.php renamed to tests/Security/fixtures/expected/UserEntityWithUser_IdentifierAsIdentifier.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
2121
/**
2222
* @ORM\Column(type="string", length=180, unique=true)
2323
*/
24-
private $username;
24+
private $user_identifier;
2525

2626
/**
2727
* @ORM\Column(type="json")
@@ -44,14 +44,14 @@ public function getId(): ?int
4444
*
4545
* @see UserInterface
4646
*/
47-
public function getUsername(): string
47+
public function getUserIdentifier(): string
4848
{
49-
return (string) $this->username;
49+
return (string) $this->user_identifier;
5050
}
5151

52-
public function setUsername(string $username): self
52+
public function setUserIdentifier(string $user_identifier): self
5353
{
54-
$this->username = $username;
54+
$this->user_identifier = $user_identifier;
5555

5656
return $this;
5757
}

0 commit comments

Comments
 (0)