Skip to content

feat: option to change email on profile page#63

Open
iliaskhat wants to merge 1 commit intosumocoders:mainfrom
iliaskhat:change-email-profile
Open

feat: option to change email on profile page#63
iliaskhat wants to merge 1 commit intosumocoders:mainfrom
iliaskhat:change-email-profile

Conversation

@iliaskhat
Copy link

@iliaskhat iliaskhat commented Mar 6, 2026

Summary by Sourcery

Add support for updating a user’s email address from the profile section using a form and message handler.

New Features:

  • Introduce a profile email change form and page that allow users to update their email address.
  • Add a ChangeEmail message and handler to process email update requests asynchronously via the message bus.

Enhancements:

  • Extend the user entity with a dedicated method for changing the email field.
  • Wire profile controller to use form handling, flash messaging, and translated labels for the email change flow.

Documentation:

  • Add translated Dutch strings for the email change success message and page title.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 6, 2026

Reviewer's Guide

Implements an email change workflow on the user profile page using a Symfony form and Messenger message, including UI template, handler, message object, entity method, and translations.

Sequence diagram for the new email change workflow

sequenceDiagram
    actor User
    participant Browser
    participant ProfileController
    participant ChangeEmailType
    participant MessageBusInterface
    participant ChangeEmailHandler
    participant UserRepository
    participant Database

    User->>Browser: Navigate to /user/profile
    Browser->>ProfileController: GET /user/profile
    ProfileController->>ChangeEmailType: createForm(ChangeEmailType, ChangeEmail)
    ChangeEmailType-->>ProfileController: Form instance
    ProfileController-->>Browser: Render email.html.twig with form

    User->>Browser: Fill email and submit
    Browser->>ProfileController: POST /user/profile (form data)
    ProfileController->>ChangeEmailType: handleRequest(Request)
    ChangeEmailType-->>ProfileController: isSubmitted() && isValid() = true
    ProfileController->>MessageBusInterface: dispatch(ChangeEmail message)
    MessageBusInterface-->>Browser: Redirect to user_profile

    MessageBusInterface-->>ChangeEmailHandler: Deliver ChangeEmail
    ChangeEmailHandler->>UserRepository: find(userId)
    UserRepository-->>ChangeEmailHandler: User entity or null
    alt User found
        ChangeEmailHandler->>User: changeEmail(email)
        User->>User: set email
        ChangeEmailHandler->>UserRepository: save()
        UserRepository->>Database: Persist changes
        Database-->>UserRepository: Success
    else User not found
        ChangeEmailHandler-->>MessageBusInterface: Return without action
    end
Loading

Updated class diagram for email change feature

classDiagram
    class ProfileController {
        - TranslatorInterface translator
        - MessageBusInterface messageBus
        + __construct(translator TranslatorInterface, messageBus MessageBusInterface)
        + __invoke(request Request, user User) Response
    }

    class ChangeEmailType {
        + buildForm(builder FormBuilderInterface, options array) void
        + configureOptions(resolver OptionsResolver) void
    }

    class ChangeEmail {
        + int userId
        + string email
        + __construct(userId int, email string)
    }

    class ChangeEmailHandler {
        - UserRepository userRepository
        + __construct(userRepository UserRepository)
        + __invoke(message ChangeEmail) void
    }

    class User {
        - string email
        + changeEmail(email string) void
    }

    class UserRepository {
        + find(id int) User
        + save() void
    }

    ProfileController --> ChangeEmailType : creates form
    ProfileController --> ChangeEmail : populates
    ProfileController --> MessageBusInterface : dispatches
    ChangeEmailHandler --> ChangeEmail : handles
    ChangeEmailHandler --> UserRepository : uses
    UserRepository --> User : manages
    User --> ChangeEmail : email value source
Loading

File-Level Changes

Change Details Files
Add email change form flow to the profile controller using a Messenger message and flash feedback.
  • Inject TranslatorInterface and MessageBusInterface into the profile controller.
  • Instantiate ChangeEmail message prefilled with the current user email and bind it to a new ChangeEmailType form.
  • Handle form submission, dispatch the ChangeEmail message on valid submit, add a translated success flash, and redirect back to the profile route.
  • Render a new email-specific profile template passing both user and form.
src/Controller/User/ProfileController.php
src/Form/User/ChangeEmailType.php
src/Message/User/ChangeEmail.php
templates/user/profile/email.html.twig
Persist email changes through a dedicated message handler and entity method.
  • Introduce ChangeEmail message with userId and email as payload.
  • Create ChangeEmailHandler that loads the user, updates the email, and saves via UserRepository, safely no-oping when the user is not found.
  • Add a changeEmail(string $email) method on the User entity to encapsulate email mutation.
src/Message/User/ChangeEmail.php
src/MessageHandler/User/ChangeEmailHandler.php
src/Entity/User/User.php
Update UI and translations for email change page.
  • Add Dutch translations for the email-changed success message and the email change page title.
  • Create a new email profile Twig template with profile header, navigation include, email form rendering, and header confirm button wired to the form submit.
translations/messages+intl-icu.nl.yaml
templates/user/profile/email.html.twig

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In ProfileController, the breadcrumb argument was changed from the translation key user_profile to the literal Email; consider keeping a translation key here for consistency with the rest of the localized breadcrumbs.
  • The new User::changeEmail() method directly assigns $this->email and may bypass any normalization or checks that setEmail() (or existing logic) performs; consider delegating to the existing setter or centralizing email handling to avoid divergence.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `ProfileController`, the breadcrumb argument was changed from the translation key `user_profile` to the literal `Email`; consider keeping a translation key here for consistency with the rest of the localized breadcrumbs.
- The new `User::changeEmail()` method directly assigns `$this->email` and may bypass any normalization or checks that `setEmail()` (or existing logic) performs; consider delegating to the existing setter or centralizing email handling to avoid divergence.

## Individual Comments

### Comment 1
<location path="src/Controller/User/ProfileController.php" line_range="48-50" />
<code_context>
+            return $this->redirectToRoute('user_profile');
+        }
+
+        return $this->render('user/profile/email.html.twig', [
             'user' => $user,
+            'form' => $form,
         ]);
     }
</code_context>
<issue_to_address>
**issue (bug_risk):** The form should be passed as a FormView (`$form->createView()`) rather than the Form object itself.

Twig form helpers require a `FormView`. Passing the `FormInterface` (`'form' => $form`) will cause a runtime error. Please change this to `'form' => $form->createView()` to align with Symfony’s expected usage.
</issue_to_address>

### Comment 2
<location path="src/Controller/User/ProfileController.php" line_range="26" />
<code_context>
+    ) {
+    }
+
+    #[Breadcrumb('Email')]
     public function __invoke(
+        Request $request,
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Using a literal 'Email' label for the breadcrumb might bypass translation and be inconsistent with other breadcrumbs.

Other breadcrumbs use translation keys (e.g. `user_profile`), while this uses the literal `'Email'`. Unless `Breadcrumb` resolves this as a key, it will remain untranslated in non-English locales. Please use a dedicated key (e.g. `user_profile_email`) and add it to the translation files for consistency.

Suggested implementation:

```
    #[Breadcrumb('user_profile_email')]

```

1. Add a `user_profile_email` entry to your translation files (e.g. `translations/messages.en.yaml`, `messages.fr.yaml`, etc.), such as:
   - `messages.en.yaml`: `user_profile_email: 'Email'`
   - `messages.fr.yaml`: `user_profile_email: 'E-mail'`
2. If your project uses domain-specific translations for breadcrumbs (e.g. `breadcrumbs.en.yaml`), add the key there instead, matching how other breadcrumb labels are defined.
</issue_to_address>

### Comment 3
<location path="src/MessageHandler/User/ChangeEmailHandler.php" line_range="21-29" />
<code_context>
+            return;
+        }
+
+        $user->changeEmail((string) $message->email);
+
+        $this->userRepository->save();
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Casting a nullable email to string risks unintentionally persisting an empty email.

Because `ChangeEmail::$email` is `?string`, `(string) $message->email` will convert `null` to `''`. That hides an invalid state and can lead to empty emails being stored. Prefer making `email` non-nullable in `ChangeEmail`, or explicitly handle `null` here (e.g., return early) instead of relying on the cast.

```suggestion
        $user = $this->userRepository->find($message->userId);

        if ($user === null) {
            return;
        }

        if ($message->email === null) {
            return;
        }

        $user->changeEmail($message->email);

        $this->userRepository->save();
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +48 to +50
return $this->render('user/profile/email.html.twig', [
'user' => $user,
'form' => $form,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The form should be passed as a FormView ($form->createView()) rather than the Form object itself.

Twig form helpers require a FormView. Passing the FormInterface ('form' => $form) will cause a runtime error. Please change this to 'form' => $form->createView() to align with Symfony’s expected usage.

) {
}

#[Breadcrumb('Email')]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Using a literal 'Email' label for the breadcrumb might bypass translation and be inconsistent with other breadcrumbs.

Other breadcrumbs use translation keys (e.g. user_profile), while this uses the literal 'Email'. Unless Breadcrumb resolves this as a key, it will remain untranslated in non-English locales. Please use a dedicated key (e.g. user_profile_email) and add it to the translation files for consistency.

Suggested implementation:

    #[Breadcrumb('user_profile_email')]

  1. Add a user_profile_email entry to your translation files (e.g. translations/messages.en.yaml, messages.fr.yaml, etc.), such as:
    • messages.en.yaml: user_profile_email: 'Email'
    • messages.fr.yaml: user_profile_email: 'E-mail'
  2. If your project uses domain-specific translations for breadcrumbs (e.g. breadcrumbs.en.yaml), add the key there instead, matching how other breadcrumb labels are defined.

Comment on lines +21 to +29
$user = $this->userRepository->find($message->userId);

if ($user === null) {
return;
}

$user->changeEmail((string) $message->email);

$this->userRepository->save();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Casting a nullable email to string risks unintentionally persisting an empty email.

Because ChangeEmail::$email is ?string, (string) $message->email will convert null to ''. That hides an invalid state and can lead to empty emails being stored. Prefer making email non-nullable in ChangeEmail, or explicitly handle null here (e.g., return early) instead of relying on the cast.

Suggested change
$user = $this->userRepository->find($message->userId);
if ($user === null) {
return;
}
$user->changeEmail((string) $message->email);
$this->userRepository->save();
$user = $this->userRepository->find($message->userId);
if ($user === null) {
return;
}
if ($message->email === null) {
return;
}
$user->changeEmail($message->email);
$this->userRepository->save();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant