Skip to content

Conversation

@absumo
Copy link
Contributor

@absumo absumo commented Jan 21, 2026

Pass entity id in message instead of object.
Prevent errors when serializing the message.
See https://symfony.com/doc/current/messenger.html#doctrine-entities-in-messages

Summary by Sourcery

Replace Doctrine user entity instances in messenger messages with scalar user IDs and adjust handlers, controllers, validation, and tests accordingly.

Bug Fixes:

  • Avoid serialization issues by no longer embedding Doctrine entities directly in Symfony Messenger messages.

Enhancements:

  • Introduce a userId field on user-related messages and DTOs and resolve the corresponding User entity in handlers via the repository.

Tests:

  • Update user message handler tests to construct messages with user IDs instead of entity instances.

@absumo absumo requested a review from tijsverkoyen January 21, 2026 17:00
@sourcery-ai
Copy link

sourcery-ai bot commented Jan 21, 2026

Reviewer's Guide

Refactors user-related Messenger messages to carry user IDs instead of User entities, and updates all handlers, controllers, validation, and tests to load entities from the repository using those IDs for safer serialization and transport.

Sequence diagram for user message dispatch and handling with userId

sequenceDiagram
    actor UserActor
    participant Controller
    participant MessageBus
    participant SendConfirmationMessage as SendConfirmation
    participant SendConfirmationHandler
    participant UserRepository
    participant User

    UserActor->>Controller: trigger confirmation resend
    Controller->>MessageBus: dispatch SendConfirmation(userId, locale)
    MessageBus->>SendConfirmationHandler: __invoke(SendConfirmation)
    SendConfirmationHandler->>UserRepository: find(message.userId)
    UserRepository-->>SendConfirmationHandler: User
    SendConfirmationHandler->>User: requestConfirmation()
    SendConfirmationHandler->>UserRepository: save()
    UserRepository-->>SendConfirmationHandler: void
    SendConfirmationHandler-->>MessageBus: return User
Loading

Class diagram for updated user messages and handlers using userId

classDiagram
    class User {
        +int id
        +string email
        +array roles
        +void confirm()
        +void disable()
        +void enable()
        +void update(string email, array roles)
        +void requestPassword()
        +void requestConfirmation()
        +void setPassword(string password)
        +int getId()
        +string getEmail()
        +array getRoles()
    }

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

    class UpdateUser {
        +int userId
        +string email
        +array roles
        +__construct(User user)
    }

    class ConfirmUser {
        +int userId
        +__construct(int userId)
    }

    class DisableUser {
        +int userId
        +__construct(int userId)
    }

    class EnableUser {
        +int userId
        +__construct(int userId)
    }

    class SendConfirmation {
        +int userId
        +string locale
        +__construct(int userId, string locale)
    }

    class ChangePassword {
        +int userId
        +string password
        +__construct(int userId)
    }

    class ResetPassword {
        +int userId
        +string password
        +__construct(int userId)
    }

    class ConfirmUserHandler {
        +__construct(UserRepository userRepository)
        +__invoke(ConfirmUser message) void
    }

    class DisableUserHandler {
        +__construct(UserRepository userRepository)
        +__invoke(DisableUser message) void
    }

    class EnableUserHandler {
        +__construct(UserRepository userRepository)
        +__invoke(EnableUser message) void
    }

    class UpdateUserHandler {
        +__construct(UserRepository userRepository)
        +__invoke(UpdateUser message) void
    }

    class ChangePasswordHandler {
        +__construct(UserRepository userRepository, PasswordEncoder passwordEncoder)
        +__invoke(ChangePassword message) void
    }

    class ResetPasswordHandler {
        +__construct(UserRepository userRepository, PasswordEncoder passwordEncoder)
        +__invoke(ResetPassword message) void
    }

    class SendConfirmationHandler {
        +__construct(UserRepository userRepository)
        +__invoke(SendConfirmation message) User
    }

    class UniqueEmailValidator {
        +void validate(mixed value, Constraint constraint)
    }

    class UserDataTransferObject {
        +string email
        +array roles
    }

    class PasswordEncoder {
        +string hashPassword(User user, string password)
    }

    UserDataTransferObject <|-- UpdateUser

    ConfirmUserHandler --> ConfirmUser
    ConfirmUserHandler --> UserRepository
    ConfirmUserHandler --> User

    DisableUserHandler --> DisableUser
    DisableUserHandler --> UserRepository
    DisableUserHandler --> User

    EnableUserHandler --> EnableUser
    EnableUserHandler --> UserRepository
    EnableUserHandler --> User

    UpdateUserHandler --> UpdateUser
    UpdateUserHandler --> UserRepository
    UpdateUserHandler --> User

    ChangePasswordHandler --> ChangePassword
    ChangePasswordHandler --> UserRepository
    ChangePasswordHandler --> User
    ChangePasswordHandler --> PasswordEncoder

    ResetPasswordHandler --> ResetPassword
    ResetPasswordHandler --> UserRepository
    ResetPasswordHandler --> User
    ResetPasswordHandler --> PasswordEncoder

    SendConfirmationHandler --> SendConfirmation
    SendConfirmationHandler --> UserRepository
    SendConfirmationHandler --> User

    UniqueEmailValidator --> UpdateUser
    UniqueEmailValidator --> UserRepository
    UniqueEmailValidator --> User
Loading

File-Level Changes

Change Details Files
Refactor user messages to contain user IDs instead of User entities to comply with Messenger serialization best practices.
  • Replace User-typed public readonly properties in user message classes with int userId properties
  • Remove direct App\Entity\User\User imports from message classes where no longer needed
  • Augment UpdateUser DTO to store userId alongside copied scalar user fields
src/Message/User/ConfirmUser.php
src/Message/User/DisableUser.php
src/Message/User/EnableUser.php
src/Message/User/SendConfirmation.php
src/Message/User/ChangePassword.php
src/Message/User/ResetPassword.php
src/Message/User/UpdateUser.php
Update message handlers to load User entities from the repository using the userId from messages before performing operations.
  • In password-related handlers, fetch the User by ID before hashing and setting the password
  • In status/confirmation handlers, fetch the User by ID before calling domain methods like confirm/enable/disable
  • In UpdateUserHandler, fetch the User by ID before calling update with DTO-provided data
src/MessageHandler/User/ChangePasswordHandler.php
src/MessageHandler/User/ResetPasswordHandler.php
src/MessageHandler/User/ConfirmUserHandler.php
src/MessageHandler/User/DisableUserHandler.php
src/MessageHandler/User/EnableUserHandler.php
src/MessageHandler/User/UpdateUserHandler.php
src/MessageHandler/User/SendConfirmationHandler.php
Adjust controllers to construct and dispatch messages using user IDs instead of User entities.
  • Update form model instantiation to pass userId into ChangePassword, ConfirmUser, and ResetPassword messages
  • Update dispatch calls to SendConfirmation, EnableUser, DisableUser, and ConfirmUser to use userId
  • Adapt CreateUserHandler and RegisterUserHandler to dispatch SendConfirmation with the created user’s ID
src/Controller/User/ConfirmController.php
src/Controller/User/Admin/DisableUserController.php
src/Controller/User/Admin/EnableUserController.php
src/Controller/User/Admin/RequestConfirmationController.php
src/Controller/User/ProfileController.php
src/Controller/User/ResendConfirmationController.php
src/Controller/User/ResetPasswordController.php
src/MessageHandler/User/CreateUserHandler.php
src/MessageHandler/User/RegisterUserHandler.php
Update validation logic and tests to work with ID-based messages rather than entity-based messages.
  • Modify UniqueEmailValidator to compare UpdateUser::userId to the found user’s ID instead of comparing User entities
  • Adjust unit tests for user message handlers to construct messages with userId instead of User objects
src/Validator/User/UniqueEmailValidator.php
tests/MessageHandler/User/ChangePasswordHandlerTest.php
tests/MessageHandler/User/ConfirmUserHandlerTest.php
tests/MessageHandler/User/DisableUserHandlerTest.php
tests/MessageHandler/User/EnableUserHandlerTest.php
tests/MessageHandler/User/ResetPasswordHandlerTest.php
tests/MessageHandler/User/SendConfirmationHandlerTest.php

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 1 issue, and left some high level feedback:

  • Now that handlers load the User via $this->userRepository->find($message->userId), there’s no guarantee a user will be found; consider adding explicit handling (exception, early return, or validation) for a null result to avoid hidden failures.
  • In SendConfirmationHandler::__invoke, $this->userRepository->find($message->userId) can return null while the method still declares a User return type; it would be good to either enforce non-null lookup (e.g. findOrFail-style) or adjust the return type/behavior to reflect the possibility of a missing user.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Now that handlers load the User via `$this->userRepository->find($message->userId)`, there’s no guarantee a user will be found; consider adding explicit handling (exception, early return, or validation) for a `null` result to avoid hidden failures.
- In `SendConfirmationHandler::__invoke`, `$this->userRepository->find($message->userId)` can return `null` while the method still declares a `User` return type; it would be good to either enforce non-null lookup (e.g. `findOrFail`-style) or adjust the return type/behavior to reflect the possibility of a missing user.

## Individual Comments

### Comment 1
<location> `src/MessageHandler/User/ChangePasswordHandler.php:21-23` </location>
<code_context>
     {
-        $encodedPassword = $this->passwordEncoder->hashPassword($message->user, $message->password);
-        $message->user->setPassword($encodedPassword);
+        $user = $this->userRepository->find($message->userId);
+        $encodedPassword = $this->passwordEncoder->hashPassword($user, $message->password);
+        $user->setPassword($encodedPassword);
         $this->userRepository->save();
     }
</code_context>

<issue_to_address>
**issue:** Handle the case where the user cannot be found by the provided userId.

`find()` may return `null`, so if the user was deleted or the ID is invalid, both `hashPassword()` and `setPassword()` will fail on a `null` value. Add a guard for a missing user (e.g. throw a domain-specific exception, early return, or use a `getOrFail`-style method), and apply the same handling in the other handlers that call `find($message->userId)`.
</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.

Copy link
Member

@jonasdekeukelaere jonasdekeukelaere left a comment

Choose a reason for hiding this comment

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

  • Meeste van die messages kunnen readonly op class-niveau krijgen
  • Zou je in de handlers niet nog een check steken als user niet gevonden wordt?

Should not happen, so we want to know if it does.
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.

4 participants