For the past few years, I've managed to override the boot method in the Fortify service provider located at App\Providers\FortifyServiceProvider so that I can define the Fortify Views from my custom service provider that's loaded dynamically from a package.
This all worked perfectly until Laravel 11.9.0, where there was an update to how the service providers were loaded, I'm guessing that's where the issue may lie.
https://github.com/laravel/framework/blob/11.x/CHANGELOG.md
Do you have any ideas on how this might now be achieved without touching the main service provider in the App\Providers folder so that the custom logic remains inside my custom package?
Is it this change directly that's affecting this (just so I'm clear)?
https://github.com/laravel/framework/pull/51343
I am not 100% sure but it seems like it might be.
I used the AliasLoader to alias the App\Providers\FortifyServiceProvider, which when called used my own one within my own package which is loaded dynamically.
It's a little strange as I do the same with Jetstream which does seem to work so I'm unsure what has changed.
I set the login view (Fortify::loginView) and other views and features in the boot method.
Really strange! I’ll do some experimenting and get back to you if I find something.
Do you have a snippet of code for how you’re currently doing this inside your package?
Yes here is the service provider:
<?php
namespace Adonis\Cms\Providers;
use Adonis\Cms\Facades\Setting;
use Illuminate\Foundation\AliasLoader;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Fortify;
class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
AliasLoader::getInstance()->alias('App\Providers\FortifyServiceProvider', 'Adonis\Cms\Providers\FortifyServiceProvider');
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
if (Schema::hasTable('settings')) {
$this->overrideFortifyFeatures();
$this->overrideFortifyViews();
$this->overrideFortifyActions();
}
Fortify::ignoreRoutes();
}
/**
* Override Fortify features based on settings.
*/
public function overrideFortifyFeatures(): void
{
$features = Config::get('fortify.features');
foreach ($features as $key => $feature) {
if ($feature === 'registration' && Setting::get('features_registration_enabled', 0) === false) {
Arr::forget($features, $key);
} elseif ($feature === 'registration' && Setting::get('features_registration_enabled', 0) === true && Setting::get('features_registration_method', 'none') != 'fortify') {
Arr::forget($features, $key);
}
}
Config::set('fortify.features', $features);
}
/**
* Override Fortify views based on settings.
*/
public function overrideFortifyViews(): void
{
Fortify::loginView(function () {
return view('cms::public.auth.'.Setting::get('layout_login', 'login-default'));
});
Fortify::registerView(function () {
return view('cms::public.auth.register');
});
Fortify::resetPasswordView(function () {
return view('cms::public.auth.forgot-password');
});
Fortify::twoFactorChallengeView(function () {
return view('cms::public.auth.two-factor-challenge');
});
Fortify::resetPasswordView(function () {
return view('cms::public.auth.reset-password');
});
Fortify::requestPasswordResetLinkView(function () {
return view('cms::public.auth.forgot-password');
});
Fortify::verifyEmailView(function () {
return view('cms::public.auth.verify-email');
});
Fortify::confirmPasswordView(function () {
return view('cms::public.auth.confirm-password');
});
}
/**
* Override Fortify actions.
*/
public function overrideFortifyActions(): void
{
Fortify::createUsersUsing(\Adonis\Cms\Actions\Fortify\CreateNewUser::class);
Fortify::updateUserProfileInformationUsing(\Adonis\Cms\Actions\Fortify\UpdateUserProfileInformation::class);
Fortify::updateUserPasswordsUsing(\Adonis\Cms\Actions\Fortify\UpdateUserPassword::class);
Fortify::resetUserPasswordsUsing(\Adonis\Cms\Actions\Fortify\ResetUserPassword::class);
}
}
I've been playing around with this and understand the issue better now. One thing, can you show me the code where you're registering the service provider in your package?
Yeah sure mate, I have the main package service provider with this method, which is registered in the register method:
private function RegisterProviderClasses(): void
{
$providers = FileHelper::getClassNames(base_path('packages/Adonis/Cms/src/Providers'), 'Adonis\\Cms\\Providers');
foreach ($providers as $providerClass) {
if (class_exists($providerClass)) {
$this->app->register($providerClass);
} else {
echo "Class {$providerClass} does not exist.";
}
}
}
this is what's in the boot method:
$this->registerProviderClasses();
The fileHelper loads all the class names then register them dynamically:
public static function getClassNames(string $folder, string $namespaceBase): array
{
// Get all the files in the directory
$files = File::files($folder);
$classNames = [];
foreach ($files as $file) {
// Get filename without extension
$filename = $file->getBasename('.php');
// Append the namespace base
$fileNamespace = $namespaceBase.'\\'.$filename;
$classNames[] = $fileNamespace;
}
return $classNames;
}
I hope this makes sense ;-)
Makes perfect sense and super helpful. Working on this right now for you :)
Playing around with this, I've noticed that using
AliasLoader::getInstance()->alias('App\Providers\LocalServiceProvider', 'App\Providers\PackageServiceProvider');
much earlier on in the lifecycle of the app works (e.g. if it's put in bootstrap/app.php
). Have you tried adding the alias to your package service provider boot method?
I don't have a local package up and running to test this, so if you've not already tried, give that a go and we'll take it from there.
Yeah I'm sure I did try that, I'll give it a go again tomorrow and see how it goes, I'll keep you posted Alex, thanks mate.
No worries, I'll wait for you to try and then continue experimenting if not.