performer/rer-user-bundle
Performer RER User Bundle is a Symfony/PHP bundle for adding user account management to RER-based applications, with ready-made user entities, authentication, registration, role handling, and admin screens to speed up secure user setup.
Frame it correctly first. performer/rer-user-bundle is a Symfony bundle, not a Laravel package. It has no extra.laravel block, no service provider, no vendor:publish tags. composer require performer/rer-user-bundle will succeed and then do nothing — Laravel will never boot the bundle or register its services. For a Laravel developer, "leverage" here almost always means port the domain model and semantics, not install the code.
Minimal sequence:
git clone … /tmp/rer-user) or a repositories VCS entry pinned to a commit. Read it before it can pollute your composer.lock.composer.json — PHP constraint, symfony/* and doctrine/orm versions, whether it leans on FOSUserBundle.Entity/ or Model/ — the actual user fields and relations. This is the artifact you want.Resources/config/doctrine/*.orm.xml (or attributes/annotations) — column names, types, lengths, unique constraints, join tables.DependencyInjection/*Extension.php — role hierarchy, encoder config, registered services.LICENSE, last commit date, tags. With 3 stars / 0 dependents, assume unmaintained.Aim your spike at that single deliverable. If it takes more than a couple of days, the domain is not being reused — it is being re-implemented, and you should own that decision explicitly.
Pattern 1 — Schema-first port. Translate Doctrine mappings into migrations by hand (do not attempt to run Doctrine migrations against a Laravel database):
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('email')->unique(); // mirror length from the ORM mapping
$table->string('password');
$table->boolean('enabled')->default(true); // whatever the entity calls "active"
$table->timestamps();
$table->softDeletes(); // only if the entity has it
});
Schema::create('roles', fn (Blueprint $t) => [
$t->id(), $t->string('code')->unique(), $t->string('name'),
]);
Schema::create('role_user', function (Blueprint $table) {
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('role_id')->constrained()->cascadeOnDelete();
$table->primary(['user_id', 'role_id']);
});
Keep the vendor's column names where they are externally visible (reporting joins, ETL). Rename freely where they are not.
Pattern 2 — Eloquent model as the port. Wrap the schema behind a model with $hidden, casts, and a narrow role API. Never let calling code reach into a roles collection directly:
class User extends Authenticatable
{
use Notifiable;
protected $hidden = ['password', 'remember_token'];
protected $casts = ['enabled' => 'bool', 'email_verified_at' => 'datetime'];
public function roles() { return $this->belongsToMany(Role::class); }
public function hasRole(string $code): bool
{
return $this->roles->contains('code', $code);
}
}
Pattern 3 — Voters → Gates/Policies. Symfony voters operate on (attribute, subject); Laravel policies are model-bound. Port each voter as a policy method and move any multi-entity/scope logic into an explicit check:
class ReportPolicy
{
public function view(User $user, Report $report): bool
{
return $user->hasRole('admin')
|| $report->scope->users()->whereKey($user->id)->exists();
}
}
Reproduce Symfony's role hierarchy with a single Gate::before that expands inherited roles, rather than scattering hasRole chains across controllers.
Pattern 4 — Services → Actions. The bundle's "reusable user services" (register, change password, enable/disable) map cleanly onto single-purpose action classes invoked from FormRequests/controllers. Keep password hashing on Hash::make() and never port the Symfony encoder config verbatim.
Pattern 5 — Sync, if Performer is the system of record. In a Performer-based enterprise estate the user/role data often originates upstream. Model that as a queued, idempotent job that upserts users and reconciles roles — never as a live read from the vendor's tables inside a request:
class SyncUsersFromPerformer implements ShouldQueue
{
public function handle(PerformerClient $client): void
{
foreach ($client->users() as $row) {
$user = User::updateOrCreate(['email' => Str::lower($row['email'])], [...]);
$user->roles()->sync(Role::whereIn('code', $row['roles'])->pluck('id'));
}
}
}
Pattern 6 — Anti-corruption layer. Even if you never install the bundle, keep the port narrow: an interface like UserDirectory / AccessResolver in your app, with the Eloquent implementation behind it. That is what makes "replace with OIDC later" a swap rather than a rewrite, and it is the only durable benefit of this evaluation.
Pattern 7 — Sidecar (last resort). A heterogeneous PHP estate can run Symfony as a separate service and expose the bundle over HTTP. Enforce one owner of the users table and one migration tool per schema. Do not attempt to boot a Symfony kernel inside a Laravel app.
composer require is a trap. It resolves, installs, and does nothing. If you only want the domain model, use a VCS repository with a pinned commit or vendor the entity classes — do not leave an unused auth-path dependency in composer.json where a scanner will flag it.EntityManager, no second-level cache. $entity->setEmail() getters/setters become attributes; cascade and orphanRemoval become cascadeOnDelete and explicit deletes. Join tables with a composite primary key need $table->primary([...]).security.password_hashers config (algorithm, cost, migrate-on-login) has no automatic Laravel equivalent. If you are reading existing rows, verify the algorithm and cost against config/hashing.php; Symfony bcrypt cost 13 vs Laravel's default 12 will silently rehash on every login if the driver disagrees. Never accept plaintext or a homemade encoder from the bundle.role_hierarchy implies inheritance (ROLE_ADMIN ⊃ ROLE_EDITOR ⊃ ROLE_USER). Eloquent relations do not. Implement one Gate::before that computes the transitive closure and cache it per request; otherwise you will get authorization drift that only shows up in an audit.utf8mb4_unicode_ci will mask the bug in dev and a utf8mb4_bin column or Postgres will expose it in production as duplicate accounts.created_at/updated_at. Laravel assumes them. Either add the columns or set public $timestamps = false; — forgetting produces Unknown column 'created_at' on the first save().users/roles tables. Pick Laravel and document it.bin/console debug:container in Laravel. If you genuinely need to inspect the bundle's wiring, run it in a throwaway Symfony skeleton and dump the container there; otherwise read DependencyInjection/ directly.composer audit and an explicit commit hash. With zero dependents there is no downstream pressure to patch, so assume you own every future CVE in the transitive tree.assertTrue($user->can('view', $scopedReport)) — are your regression net when the underlying implementation changes from "vendor semantics" to "ours."How can I help you explore Laravel packages today?