Summer sale! Save 50% on access to our entire library of courses.Join here →
Laravel

Overriding Service Provider Methods (Fortify)

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?

derekbuntin
derekbuntin
0
10
312
alex
alex
Moderator

Is it this change directly that's affecting this (just so I'm clear)?

https://github.com/laravel/framework/pull/51343

derekbuntin
derekbuntin

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.

alex
alex
Moderator

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?

derekbuntin
derekbuntin

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);
    }
}
alex
alex
Moderator

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?

derekbuntin
derekbuntin

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 ;-)

alex
alex
Moderator

Makes perfect sense and super helpful. Working on this right now for you :)

alex
alex
Moderator

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.

derekbuntin
derekbuntin

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.

alex
alex
Moderator

No worries, I'll wait for you to try and then continue experimenting if not.