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

Ligonsker's avatar

Where to place this piece of code that repeats itself?

Hello,

I remember once someone told me here that using custom helper functions is for lazy people and mostly should not be used, so now I wonder where I should put a simple code that needs to be used in a few places. This code just fetched certain $data about the user based on the environment, i.e. if it's the test environment read the details from one place and if it's prod read from another:

$env = config('app.env');
if ($env === 'test') {
    $data = // get from one place;
} else if ($env === 'prod') {
    $data = // get from another place
}

And I need to use it in many places and I really wanted to just put it in App\Helpers\Util or similar, create some static method and use it wherever I need. But, is there a better way to do it?

Ty

0 likes
22 replies
martinbean's avatar

@ligonsker It was probably me, as that’s an opinion I do hold 😅

In your case, if application behaviour is changing based on the application environment then that isn’t something you want as a “helper” and peppering throughout your app; that sounds like something that’s better wrapping up into services and binding to the container, and the correct implementation being resolved at runtime based on the environment:

public function register(): void
{
    $this->app->singleton(FooInterface::class, function () {
        return $this->app->environment('production')
            ? $this->app->make(ProductionFooImplementation::class);
            : $this->app->make(NonProductionFooImplementation::class);
    });
}

Now you would just use dependency injection to use that service and call its methods, instead of peppering an if/else statement everywhere:

public function someControllerAction(FooInterface $foo)
{
    $data = $foo->someMethod();
}

It doesn’t matter what environment your application is running in now, your code is the same regardless. It’s just that $foo will be an instance of one class if the environment is production, or an instance of another class if it’s a different environment. But whatever implementation $foo holds adheres to the same interface, so you know what methods you can safely call, regardless of the implementation.

1 like
Ligonsker's avatar

@martinbean yes it really might be you 😁

But when using this:

public function someControllerAction(FooInterface $foo)
{
    $data = $foo->someMethod();
}

Then what do I do if for example I also need a FormRequest for that controller method? I won't be able to inject the FormRequest class?

That's why I wanted some more "global" static class that can be used from anywhere, not just controller

martinbean's avatar

Then what do I do if for example I also need a FormRequest for that controller method? I won't be able to inject the FormRequest class?

@Ligonsker Why wouldn’t you be able to? You just add it as another parameter:

public function someAction(StoreFooRequest $request, FooInterface $foo)
{
    // Form request will run as normal...

    $data = $foo->someMethod();
}
1 like
Ligonsker's avatar

@martinbean Oh, ok I think I never had too many parameters in the request 😅. But what about cases where it's not about injecting into controller's method?

let's say I need somewhere else, and not a controller. Then where do I do it?

Also, just to be sure, this method:

public function register(): void
{
    $this->app->singleton(FooInterface::class, function () {
        return $this->app->environment('production')
            ? $this->app->make(ProductionFooImplementation::class);
            : $this->app->make(NonProductionFooImplementation::class);
    });
}

is the register() of a service provider that I will make right?

<?php
 
namespace App\Providers;
 
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
use App\Services\FooInterface;
use App\Services\ProductionFooImplementation;
use App\Services\NonProductionFooImplementation;
 
class FooServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $this->app->singleton(FooInterface::class, function () {
            return $this->app->environment('production')
                ? $this->app->make(ProductionFooImplementation::class);
                : $this->app->make(NonProductionFooImplementation::class);
        });
    }
}

Am I doing it right? Then, are the FooInterface, ProductionFooImplementation, and NonProductionFooImplementation classes considered Services? Or how would I do that (I mean where to place these files, the interface and the other classes)? I just never did anything like that so I prefer to do it right 😅

Snapey's avatar

@Ligonsker You can put it in its own service provider or add to one of the existing ones.

1 like
Ligonsker's avatar

@Snapey In my code I need it for example in a Listener. Then I'm not sure I can inject it but maybe just use it like a method in the handle or construct method of the Listener:

namespace App\Listeners;

class MyListener
{ 
    public function __construct() {
        $data = // get the right data for prod or test       
    }
}

How would I use that if it's a service provider?

Also, I never did that so I might have really dumb questions that might be obvious but pass above my head because I just never did it. I think that after this one case, I will know better

Ligonsker's avatar

@PovilasKorop thanks, I am going to watch now! But also regardless of the interface, it also seem to be a service provider.

@martinbean In my example, I actually need to use it in a Listener, so I can't inject it like I would do in a Controller I assume? (Which is an example of why I needed a method that can be accessed from anywhere):

namespace App\Listeners;

class MyListener
{ 
    public function __construct() {
        $data = // get the right data for prod or test       
    }
}

Or, you mean that I should inject the service to the controller that dispatches the event, then save the $data as a public property of the event, and then because event is injected to a listener, I will have it in the $event?

Snapey's avatar

@Ligonsker You can inject via the constructor.

<?php

namespace App\Listeners;


class AccumulateDriver
{
	$public FooInterface $foo;

    public function __construct(FooInterface $foo)
    {
        $this->foo = $foo;
    }

    public function handle($event)

// etc

When the Listener is instantiated by the framework it will check the dependencies and realise it needs to provide the listener with an instance of FooInterface

If running php 8 you can use Constructor property promotion to simplify the constructor https://stitcher.io/blog/constructor-promotion-in-php-8

1 like
Ligonsker's avatar

@Snapey At first I thought that I should inject the provider to the Controller method that dispatches the Event, and then save it in a public property of that Event. Then the Event is being injected into the Listener thus I will have the data all the way from the controller. Would that also work?

Because I just never knew I could simply inject providers every where I want 😅, even in a Listener constructor. But also how does that work? What happens if there is another class where I can pass variables to the constructor:

new SomeLaravelClass($my, $variables);

Then where would I inject FooInterface $foo if the __construct expects other variables:

public function __construct($my, $variables) // <-- how can I inject in this case?
{
    $this->foo = $foo;
}

In the controller as martinbean said, I can just add it alongside the other parameters:

public function (FooFormValidation $request, FooInterface $foo)

But how do I know if I can do it with other methods as well?

martinbean's avatar

@Ligonsker You can inject things in classes and methods that are going to get resolved by the container.

1 like
Snapey's avatar

@Ligonsker You define what the constructor needs. It can be one or multiple dependencies.

Laravel will try to resolve them all from the container by using reflection to see what the class needs. If it cannot work out how to instantiate the class with all its requirements then you will get a binding resolution exception.

1 like
Ligonsker's avatar

@Snapey @martinbean

Where do I find what is going to get resolved by the container (For example how would I know that I can inject the provider to a Listener? I assume it's also going to be resolved by it. But where do I see that?)

@snapey but how can I define it? what order?

public function __construct($my, $variables, FooInterface $foo) {...}
// or
public function __construct($my, FooInterface $foo, $variables) {...}
// or
public function __construct(FooInterface $foo, $my, $variables) {...}

Because when I pass my variables to it, I do it without the injected class:

new SomeLaravelClass($my, $variables);

Then how do I know where to place the injected service/class? Like how would I know the order I could inject it to a controller's method:

public function someAction(StoreFooRequest $request, FooInterface $foo) {...}
// or
public function someAction(FooInterface $foo, StoreFooRequest $request) {...}
Ligonsker's avatar

@PovilasKorop Update: I finished watching the video and it really helped, thank you 😀

Assuming I do go by Contracts (as @martinbean said) I have something to ask about your implementation vs @martinbean's. In your video, you don't use make(), you use bind() and you use it in the boot() of AppServiceProvider. How does this code from above compare to yours:

    public function register(): void
    {
        $this->app->singleton(FooInterface::class, function () {
            return $this->app->environment('production')
                ? $this->app->make(ProductionFooImplementation::class);
                : $this->app->make(NonProductionFooImplementation::class);
        });
    }

assuming I also put it in AppServiceProvider, why in this case it's using make() and why it's in register() and not boot()?

PovilasKorop's avatar

@Ligonsker to be honest, I'm not entirely sure, I always did it my way. I guess they are different syntax options to achieve same (similar) results. If both work, pick any of them you like.

martinbean's avatar

In your video, you don't use make(), you use bind()

@ligonsker They have two completely different functions:

  • make resolves something from the container
  • bind binds something to the container

They’re not analogous.

As for register versus boot, you should register bindings in the register method, as the name implies. The boot methods of service providers are ran after all service providers have finished registering. Laravel basically loops through each service provider twice:

  • Calls the register method of each service provider in the first iteration
  • Then calls any boot methods of service providers in a second iteration
2 likes
PovilasKorop's avatar

@martinbean thanks for the clarification, perhaps I haven't understood the full situation while reading this post from my phone. And/or it's time to review my video and shoot a new one with clarification.

1 like
Ligonsker's avatar

@martinbean thanks, I just read a bit more in-depth about Services/Containers/Providers. I've never used it that way as I always used Laravel's built-in providers and never did extra.

If I understand right now after reading, in your example, it means that you also have ProductionFooImplementation and NonProductionFooImplementation bound to a service container somewhere previously, that's why you can use make on them here:

// in some service provider
$this->app->bind(ProductionFooImplementation::class, function (Application $app) {
    return new ProductionFooImplementation();
});

Then, whenever I inject FooInterface, I will (btw should I change it to a Contract instead of a Service in your example?), it will bind (as a singleton) either ProductionFooImplementation or NonProductionFooImplementation.

The thing I don't understand in this example is the relation between the interface itself and the two implementations. Because with PHP's interfaces, I actually need to implement the interface using implements. But here, the interface is merely for the name of the class to be injected as a dependency, because I return completely different classes.

Or you meant that it should be class ProductionFooImplementation implements FooInterface for example?

martinbean's avatar

@Ligonsker Any implementations should implement the interface used for the binding.

class ProductionFooImplementation implements FooInterface
{
    // Implementation...
}
class NonProductionFooImplementation implements FooInterface
{
    // Implementation...
}

You then get one of these when you ask for FooInterface from the container. You don’t know which one; you just rely on it being an implementation of FooInterface and call methods declared by that interface.

1 like
PovilasKorop's avatar

@Ligonsker ok I decided to spent some more time and create a full example to write on my blog. So here's the code, I hope it will give you full understanding.

One of the solutions is this:

  1. Create an interface with a method getList() that should return the array
  2. Create two Service classes with different logic for that method: one for "real data" and one for "fake data".
  3. When calling that Service from Controller, you call the interface everywhere instead.
  4. In the AppServiceProvider, you resolve the Interface with one of the Service classes, depending on the environment.

Let's see it in action, in the code.

Step 1. Create Interface

app/Interfaces/CityListInterface.php:

namespace App\Interfaces;

interface CityListInterface {

    public function getList(): array;

}

Step 2. Create Two Service Classes

Both classes should implement that interface which requires them to have a method getList() with identical parameters and return types.

app/Services/RealCityService.php:

namespace App\Services;

use App\Interfaces\CityListInterface;
use App\Models\City;

class RealCityService implements CityListInterface {

    public function getList(): array
    {
        return City::all()->toArray();
    }

}

app/Services/FakeCityService.php:

namespace App\Services;

use App\Interfaces\CityListInterface;

class FakeCityService implements CityListInterface {

    public function getList(): array
    {
        return config('app.fake_cities');
    }

}

Step 3. In Controller, Type-Hint the Interface

Whenever you need to get the list of cities in Controller or elsewhere, type-hint the Interface, not a specific service.

If you need that in a particular method, do this:

use App\Interfaces\CityListInterface;

class RestaurantController extends Controller
{
    public function create(CityListInterface $cityList)
    {
        $cities = $cityList->getList();

If you need to use the service in multiple methods of the class, use it in Constructor:

use App\Interfaces\CityListInterface;

class RestaurantController extends Controller
{
    public function __construct(public CityListInterface $cityList) { }

    public function create()
    {
        $cities = $this->cityList->getList();

        // ...
    }

    public function edit(Restaurant $restaurant)
    {
        $cities = $this->cityList->getList();

        // ...
    }

Step 4. Resolve Interface with Service

In the AppServiceProvider or any ServiceProvider (Laravel default one or your custom one), you need to add this into the register() method.

app/Providers/AppServiceProvider.php:

namespace App\Providers;

use App\Interfaces\CityListInterface;
use App\Services\FakeCityService;
use App\Services\RealCityService;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        if (app()->isProduction()) {
            $this->app->singleton(CityListInterface::class, RealCityService::class);
        } else {
            $this->app->singleton(CityListInterface::class, FakeCityService::class);
        }
    }

Here you have the logic that creates a specific object of your specific Service class for all your application based on a global environment. So, you define it once here and "forget" it.

Back to the @martinbean point about singleton/bind, I dug deeper and indeed, singleton is a better approach. I've also seen different syntax using boot() method of the ServiceProvider, and bind() instead of singleton():

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        if (app()->isProduction()) {
            $this->app->bind(CityListInterface::class, RealCityService::class);
        } else {
            $this->app->bind(CityListInterface::class, FakeCityService::class);
        }
    }

While practically you wouldn't notice much difference, this bind() method would create a new Service object whenever called. The singleton() way creates the object once and reuses it, saving memory.

2 likes
Snapey's avatar

@PovilasKorop getting the extra data for the user might be an 'expensive' operation such as calling an LDAP service. A singleton allows the data to be fetched once and then stored as properties of the singleton, avoiding the need to call the external service more than once in any request.

Of course this could be extended further by the service using caching to further improve performance

1 like

Please or to participate in this conversation.