Open
Conversation
added 7 commits
March 4, 2026 07:40
Reviewer's GuideRefactors several user-related controllers and message handlers for consistency and immutability while centralizing admin user actions (enable/disable/resend confirmation/password reset) into the edit-user page using modal-backed Symfony forms instead of separate controllers and links. Sequence diagram for admin disabling a user via edit page modalsequenceDiagram
actor Admin
participant Browser
participant EditUserController
participant FormFactoryInterface as FormFactory
participant MessageBusInterface as MessageBus
participant DisableUserHandler
participant UserRepository
participant Database
Admin->>Browser: Click Disable button (open modal)
Admin->>Browser: Confirm disable in modal form
Browser->>EditUserController: POST /admin/users/{user}/edit (disable_user form)
EditUserController->>FormFactory: createNamed(disable_user)
FormFactory-->>EditUserController: disableUserForm
EditUserController->>EditUserController: handleRequest(request) on disableUserForm
EditUserController->>EditUserController: disableUserForm isSubmitted() && isValid()
EditUserController->>MessageBus: dispatch(DisableUser(userId))
MessageBus-->>DisableUserHandler: Handle DisableUser
DisableUserHandler->>UserRepository: find(userId)
UserRepository->>Database: Load user
Database-->>UserRepository: User entity
DisableUserHandler->>UserRepository: save(disabled user)
UserRepository->>Database: Persist changes
DisableUserHandler-->>MessageBus: return
EditUserController->>EditUserController: addFlash(success, User successfully disabled.)
EditUserController-->>Browser: Redirect to /admin/users/{user}/edit
Browser-->>Admin: Updated edit user page with flash message
Class diagram for updated EditUserController and related messagesclassDiagram
class EditUserController {
- FormFactoryInterface formFactory
- TranslatorInterface translator
- MessageBusInterface messageBus
+ __construct(FormFactoryInterface formFactory, TranslatorInterface translator, MessageBusInterface messageBus)
+ __invoke(Request request, User currentUser, User user) Response
}
class User {
+ getId() int
+ getEmail() string
+ isEnabled() bool
+ isConfirmed() bool
}
class DisableUser {
+ __construct(int userId)
+ getUserId() int
}
class EnableUser {
+ __construct(int userId)
+ getUserId() int
}
class SendConfirmation {
+ __construct(int userId, string locale)
+ getUserId() int
+ getLocale() string
}
class SendPasswordReset {
+ email string
}
class DisableUserHandler {
- UserRepository userRepository
+ __construct(UserRepository userRepository)
+ __invoke(DisableUser message) void
}
class EnableUserHandler {
- UserRepository userRepository
+ __construct(UserRepository userRepository)
+ __invoke(EnableUser message) void
}
class SendConfirmationHandler {
- MailerInterface mailer
- TranslatorInterface translator
- RouterInterface router
- UserRepository userRepository
- Address from
+ __construct(MailerInterface mailer, TranslatorInterface translator, RouterInterface router, UserRepository userRepository, string from)
+ __invoke(SendConfirmation message) void
}
class SendPasswordResetHandler {
- MailerInterface mailer
- TranslatorInterface translator
- RouterInterface router
- UserRepository userRepository
- Address from
+ __construct(MailerInterface mailer, TranslatorInterface translator, RouterInterface router, UserRepository userRepository, string from)
+ __invoke(SendPasswordReset message) void
}
EditUserController --> User : edits
EditUserController --> DisableUser : dispatches
EditUserController --> EnableUser : dispatches
EditUserController --> SendConfirmation : dispatches
EditUserController --> SendPasswordReset : dispatches
DisableUserHandler ..> DisableUser : handles
EnableUserHandler ..> EnableUser : handles
SendConfirmationHandler ..> SendConfirmation : handles
SendPasswordResetHandler ..> SendPasswordReset : handles
DisableUserHandler --> UserRepository
EnableUserHandler --> UserRepository
SendConfirmationHandler --> UserRepository
SendPasswordResetHandler --> UserRepository
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- Changing the breadcrumb labels from
'add'/'edit'to'Add'/'Edit'while also removing the lowercaseadd/edittranslation keys may break existing translations; either restore the old keys or add matchingAdd/Editentries to the translation files. - In
EditUserController::__invoke, the#[CurrentUser] User $currentUserargument is non-nullable; if there is any chance this admin route can be hit without an authenticated user, consider making it?Userand handling the null case explicitly to avoid 500s.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Changing the breadcrumb labels from `'add'/'edit'` to `'Add'/'Edit'` while also removing the lowercase `add`/`edit` translation keys may break existing translations; either restore the old keys or add matching `Add`/`Edit` entries to the translation files.
- In `EditUserController::__invoke`, the `#[CurrentUser] User $currentUser` argument is non-nullable; if there is any chance this admin route can be hit without an authenticated user, consider making it `?User` and handling the null case explicitly to avoid 500s.
## Individual Comments
### Comment 1
<location path="templates/user/admin/edit.html.twig" line_range="53-58" />
<code_context>
+
+ {% if disableUserForm is defined and disableUserForm %}
+ {{ form_start(disableUserForm) }}
+ <div class="modal fade" id="disableUserModal" tabindex="-1" aria-labelledby="disableUserModalLabel"
+ aria-hidden="true">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h1 class="modal-title fs-5" id="disableUserModal">{{ 'Disable'|trans }}</h1>
+ <button type="button" class="btn-close" data-bs-dismiss="modal"
+ aria-label="{{ 'dialogs.close'|trans }}"></button>
</code_context>
<issue_to_address>
**issue (bug_risk):** The `aria-labelledby` attribute references an ID that does not exist in the modal header.
Here, `aria-labelledby="disableUserModalLabel"` references a non-existent ID; the heading’s ID is `disableUserModal`. Please align these by either changing `aria-labelledby` to `disableUserModal` or renaming the `<h1>` ID to `disableUserModalLabel`, and apply the same fix to the enable and resend confirmation modals for consistency and valid ARIA.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
tijsverkoyen
approved these changes
Mar 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary by Sourcery
Refactor user management and authentication flows to use modal-based form submissions, simplify controllers, and tighten type safety and dependencies across the user module.
New Features:
Bug Fixes:
Enhancements: