genealabs/laravel-registrar
GeneaLabs Laravel Registrar offers a fluent, convention-driven API for registering service container bindings, aliases, and providers in Laravel, reducing boilerplate in service providers and keeping application wiring organized, reusable, and easy to maintain across projects.
First, resolve what the package actually does — it is not a trivial step here. Three sources disagree: the repository metadata says "user account activation via email confirmation," the packaged description.md describes a fluent service-container registry (register() / resolve() for named implementations), and the only changelog entry (0.1.6) is a "double-save" data-integrity fix. Clone the repo and read the service provider before you write a line of application code. If your goal is container binding ergonomics, the native ServiceProvider API ($this->app->singleton(), bind(), contextual binding) already covers it and this dependency adds nothing but risk. If your goal is email-confirmed signup, continue below.
Minimal install path for a Laravel 5.x-era app:
# 1. Read the constraint block first — do not assume modern framework support
composer show genealabs/laravel-registrar --all
# 2. Pin exactly; there is no maintainer to ship a 0.1.7
composer require genealabs/laravel-registrar:0.1.6
# 3. Publish and migrate
php artisan vendor:publish --provider="GeneaLabs\LaravelRegistrar\LaravelRegistrarServiceProvider"
php artisan migrate --pretend # inspect before running for real
php artisan migrate
Then apply the package's trait (or extend its base user model) to App\User, point it at the activation column, and configure a mail driver.
Where to look first, in this order — with no real documentation, this five-file read is your entire mental model:
users and what shape it assumes.First use case: the happy path, driven by a feature test, not by clicking. Write it before anything else:
public function test_registration_sends_activation_and_activates_on_click()
{
Notification::fake();
$this->post('/register', [
'name' => 'A', 'email' => '[email protected]',
'password' => 'secret123', 'password_confirmation' => 'secret123',
]);
$user = User::where('email', '[email protected]')->firstOrFail();
$this->assertNull($user->activated_at);
Notification::assertSentTo($user, ActivationNotification::class);
$this->get($this->activationUrlFor($user));
$this->assertNotNull($user->fresh()->activated_at);
}
That test is your regression guard for every subsequent change, and it doubles as documentation of a flow the package itself does not document.
Wrap the package; never let it own your routes or controllers. Route your own endpoints and call the package's registrar/action inside them. You keep validation, flash messages, redirects, and audit logging — and you avoid coupling route names to a frozen dependency.
// routes/web.php — your routes, your middleware
Route::post('/register', [RegisterController::class, 'store']);
Route::get('/activate/{token}', [ActivationController::class, 'activate'])
->name('activate');
// app/Http/Controllers/ActivationController.php
public function activate(string $token)
{
$result = $this->registrar->activate($token); // package call, one place
return match ($result) {
'activated' => redirect()->route('login')->with('status', 'Account activated.'),
'already' => redirect()->route('login')->with('status', 'Already active.'),
default => redirect()->route('login')->withErrors('Invalid or expired link.'),
};
}
Gate authentication on the activation column. A confirmed email is worthless if unactivated users can still log in. Add a check in attemptLogin or a middleware:
protected function attemptLogin(Request $request)
{
$user = User::where('email', $request->email)->first();
if ($user && is_null($user->activated_at)) {
throw ValidationException::withMessages([
'email' => 'Please confirm your email before signing in.',
]);
}
return parent::attemptLogin($request);
}
Make activation idempotent. Users double-click; corporate mail scanners prefetch links. The second click must return a friendly "already active" redirect, not a 500 or an "invalid token" error.
Own your token handling if the package does not. Generate random bytes, store a hash, store an expiry, compare with hash_equals. If the package stores raw tokens, add a migration. Expiry should be 24–48h, and there must be a resend endpoint behind a rate limiter.
Replace the notification rather than fight it. If the package's mailable is too rigid, bind your own notification class through the published config. Keep activation-URL generation in exactly one place so a domain change doesn't silently break every outstanding link.
Emit a domain event from your controller, not from the package's internals:
// after successful activation
event(new UserActivated($user));
// app/Listeners/SendWelcomeEmail.php — queued, idempotent
public function handle(UserActivated $event): void
{
if ($event->user->welcome_sent_at) {
return;
}
// ...
}
Post-activation side effects (welcome email, seed default records, analytics) hang off that listener. It is testable, it is yours, and it survives the day you rip the package out.
Testing patterns that pay for themselves:
Notification::fake() plus assert-sent-to-the-right-user; Mail::fake() if it uses a mailable.->unactivated() and ->activated().MAIL_MAILER=array and QUEUE_CONNECTION=sync in phpunit.xml so nothing escapes and nothing stalls.Plan the exit while you are integrating. Put every package touchpoint behind one small class (ActivationService) with your own interface. When you replace it with MustVerifyEmail + signed URLs + the verified middleware, you change one file.
0.1.6 exactly and put a replacement ticket on the board the same day you install it.users table with a specific shape. If your model is App\Models\User rather than App\User, or your table is named differently, edit the migration and any hardcoded model references. Always migrate --pretend first.Auth::routes(), names or middleware will collide. Run php artisan route:list right after install and diff for duplicates.php artisan config:clear && php artisan view:clear. Do it as part of every deploy that touches published assets.sync.vendor/. An archived package will never accept your PR, so a fork is a permanent commitment — decide that deliberately, not accidentally.MAIL_* env and driver → queue worker running → config cache cleared → route registered and not shadowed → trait applied to the model → activation column present and fillable → token not already expired. That sequence resolves the large majority of reports.How can I help you explore Laravel packages today?