Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

i960's avatar
Level 3

Multiple services implementing same interface, switching at runtime

This is going to be hard for me to explain as I don't fully understand the concepts I'm discussing, so bear with me. I've also tried searching for an answer.

I'm trying to create multiple services that all implement the same interface/contract, and which service that is used depends on user input. What I have is multiple report types with different fields, and each type has it's own table with the custom fields. All of the reports share common fields, and those are in a reports table. I'm doing something like Class Table Inheritance, and I'm using polymorphism in Laravel to pull it off.

I have a contract in App\Contracts\Report.php that has a create() method defined. Then in App\Services I have a class for each report type that implements the contract with a create() method. Each version of the create method differs slightly based on the report type.

The part I'm lost on is how to use the different services based on user input. If I were to do what I would consider the wrong way, it would look something like this. In my controller, I would have a method like this:

    public function create(ReportContract $report)
    {
        $report->create();
    }

Then if I were to mimic a user selecting a Personal Injury report, I would do something like this:

        $user_selection = "personal_injury";

        $class = 'App\\Services\\' . studly_case($user_selection) . 'Report';

        $this->create(new $class);

That would pass a new PersonalInjuryReport to the create method, and then that create method would create a new Personal Injury report using eloquent. That's not how I would really do it, it's just hard coded to see if it works, and it does. But it's wrong and I'm not sure how I'm supposed to go about making this work.

0 likes
8 replies
i960's avatar
Level 3

Another way to explain this is that I am trying to almost exactly mimic what the built in Registrar service does in Laravel 5 where it registers a new user. But I want a different "Registrar" service for each report type based on a selection from the user. I don't know how to call the different implementations based on user input, and I for sure don't want to use a switch statement or a bunch of if statements.

JarekTkaczyk's avatar
Level 53

@i960 It looks good, you just need to bind the implementation at runtime, depending on some value - tell me how a user can choose.

@i960 In other words create a Service Provider that will bind the implementation based on the user selection - something like this:

protected $availableServices = ['SomeService', 'AnotherService', ...];

public function register()
{
    $this->app->call([$this, 'registerMyService']);
}

protected function registerMyService(Request $request)
{
    $selected = studly_case($request->get('user_selection'));


    $service = (in_array($selected, $this->availableServices))
        // if user selection is OK, then bind it
        ? $selected
        // otherwise fallback to the default implementation
        : 'DefaultService';

    $this->app->bind('Your\Contract', "Some\Namespace\\{$service}");
}
2 likes
i960's avatar
Level 3

Hmmm. So with the Registrar service, it's bound like this in AppServiceProvider:

        $this->app->bind(
            'Illuminate\Contracts\Auth\Registrar',
            'App\Services\Registrar'
        );

I would like to do something similar, but switch out 'App\Services\Registrar' at runtime based on user selection.

The user just has a dropdown with a select like this:

<select name="report_type" id="report_type">
    <option value="personal_injury">Personal Injury</option>
    <option value="motor_vehicle_accident">Motor Vehicle Accident</option>
    ... etc
 </select>

I have all the report types stored in a report_types table, and I generate the select based on that. This is how I'm actually implementing it in my controller:

$report_types = ReportType::all();

if (in_array($request->report_type, $report_types->toArray())) {
    $report_class = 'App\\Services\\' . studly_case($request->report_type) . 'Report';

     $report = new $report_class;

     $report->create();
}

I'm doing that to verify that the report type passed into the controller is valid so I'm not trying to new up a class that doesn't exist. It just seems so dirty/wrong to me. I'm trying to avoid doing crap like that in my controller or anywhere really. I'm just hoping there is a more "laravel" like way to do this.

i960's avatar
Level 3

@JarekTkaczyk That looks fantastic and gets me a step closer. Thank you! One question. If this is in a service provider, isn't that being called every time the framework boots? Even on routes where the user isn't selecting a report type?

i960's avatar
Level 3

Dude, you are amazing. I had read that before in the docs but glossed over it. I think this will work for me. Thank you!

i960's avatar
Level 3

Just an update, I found an issue with this when doing a DB migration, but I have a solution. In my case, instead of listing the various report types as an array within the service provider, I am loading the types from the DB using eloquent, like this:

$report_types = ReportType::lists('key');

The problem is, if I start fresh and run artisan migrate before the DB tables are created, then I will get an error that the report_types table doesn't exist as the framework is booting. Obviously Laravel is running this server provider upon boot, even though it's not needed on the command line and is set to be deferred. The solution is simple, I just check to see if I'm running in the console, and skip registering the service provider if I am. Like this:

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        if (!App::runningInConsole()) {
            $this->registerReportService();
        }
    }

kakallatt's avatar

@JarekTkaczyk In Controller you can use $request->input() to get data but if I use queue I can't use $request->input() so how can I get the class name?

Please or to participate in this conversation.