Skip to content

DI with job Invokable classes #78

@jenky

Description

@jenky

https://chevere.org/packages/workflow.html#example-with-invokable-class-dependencies

It doesn’t work properly when the properties have default values.

With an object default

public function __construct(
    private readonly LoggerInterface $logger = new NullLogger(),
) {
}
[Chevere\Container\Exceptions\ContainerException]                                                              
  [logger]: Failed to instantiate Psr\Log\LoggerInterface: Cannot instantiate interface Psr\Log\LoggerInterface

With primitive default values

public function __construct(
      private readonly LoggerInterface $logger,
      private readonly string $channel = 'default',
  ) {
  }
[OutOfBoundsException]                         
  Dependency `channel` not defined in container

I’m not sure whether this is a bug, but I think the container shouldn’t be required to bind values that already have defaults.
If you have multiple containers for different workflow groups, this becomes quite repetitive, especially as the class dependencies grow later.

Another thing I’d suggest is that the container might already contain the invokable class definition.

if (is_string($action)) {
      $dependencies = $this->run->workflow()->dependencies()->extract(
          $action,
          $this->run->container()
      );
      /** @var ActionInterface $action */
      $action = new $action(...$dependencies);
  }

We could try resolving $action through the container before extracting dependencies:

if (is_string($action)) {
  if ($this->run->container()->has($action)) {
    $action = $this->run->container()->get($action);
  }
// ...
}

This might look like a hack because you can pass the invokable object directly. However, it would mean you need to instantiate your invokable with all dependencies before passing it to the workflow, which defeats the purpose of using the container.

In reality, some compiled containers (e.g., Symfony) can create a service locator (a separate lazy-loaded container that provides access to a set of predefined services while instantiating them only when actually needed) that contains all invokable action definitions. Then I can keep using invokable class names in the workflow sync/async and simply pass the locator as the container to run().

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions