runroom/user-bundle
A user management bundle for PHP applications that provides ready-made user entities, authentication, registration and login flows, password reset, profile editing, and admin integration, so you can add secure account handling without rebuilding the same foundation on every project.
This is a compatibility fork of sonata-project/user-bundle pinned back to PHP 5.6 / 7.0. Treat it as a drop-in replacement for the upstream Sonata bundle in a legacy app — the namespace is still Sonata\UserBundle, so everything you already know about SonataUserBundle applies. It is not a package to reach for on new projects.
1. Install it as a replacement, not an addition. Because the package name differs from the one your code and other bundles expect, you almost always need a VCS repository plus a replace in your root composer.json:
{
"repositories": [
{ "type": "vcs", "url": "https://github.com/Runroom/SonataUserBundle" }
],
"require": {
"runroom/user-bundle": "dev-master"
},
"replace": {
"sonata-project/user-bundle": "*"
}
}
The replace block is what stops Composer pulling upstream sonata-project/user-bundle alongside the fork (which would give you duplicate Sonata\UserBundle classes and a fatal error).
2. Register the bundles in app/AppKernel.php, after the Sonata admin bundles:
new FOS\UserBundle\FOSUserBundle(),
new Sonata\CoreBundle\SonataCoreBundle(),
new Sonata\BlockBundle\SonataBlockBundle(),
new Knp\Bundle\MenuBundle\KnpMenuBundle(),
new Sonata\AdminBundle\SonataAdminBundle(),
new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(),
new Sonata\UserBundle\SonataUserBundle('FOSUserBundle'),
3. Look here first inside vendor/runroom/user-bundle/:
| Path | Why |
|---|---|
Entity/BaseUser.php, Entity/BaseGroup.php |
The mapped superclasses you must extend |
Admin/Model/UserAdmin.php, Admin/Model/GroupAdmin.php |
The admin definitions you subclass |
DependencyInjection/Configuration.php |
The authoritative config tree for this version — keys drifted between Sonata releases |
Resources/config/admin.yml |
Service ids and the *.class parameters you override |
Resources/views/ |
Templates to override when the default user form doesn't fit |
4. First use case: get the user/group CRUD screens into Sonata Admin. Extend the entities, wire fos_user.user_class, define two admin services, then run:
app/console sonata:admin:setup-acl
app/console sonata:admin:generate-object-acl
You should now see "Users" and "Groups" in the admin dashboard.
This is the core workflow — BaseUser/BaseGroup are @MappedSuperclass, so you create real entities in your own bundle:
namespace Application\Sonata\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Sonata\UserBundle\Entity\BaseUser as BaseUser;
/**
* @ORM\Entity
* @ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
}
Do the same for Group (table fos_user_group) and remember to add @ORM\JoinTable overrides for the fos_user_user_group table if you keep the default schema. The fos_user / fos_user_group table names matter: reuse them and your existing data migrates untouched.
Rather than fighting the YAML, extend the model admin and override only what you need:
namespace Application\Sonata\UserBundle\Admin;
use Sonata\UserBundle\Admin\Model\UserAdmin as BaseUserAdmin;
class UserAdmin extends BaseUserAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
parent::configureFormFields($formMapper);
$formMapper->with('Extra')
->add('phone', null, ['required' => false])
->end();
}
}
Then point the parameter at it (parameters win over the bundle's defaults):
parameters:
sonata.user.admin.user.class: Application\Sonata\UserBundle\Admin\UserAdmin
sonata.user.admin.group.class: Application\Sonata\UserBundle\Admin\GroupAdmin
Two bundles have to agree on the same class. The classic pairing:
fos_user:
db_driver: orm
firewall_name: main
user_class: Application\Sonata\UserBundle\Entity\User
group:
group_class: Application\Sonata\UserBundle\Entity\Group
sonata_user:
class:
user: Application\Sonata\UserBundle\Entity\User
group: Application\Sonata\UserBundle\Entity\Group
If these two diverge you get "user class does not implement FOS\UserBundle\Model\UserInterface" or a silent login failure. Always change them in the same commit.
Sonata Admin needs its own firewall for the admin area with ROLE_ADMIN/ROLE_SONATA_ADMIN in the hierarchy:
security:
role_hierarchy:
ROLE_ADMIN: [ROLE_USER, ROLE_SONATA_ADMIN]
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
admin:
pattern: ^/admin
form_login:
login_path: sonata_user_admin_security_login
check_path: sonata_user_admin_security_check
logout:
path: sonata_user_admin_security_logout
anonymous: ~
main:
pattern: ^/
form_login: { provider: fos_userbundle, csrf_provider: form.csrf_provider }
logout: ~
anonymous: ~
The ROLE_SONATA_ADMIN entry in role_hierarchy is what makes admins able to see admin sections; ROLE_ADMIN alone only gets you into the security side. Grant per-object access afterwards with sonata:admin:generate-object-acl or the admin's security block in sonata_admin.yml.
A frequently used day-to-day feature, configured via the bundle:
sonata_user:
impersonating:
route: homepage
parameters: { path: / }
Add a custom admin action on UserAdmin that calls $this->getConfigurationPool()->getContainer()->get('session')->set('_sonata_user_impersonating', $id) — pattern is the same as upstream; the fork doesn't change it.
Extend BaseUser and reuse the FOS controllers for registration and profile editing, or point the routes at your own controllers if you need extra fields persisted. Keep SonataUserBundle out of the registration flow — it's an admin-layer bundle; FOSUserBundle owns the front-end user lifecycle.
Version ceiling is real. The package exists specifically for PHP 5.6/7.0. If your CI, Docker image, or shared host quietly moved to PHP 7.4/8.x, this bundle (and the Sonata/FOS generation it supports) will fall over on syntax or removed functions. Pin PHP in composer.json ("php": "~5.6|~7.0") so Composer surfaces the problem at install time rather than at runtime.
The replace trap. Skip the replace block and you get two packages providing Sonata\UserBundle\...; the autoloader picks whichever wins the class map, giving you wonderfully confusing "method does not exist" errors. Verify with composer show | grep user-bundle — you should see exactly one.
Config key drift. Upstream renamed sonata_user keys several times (class.user vs admin.user.class vs entity.user). Don't copy YAML from blog posts — read DependencyInjection/Configuration.php in this checkout. A wrong key is silently ignored (Symfony 2's config component doesn't always warn), so the symptom is "my override does nothing."
Mapped superclass pitfalls. BaseUser has no @ORM\Entity, so forgetting @ORM\Table on your subclass yields Doctrine's default User table and a migration that looks alarming. Also, BaseUser already declares $groups with a JoinTable; if you redeclare $groups, you must redeclare the association annotations too, or Doctrine complains about a mapped-by mismatch.
Password encoding. Old FOSUserBundle defaults to plaintext. Configure encoders explicitly:
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
Switching encoders after users exist silently locks everyone out — plan the migration.
Debugging admin access. When a page 403s: check role_hierarchy (needs ROLE_SONATA_ADMIN), then the admin service's security: block in sonata_admin.yml, then the object ACL. Run app/console sonata:admin:explain <service.id> — it prints the resolved security expression and roles, which is far faster than reading voter code.
Cache and asset quirks. After overriding templates or translations under Application/Sonata/UserBundle/Resources/, clear app/cache and run assets:install --symlink; the admin blocks (profile/account) live in Resources/views/Block and are not Symfony-overridable in the usual app-level location.
Extension points worth knowing:
sonata.user.admin.user.class / sonata.user.admin.group.class — subclass the admins.SonataUserBundle::class parameters for form types and controllers in Resources/config/admin.yml — swap the controller without touching the admin model.getNewInstance() on the admin if you need defaults populated (e.g. auto-assign a group on creation).prePersist for user defaults rather than a Sonata admin hook — it also covers fixtures and programmatic creation.Maintenance stance. Zero stars, zero dependents, and the stated purpose is unbreaking a legacy stack. Before adopting it, check whether you can instead pin the last upstream SonataUserBundle that supports your PHP (often cleaner and gets security patches), or drop the Sonata admin user screens entirely and manage users with a small custom admin + FOSUserBundle. Adopt the fork only when Composer resolution genuinely leaves you no other option, and isolate it behind your Application\Sonata\UserBundle so a future swap is a one-file change.
How can I help you explore Laravel packages today?