Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Profile Bundle Laravel Package

nucleos/profile-bundle

Symfony bundle that adds a ready-made profile area for NucleosUserBundle-based apps, letting authenticated users view and edit account details and change passwords through prebuilt controllers, forms, and routes.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and enable

composer require nucleos/profile-bundle

Requires Symfony 6.4/7.x, and as of 2.9.0 also Symfony 8 (see PR #576) — matching your app's Symfony major is the only real install constraint. If you don't use Symfony Flex, add the bundle manually:

// config/bundles.php
Nucleos\Bundle\ProfileBundle\NucleosProfileBundle::class => ['all' => true],

Prerequisite: a User entity

The bundle is persistence-agnostic and opinionated only about the Symfony\Component\Security\Core\User\UserInterface contract. You need your own Doctrine entity (or ODM document) implementing UserInterface and PasswordAuthenticatedUserInterface. Typical minimal shape:

class User implements UserInterface, PasswordAuthenticatedUserInterface
{
    #[ORM\Id, ORM\GeneratedValue, ORM\Column]
    private ?int $id = null;

    #[ORM\Column(unique: true)]
    private string $username;

    #[ORM\Column(unique: true)]
    private string $email;

    #[ORM\Column]
    private string $password;

    private ?string $plainPassword = null;

    // getters/setters, getRoles(), eraseCredentials()...
}

Wire routes

# config/routes/nucleos_profile.yaml
nucleos_profile:
    resource: "@NucleosProfileBundle/config/routes.php"
    prefix: /

Then bin/console debug:router should show nucleos_profile_registration_register, nucleos_profile_registration_confirm, nucleos_profile_profile_edit, and the change-password / reset-password routes.

Build your form types

The bundle ships abstract form types whose data_class you must supply, plus the extra fields (password, plainPassword) you need:

// src/Form/RegistrationFormType.php
class RegistrationFormType extends AbstractRegistrationFormType
{
    public function __construct() { parent::__construct(User::class); }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        parent::buildForm($builder, $options);
        $builder->add('plainPassword', RepeatedType::class, [
            'type' => PasswordType::class,
            'mapped' => false,
            'constraints' => [new NotBlank(), new Length(['min' => 8])],
        ]);
    }
}
// src/Form/ProfileFormType.php
class ProfileFormType extends AbstractProfileFormType
{
    public function __construct() { parent::__construct(User::class); }
}

Point the config at them

# config/packages/nucleos_profile.yaml
nucleos_profile:
    registration:
        form:
            type: App\Form\RegistrationFormType
            name: app_registration
        confirmation:
            enabled: true
    profile:
        form:
            type: App\Form\ProfileFormType
            name: app_profile
    firewall_name: main
    from_email:
        address: [email protected]
        sender_name: My App

First use case: hit /register, submit, confirm the account (if confirmation is on), then /profile/edit. From a developer's perspective, the value is that you get working, CSRF-protected, event-dispatched controllers for the three pieces of user lifecycle that everyone rewrites badly: registration, profile edit, and password change.


Implementation Patterns

Hashing passwords with a Doctrine listener

The bundle does not hash passwords for you — it just gives you a form field. The canonical pattern is a Doctrine prePersist/preUpdate listener:

final class UserPasswordListener
{
    public function __construct(private UserPasswordHasherInterface $hasher) {}

    public function prePersist(LifecycleEventArgs $args): void
    {
        $user = $args->getObject();
        if ($user instanceof User && $user->getPlainPassword()) {
            $user->setPassword($this->hasher->hashPassword($user, $user->getPlainPassword()));
            $user->eraseCredentials();
        }
    }

    public function preUpdate(LifecycleEventArgs $args): void { /* same, with recomputeChangeSet */ }
}

Register it as a doctrine.event_listener (['prePersist', 'preUpdate']). This keeps hashing out of controllers and stays out of the way of admin tools.

Reacting to registration with events

The bundle dispatches Nucleos\Bundle\ProfileBundle\Event\UserEvent at well-known constants: REGISTRATION_INITIALIZE, REGISTRATION_SUCCESS, REGISTRATION_COMPLETED, REGISTRATION_CONFIRM, REGISTRATION_CONFIRMED, PROFILE_EDIT_INITIALIZE/SUCCESS/COMPLETED, CHANGE_PASSWORD_*. Subscribe in getSubscribedEvents():

public static function getSubscribedEvents(): array
{
    return [
        ProfileEvents::REGISTRATION_COMPLETED => 'onRegistrationCompleted',
    ];
}

public function onRegistrationCompleted(UserEvent $event): void
{
    $this->mailer->sendWelcome($event->getUser());
}

This is the right integration seam — don't override the controller just to send an email.

Integrating with your own controllers

Each shipped controller is a normal service and can be extended or replaced. If you need behavior the events don't cover, subclass RegistrationController (its service ID is nucleos_profile.controller.registration), override one action, and re-point the route resource at your class. Prefer events; subclass only when the event payload is insufficient.

Template overrides

Override by copying the bundle template into templates/bundles/NucleosProfileBundle/Registration/register.html.twig — Symfony's bundle override path is picked up automatically, no config needed. Keep the same block names (content, form) so the controllers' render() calls keep working.

Wiring security

If symfony/security-bundle is installed, set firewall_name in config so that the controller can dispatch the "authenticate after registration" behavior. If you skip security (e.g. API-only use), remove nucleos_profile.firewall_name; the bundle degrades gracefully and won't attempt to log the user in.

Confirmation flow

Enable registration.confirmation.enabled: true and make sure an email is dispatched from REGISTRATION_CONFIRM (or REGISTRATION_COMPLETED, depending on when you want the link sent). The bundle holds the confirmation token and route; you supply the mailer. For SPAs, override the confirmation template with an HTML page that deep-links back into your frontend rather than relying on the default Twig view.


Gotchas and Tips

  • No persistence out of the box. You must flush (EntityManager::flush() or a UserManager you write yourself). The controllers dispatch events and render; they don't own the entity manager lifecycle. This is the #1 source of "nothing happens after submit" bugs.
  • plainPassword is not mapped. You must set 'mapped' => false on it in your form and copy it to setPassword() in a listener; otherwise validation will fail or the password is silently dropped.
  • Password length validation is your problem. The bundle doesn't ship constraints; if you forget Length(['min' => 8]) you'll accept a as a password.
  • Route names are stable but service IDs are not documented. Use bin/console debug:router and debug:container nucleos_profile.controller.registration to discover the actual service IDs before subclassing — they've shifted between major versions.
  • Config keys are nested one level deeper than expected. registration.form.type and registration.form.name are different from what you might guess from FOSUserBundle's flatter layout; keep the YAML indentation right or Symfony will silently ignore the setting.
  • firewall_name mismatch causes silent no-login. If your firewall is admin but config says main, registration succeeds but the session isn't populated. Check with security.firewalls in config/packages/security.yaml.
  • Template override path is bundle-name case-sensitive. templates/bundles/NucleosProfileBundle/... — an extra d or a lowercase profile will silently fall back to the vendor template.
  • from_email is required for the email listeners to work. If you never configured it and never enabled confirmation, that's fine; if you enable confirmation, the bundle will throw on send because address is null.
  • Small community, so read the source. 12 stars means few Stack Overflow answers and zero third-party tutorials. When you hit an edge case, vendor/nucleos/profile-bundle/src/Controller/ and src/Event/ are small enough to read in full.
  • Extension points, ranked by cost: (1) event listeners, (2) template overrides, (3) form type extension via AbstractTypeExtension, (4) subclassing controllers. Reaching level (4) is almost always a sign your use case belongs in your own controller, not in this bundle.
  • Upgrade hygiene: the project is actively maintained — 2.9.0 (2026-04) added Symfony 8 support and moved the test suite to PHPUnit 13. That release is additive only: no config renames, no API changes, so upgrading from 2.8 should be a composer update and nothing more. Still pin the major explicitly (^2.9) in composer.json, since this bundle has done breaking config renames between majors before. If you're on Symfony 8, require ^2.9 — earlier 2.x tags cap out on Symfony 7.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings