Install User Bundle
This guide covers the practical minimum to get the bundle running in a real application.
At the end of this guide you will have:
- a concrete
App\Entity\User - the bundle enabled
- routes imported
- Symfony Security configured
- a first admin user created from CLI
Install The Package
composer require softspring/user-bundle:6.0.x-dev
If your application does not use Symfony Flex, enable the bundle manually:
<?php
return [
// ...
Softspring\UserBundle\SfsUserBundle::class => ['all' => true],
];
What The Flex Recipe Adds
With Symfony Flex, the recipe gives you a practical starting point:
src/Entity/User.phpconfig/routes/sfs_user.yamlconfig/routes/admin/sfs_user.yamlconfig/packages/sfs_user.yamlconfig/packages/security.yaml.dist
Some features stay disabled or commented on purpose:
- public register
- invitations
- access history
That default is intentional. Invitations and access history need extra concrete entities and should not be enabled accidentally in a fresh install.
Create Your User Entity
The bundle is designed to work with your own concrete User class.
The usual pattern is:
- extend
Softspring\UserBundle\Model\User - implement only the interfaces you need
- compose the matching traits
A practical baseline for many projects is:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Softspring\UserBundle\Entity\ConfirmableTrait;
use Softspring\UserBundle\Entity\EnabledTrait;
use Softspring\UserBundle\Entity\NameSurnameTrait;
use Softspring\UserBundle\Entity\PasswordRequestTrait;
use Softspring\UserBundle\Entity\RolesAdminTrait;
use Softspring\UserBundle\Entity\UserHasLocalePreferenceTrait;
use Softspring\UserBundle\Entity\UserIdentifierEmailTrait;
use Softspring\UserBundle\Entity\UserLastLoginTrait;
use Softspring\UserBundle\Entity\UserPasswordTrait;
use Softspring\UserBundle\Model\ConfirmableInterface;
use Softspring\UserBundle\Model\EnablableInterface;
use Softspring\UserBundle\Model\NameSurnameInterface;
use Softspring\UserBundle\Model\PasswordRequestInterface;
use Softspring\UserBundle\Model\RolesAdminInterface;
use Softspring\UserBundle\Model\User as UserModel;
use Softspring\UserBundle\Model\UserHasLocalePreferenceInterface;
use Softspring\UserBundle\Model\UserIdentifierEmailInterface;
use Softspring\UserBundle\Model\UserLastLoginInterface;
use Softspring\UserBundle\Model\UserPasswordInterface;
#[ORM\Entity]
#[ORM\HasLifecycleCallbacks]
class User extends UserModel implements
EnablableInterface,
NameSurnameInterface,
UserPasswordInterface,
PasswordRequestInterface,
UserIdentifierEmailInterface,
ConfirmableInterface,
RolesAdminInterface,
UserLastLoginInterface,
UserHasLocalePreferenceInterface
{
use EnabledTrait;
use UserIdentifierEmailTrait;
use NameSurnameTrait;
use UserPasswordTrait;
use PasswordRequestTrait;
use ConfirmableTrait;
use RolesAdminTrait;
use UserLastLoginTrait;
use UserHasLocalePreferenceTrait;
#[ORM\Id]
#[ORM\Column(length: 36, options: ['fixed' => true])]
#[ORM\GeneratedValue(strategy: 'NONE')]
protected ?string $id = null;
#[ORM\PrePersist]
public function generateId(): void
{
$this->id ??= \Symfony\Component\Uid\Uuid::v7()->toRfc4122();
}
public function getId(): ?string
{
return $this->id;
}
public function getDisplayName(): string
{
return trim(($this->getName() ?? '').' '.($this->getSurname() ?? ''));
}
}
This baseline gives you:
- email-based login
- encoded passwords
- admin and super-admin flags
- email confirmation
- reset password
- last login storage
- locale preference
Configure The Bundle
Minimal config:
YAML
# config/packages/sfs_user.yaml
sfs_user:
class: App\Entity\User
PHP
<?php
// config/packages/sfs_user.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return static function (ContainerConfigurator $container): void {
$container->extension('sfs_user', ['class' => \App\Entity\User::class]);
};
Recommended starter config:
YAML
# config/packages/sfs_user.yaml
imports:
- { resource: '@SfsUserBundle/config/security/admin_role_hierarchy.yaml' }
sfs_user:
class: App\Entity\User
confirm_email:
enabled: true
reset_password:
token_ttl: 86400
login:
target_path_parameter: _target_path
invite:
enabled: false
history:
enabled: false
PHP
<?php
// config/packages/sfs_user.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return static function (ContainerConfigurator $container): void {
$container->import('@SfsUserBundle/config/security/admin_role_hierarchy.yaml');
$container->extension('sfs_user', [
'class' => \App\Entity\User::class,
'confirm_email' => ['enabled' => true],
'reset_password' => ['token_ttl' => 86400],
'login' => ['target_path_parameter' => '_target_path'],
'invite' => ['enabled' => false],
'history' => ['enabled' => false],
]);
};
Important options:
class: your concrete user entityentity_manager: Doctrine entity manager nameconfirm_email.enabled: enable or disable the confirmation email flowreset_password.token_ttl: password reset token lifetime in secondslogin.target_path_parameter: query or form parameter used for redirect-after-logininvite.enabled: enables invitation services and routeshistory.enabled: enables access history services and routes
Import Routes
Import the route groups in your main config/routes.yaml:
YAML
_sfs_user:
resource: 'routes/sfs_user.yaml'
prefix: '/app'
_sfs_admin_user:
resource: 'routes/admin/sfs_user.yaml'
prefix: '/admin/users'
PHP
<?php
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;
return static function (RoutingConfigurator $routes): void {
$routes->import('routes/sfs_user.php')->prefix('/app');
$routes->import('routes/admin/sfs_user.php')->prefix('/admin/users');
};
Then create the public routes file:
YAML
# config/routes/sfs_user.yaml
_sfs_login:
resource: '@SfsUserBundle/config/routing/login.yaml'
_sfs_reset_password:
resource: '@SfsUserBundle/config/routing/reset_password.yaml'
prefix: '/reset-password'
_sfs_change_password:
resource: '@SfsUserBundle/config/routing/change_password.yaml'
prefix: '/user/change-password'
_sfs_preferences:
resource: '@SfsUserBundle/config/routing/preferences.yaml'
prefix: '/user/preferences'
#_sfs_register:
# resource: '@SfsUserBundle/config/routing/register.yaml'
# prefix: '/register'
PHP
<?php
// config/routes/sfs_user.php
namespace Symfony\Component\Routing\Loader\Configurator;
return static function (RoutingConfigurator $routes): void {
$routes->import('@SfsUserBundle/config/routing/login.yaml');
$routes->import('@SfsUserBundle/config/routing/reset_password.yaml')->prefix('/reset-password');
$routes->import('@SfsUserBundle/config/routing/change_password.yaml')->prefix('/user/change-password');
$routes->import('@SfsUserBundle/config/routing/preferences.yaml')->prefix('/user/preferences');
};
Symfony applications often auto-import files from config/routes/. Check your kernel before also importing the same files through routes.yaml: import each route group once. The imports above assume explicit route loading.
Start with login, reset password, preferences, and change password. Add the optional route groups only when the project needs them:
register.yamlfor self-service signupinvitation.yamlfor invitation-only onboardingchange_email.yaml,change_username.yaml, andsettings_confirmation.yamlfor extra self-service pages
And the admin routes file:
YAML
# config/routes/admin/sfs_user.yaml
_sfs_user_admin:
resource: '@SfsUserBundle/config/routing/admin_users.yaml'
prefix: '/user'
_sfs_user_admins:
resource: '@SfsUserBundle/config/routing/admin_administrators.yaml'
prefix: '/admins'
#_sfs_user_invitations:
# resource: '@SfsUserBundle/config/routing/admin_invitations.yaml'
# prefix: '/invitations'
#_sfs_user_history:
# resource: '@SfsUserBundle/config/routing/admin_access_history.yaml'
# prefix: '/access-history'
PHP
<?php
// config/routes/admin/sfs_user.php
namespace Symfony\Component\Routing\Loader\Configurator;
return static function (RoutingConfigurator $routes): void {
$routes->import('@SfsUserBundle/config/routing/admin_users.yaml')->prefix('/user');
$routes->import('@SfsUserBundle/config/routing/admin_administrators.yaml')->prefix('/admins');
};
Keep invitations and access history commented until the related sfs_user feature flags and concrete entities exist.
The bundle is modular. Import only the route groups you actually want to expose.
Configure Symfony Security
The bundle expects a provider based on sfs_user.user_provider.
Use a base security config like this:
YAML
# config/packages/security.yaml
security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
sfs_user:
id: sfs_user.user_provider
access_decision_manager:
strategy: unanimous
allow_if_all_abstain: false
role_hierarchy:
ROLE_ADMIN:
- ROLE_SFS_USER_ADMIN_USERS_RW
- ROLE_SFS_USER_ADMIN_ADMINISTRATORS_RW
ROLE_SUPER_ADMIN:
- ROLE_ADMIN
- PERMISSION_SFS_USER_ADMIN_ADMINISTRATORS_PROMOTE_SUPER
- PERMISSION_SFS_USER_ADMIN_ADMINISTRATORS_DEMOTE_SUPER
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/(admin|app)/
lazy: true
provider: sfs_user
form_login:
provider: sfs_user
login_path: sfs_user_login
check_path: sfs_user_login_check
logout:
path: sfs_user_logout
access_control:
- { path: ^/app/login, roles: PUBLIC_ACCESS }
- { path: ^/app/reset-password, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
PHP
<?php
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return static function (ContainerConfigurator $container): void {
$container->extension('security', [
'password_hashers' => ['Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface' => 'auto'],
'providers' => ['sfs_user' => ['id' => 'sfs_user.user_provider']],
'access_decision_manager' => ['strategy' => 'unanimous', 'allow_if_all_abstain' => false],
'role_hierarchy' => [
'ROLE_ADMIN' => ['ROLE_SFS_USER_ADMIN_USERS_RW', 'ROLE_SFS_USER_ADMIN_ADMINISTRATORS_RW'],
'ROLE_SUPER_ADMIN' => [
'ROLE_ADMIN',
'PERMISSION_SFS_USER_ADMIN_ADMINISTRATORS_PROMOTE_SUPER',
'PERMISSION_SFS_USER_ADMIN_ADMINISTRATORS_DEMOTE_SUPER',
],
],
'firewalls' => [
'dev' => ['pattern' => '^/(_(profiler|wdt)|css|images|js)/', 'security' => false],
'main' => [
'pattern' => '^/(admin|app)/',
'lazy' => true,
'provider' => 'sfs_user',
'form_login' => [
'provider' => 'sfs_user',
'login_path' => 'sfs_user_login',
'check_path' => 'sfs_user_login_check',
],
'logout' => ['path' => 'sfs_user_logout'],
],
],
'access_control' => [
['path' => '^/app/login', 'roles' => 'PUBLIC_ACCESS'],
['path' => '^/app/reset-password', 'roles' => 'PUBLIC_ACCESS'],
['path' => '^/admin', 'roles' => 'ROLE_ADMIN'],
],
]);
};
If you enable registration or invitations later, open those public routes explicitly in access_control.
Run Doctrine Migrations
The UUID example requires symfony/uid; install it if your project does not already have it. Keep an existing application's identifier strategy when adding the bundle rather than changing primary keys in place.
Once your User entity exists and the bundle config is in place, create and review the migration before applying it:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate -n
Create The First Admin User
The bundle ships CLI commands for the first bootstrap:
php bin/console sfs:user:create admin admin@example.com "local-demo-change-this-password" --enabled --admin
php bin/console sfs:user:promote admin@example.com --super-admin
The example entity logs in by email. The create command still accepts a username argument, but only stores it when the entity implements the username identifier contract. Promote users by the identifier actually configured for the entity. Use a unique password for each environment.
Main commands:
sfs:user:createsfs:user:promotesfs:user:invite
Real Integration Pattern
In armonic-standalone, the bundle is usually integrated with:
- public app routes under
/app - admin routes under
/admin/users - imported admin role hierarchy
- a single
mainfirewall for/appand/admin
That is a good reference when you want a real project example instead of an isolated snippet.
Related Guides
- Login and security for the full firewall, target path, remember me, and switch user setup.
- Register and reset password if your application is public and users can create their own accounts.
- Admin area to wire the backoffice screens after the base install is done.
- Invitations and access history if you need invitation-only onboarding or login audit trails.