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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy