deebow's avatar

Laravel Service Container: Am I doing the right thing?

I have an invokable action class called: AttachMembersToRoom . Now, aside from basic form request validations, I also have complex validation, like comparing values across members, identify overlapping member rooms, and more... so I decided to make each validations into custom invokable classes, like CompareMembersValues, IdentifyOverlaps , and more....

At first, I did the following to execute every validations in AttachMembersToRoom :

class AttachMembersToRoom
{
    public function __invoke(MyData $data)
    {
        foreach ($this->validators() as $validator) {
            $validator()($data);
        }

		execute main logic here...
    }

    /**
    * @return callable[]
    **/
    private function validators(): array
    {
        return [
            fn () => app(CompareMembersValues::class),
            fn () => app(IdentifyOverlaps::class),
            ...
            ...
        ];
    }
}

but looking at it, I think it's a bit excessive to use app() for every validators I have, and I'm afraid that the whole "attaching members to room" process would take up much memory than it should be.

so I decided to bind-tag it in the service container, like so:

$this->app->tag([
    CompareMembersValues::class,
    IdentifyOverlaps::class,
], 'memberValidators');

and in AttachMembersToRoom , I made the following adjustments:

class AttachMembersToRoom
{
    public function __construct(#[Tag('memberValidators')] protected iterable $memberValidators) {}

    public function __invoke(MyData $data)
    {
        foreach ($this->memberValidators as $validator) {
            $validator($data);
        }

		execute main logic here...
    }
}

At this point, I'm not even sure if I'm doing the right thing, or am I even overcomplicating things?

But my goal for all of this is the testability.. at some point, I want to mock each of these validators, and bypass some of these validators during testing.

0 likes
7 replies
janum's avatar

i think what you are doing actually makes sense and you are moving in the right direction. Using the service container tag approach is cleaner than calling app() inside your method for each validator. It makes your AttachMembersToRoom class more testable and decoupled because the validators are injected rather than resolved manually.

A few points to consider:

Memory / Performance: Tagging validators in the container doe not instantiate them all upfront unless you explicitly resolve them. PHP will lazy-load them when iterating over $memberValidators, so memory usage is usually fine.

Testability: Since you inject an iterable of validators, you can now mock or bypass individual validators in tests easily by passing a custom iterable to the constructor. That is much cleaner than using app() directly.

Simplicity vs Overcomplication: If you only have a few validators, this might feel like overengineering, but if your validation rules grow, this approach scales well. You could even add an interface like MemberValidatorInterface to make it explicit what each validator does.

deebow's avatar

It's such as a relief that I am going in the right path.

Thank you!

aleahy's avatar

I don't know your app, so I can't comment on the memory issues, but....

I don't see anything wrong with the first method you show. The classes are clearly listed there if you were to come back to this code after you've completely forgotten how it works. And by using the container to create the classes, you can easily mock each one in tests. And are you dealing with enough data that memory is a concern? Are you able to chunk up the data in any way?

The second way seems to hide everything too much for my liking. If I were new to this project or I had forgotten how it connects, I would have to click around to service providers just to know where everything is and how it works.

deebow's avatar

"The second way seems to hide everything too much for my liking"

I guess it's true, but I think it's only partially true. For me, the #Tag[] attribute annotates it well. But again, it's a preference thing lol.

Anyway, thank you so much!

deebow's avatar

I'm really sorry, I saw your comment in my notification but it's not anywhere in the replies section of my post, tried refreshing several times. Same with Snapey's comment.

OussamaMater's avatar

Feels a bit overcomplicated if I'm being honest.

I don't have enough context about the rules or whether the order matters, etc. But if you have already created the classes and they all take the same input, you might as well turn them into validation rules and chain them in your validation, like this:

validator($data, rules: [
    // .. other request validation rules if they make sense
    new CompareMembersValues,
    new IdentifyOverlaps,
])->validate();

No need for tags

Please or to participate in this conversation.