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

vincent15000's avatar

Service Provider to be used with and without an authenticated user

Hello,

I have this service provider.

It works fine, but now I'd like to run a cron task to use automatically the right data service according to a company.

public function register(): void
{
    $this->app->singleton(DatasourceInterface::class, function (Application $app) {
        $user = $this->app->make('auth')->user();

        $datasource = $user->company->datasource?->value;
        $credentials = $user->company->datasource_credentials;

        if ($datasource) {
            switch ($datasource) {
                case 'pipedrive':                
                    return new PipedriveDataService($this->app->make('config')->get('services.pipedrive.api_url'), $credentials);
                case 'googlesheet':
                    return new GoogleSheetDataService($this->app->make('config')->get('services.googlesheet.api_url'), $credentials);
                default:
                    throw new Exception('Unsupported data source', 404);
            }
        }
    });
}

How can I adapt my service provider so that I can use from a cron task.

foreach ($companies as $company) {
	$dataservice = ...
}

Thanks for your help.

V

0 likes
14 replies
LaryAI's avatar
Level 58

To adapt your service provider so that it can be used both with an authenticated user and in a cron task, you need to modify the logic to handle cases where there is no authenticated user. In a cron job, you typically won't have an authenticated user, so you'll need to pass the company information directly.

Here's how you can modify your service provider to accommodate both scenarios:

  1. Modify the Service Provider: Update the register method to accept a company parameter when no user is authenticated.
public function register(): void
{
    $this->app->singleton(DatasourceInterface::class, function (Application $app, $params = []) {
        $user = $this->app->make('auth')->user();
        $company = $params['company'] ?? ($user ? $user->company : null);

        if (!$company) {
            throw new Exception('No company available', 404);
        }

        $datasource = $company->datasource?->value;
        $credentials = $company->datasource_credentials;

        if ($datasource) {
            switch ($datasource) {
                case 'pipedrive':                
                    return new PipedriveDataService($this->app->make('config')->get('services.pipedrive.api_url'), $credentials);
                case 'googlesheet':
                    return new GoogleSheetDataService($this->app->make('config')->get('services.googlesheet.api_url'), $credentials);
                default:
                    throw new Exception('Unsupported data source', 404);
            }
        }
    });
}
  1. Using the Service Provider in a Cron Task: When running a cron task, you can pass the company information directly to the service provider.
foreach ($companies as $company) {
    $dataservice = app(DatasourceInterface::class, ['company' => $company]);
    // Use $dataservice as needed
}

Explanation:

  • Service Provider Modification: The service provider now accepts an optional company parameter. If a user is authenticated, it defaults to using the user's company. If not, it uses the company passed in the parameters.

  • Cron Task Usage: When iterating over companies in a cron task, you pass each company to the service provider using Laravel's app() helper, which allows you to resolve the service with additional parameters.

This approach ensures that your service provider can be used both in a web context with an authenticated user and in a background job or cron task where you manually specify the company.

martinbean's avatar

@vincent15000 This is something a factory would be better for. Have a factory that you can pass a user to, and it returns the correct implementation.

class DataServiceFactory
{
    protected Container $container;

    public function __construct(Container $container)
    {
        $this->container = $container;
    }

    public function forCompany(Company $company)
    {
        return match ($company->datasource) {
            DataSource::GoogleSheet = $this->container->make(GoogleSheetDataService::class),
            DataSource::Pipedrive => $this->container->make(PipedriverDataService::class),
        };
    }
}

You’d bind this factory to the container:

$this->app->singleton(DataServiceFactory::class, function () {
    return new DataServiceFactory($this->app);
});

And then you can resolve data services by passing in a Company model instance, instead of relying of trying to grab an authenticated user (and their company) in contexts where there isn’t an authenticated user (such as a scheduled task).

$service = $factory->forCompany($company);
1 like
vincent15000's avatar

@martinbean Ok thank you.

But I need to initialize the PipedriveDataService and the GoogleSheetDataService with the datas (api_url and credentials), will it work with the factory ?

martinbean's avatar

But I need to initialize the PipedriveDataService and the GoogleSheetDataService with the datas (api_url and credentials), will it work with the factory ?

@vincent15000 The credentials are coming from your Company model. That model is passed to the factory method. So just pull the credentials from that instance?

public function forCompany(Company $company)
{
    $config = $this->container->make('config');

    return match ($company->datasource) {
        DataSource::GoogleSheet => new PipedriveDataService($config->get('services.pipedrive.api_url'), $company->credentials),
        DataSource::Pipedrive => new GoogleSheetDataService($config->get('services.googlesheet.api_url'), $company->credentials),
    };
}
1 like
vincent15000's avatar

@martinbean Very interesting ... hmmm ...

There is only one thing that disturbs me : I have 2 places in my code to get the right data service.

  • a DatasourceInterface for situations where there is an authenticated user
  • a DataServiceFactory for situations where I don't have an authenticated user

Is it a good idea to keep both ?

Would it be a good idea to try to merge both ?

martinbean's avatar

@vincent15000 I don’t really follow? How are you going to get a data source implementation when it relies on being instantiated using a company’s credentials?

1 like
vincent15000's avatar

@martinbean Ok I understand.

The idea is for example to use the factory with the authenticated users' company.

martinbean's avatar

@vincent15000 The factory just uses the Company instance it’s given, to instantiate a specific implementation. It doesn’t care whether that Company model instance came from an authenticated user or not.

You give it a company, it then just creates and returns the relevant data service for that company.

1 like
vincent15000's avatar

@martinbean Yes I understand this.

Effectively it's easier to have a specialized service provider for an authenticated user and another one for a company.

martinbean's avatar

Effectively it's easier to have a specialized service provider for an authenticated user and another one for a company.

@vincent15000 That’s not what service providers are for. At all.

A service provider just binds a service to the container. That’s it. I don’t get why you’re so obsessed with “authenticated” and “unauthenticated” users. A service bound to the container shouldn’t care if there’s an authenticated user or not. That’s a runtime detail.

There’s absolutely no need to have one “service” for “authenticated users” and another service for unauthenticated users. You’re just then doubling the number of classes you need to create. And tripling if you then decide you need another “service” for an entirely different use case.

1 like
vincent15000's avatar

@martinbean Obsessed no ;) ... but sure I know that I handle service providers for a short time and I continue discovering how it works yet.

Hmmm ... ok ... I see ... so this code $user = $this->app->make('auth')->user(); isn't a good idea (see my post), is it ? Hmmm ... It's still pratice to get the right instantiation without having to pass a custom user to a function.

I should better do another way ?

Perhaps using the factory and add the function forUser(User $user) ?

Would it be better ?

martinbean's avatar

@vincent15000 But in your example, instantiating and configuring a service was done through a company, not a user. You accessed the company from the user, and then the service chosen was based on $company->datasource, and the configuration also came from the company model ($company->datasource_credentials).

So, again, why are you getting stuck on making things about a user when from your own code example, it’s all based on a company?

1 like
vincent15000's avatar

@martinbean Because I'm using this code (I already had this code) for automatically retrieving the right data service for authenticated users.

public function register(): void
{
    $this->app->singleton(DatasourceInterface::class, function (Application $app) {
        $user = $this->app->make('auth')->user();

        $datasource = $user->company->datasource?->value;
        $credentials = $user->company->datasource_credentials;

        if ($datasource) {
            switch ($datasource) {
                case 'pipedrive':                
                    return new PipedriveDataService($this->app->make('config')->get('services.pipedrive.api_url'), $credentials);
                case 'googlesheet':
                    return new GoogleSheetDataService($this->app->make('config')->get('services.googlesheet.api_url'), $credentials);
                default:
                    throw new Exception('Unsupported data source', 404);
            }
        }
    });
}

This is the first code I have and already in use in the application.

I need the factory in two different cases : in controllers and in a cron task. But effectively I could use the same factory (with the forCompany() function) in all cases, I just have to pass auth()->user()->company_id to the factory to get what I need.

martinbean's avatar
Level 80

I need the factory in two different cases : in controllers and in a cron task. But effectively I could use the same factory (with the forCompany() function) in all cases, I just have to pass auth()->user()->company_id to the factory to get what I need.

@vincent15000 Yes. This is what I’ve been trying to say all along?

You need to stop thinking about “I need X for controllers and Y for cron tasks”. You don’t if you just have a factory that returns something, regardless of the context it’s being used in.

So, use a factory where ever you need it, passing in a company model to resolve the correct implementation based on that company’s configuration. Again, it doesn’t matter where the company model comes from. It may come from an authenticated user in a controller, it may not.

$dataService = $dataServiceFactory->forCompany($user->company);
1 like

Please or to participate in this conversation.