Lazy load services dynamically

427 Views Asked by At

After watching the Laravell Nova presentation I wanted to create similar functionality to Lenses in my own app.

I have the following concepts:

  • Entity: Standard Doctrine Entity
  • Resource: A class that describes a resource including the target entity and available lenses.
  • Lens: Has an method apply(Request $request, QueryBuilder $qb) that allow you to modify the QueryBuilder based on the Request.

The goal is to define all Lenses as a service and then somehow assign them to a Resource. This is the problem I'm trying to solve.

Attempt 1: Directly inject the Lenses into the resource

ProjectResource.php

<?php

class ProjectResource
{
    protected $lenses = [];

    public function __construct(
        ProjectRepository $repository,
        LensInterface $activeProjectLens,
        LensInterface $starredProjectLens
    ) {
        $this->lenses = [
            $activeProjectLens,
            $starredProjectLens
        ];
    }

    public function getLenses() {
        return $this->lenses;
    }
}

The downside of this is that each Lens service is instantiated and needs to be defined manually

Attempt 2: Inject tagged Lenses into the resource

In my services.yaml tag the services and assign them as an argument to the resource:

  App\Lens\ActiveProjectLens:
    tags: ['resource.project.lens']

  App\Lens\StarredProjectLens:
    tags: ['resource.project.lens']

  App\Resource\ProjectResource:
    arguments:
      $lenses: !tagged resource.project.lens

ProjectResource.php

<?php

class ProjectResource
{
    protected $lenses = [];

    public function __construct(
        ProjectRepository $repository,
        iterable $lenses
    ) {
        $this->lenses = $lenses;
    }

    public function getLenses() {
        return $this->lenses;
    }
}

The downside of this approach is every Lens service and Resource must be tagged and cannot be an auto-configured service.

**Attempt 3: Add a compiler pass **

I attempted to add the process() method to the Kernel but I didn't get too far with that.


My goal is to define a list of services somehow in the Resource and have them injected. Is there any established pattern for this?

1

There are 1 best solutions below

0
On

Your approach with the tags seems good. Symfony provides a way to automatically add tags to classes that implement a certain interface: Interface-based service configuration.

To use that you have to do the following:

  1. If you don't already have one, create an interface (e.g. App\Lens\LensInterface) and let your lens classes implement the interface.
  2. In your services.yaml file add this config:
services:
    // ...

    _instanceof:
        App\Lens\LensInterface:
            tags: ['resource.project.lens']

    App\Resource\ProjectResource:
        arguments:
            $lenses: [!tagged resource.project.lens]

    // ...

Then every class implementing your LensInterface would be injected into the ProjectResource without having to explicitly configure every single lens.