Skip to content

M17 / #255 — #[AsWorkflow] autoconfigures, like the other three attributes - #278

Open
gplanchat wants to merge 1 commit into
mainfrom
fix/asworkflow-autoconfigure
Open

M17 / #255 — #[AsWorkflow] autoconfigures, like the other three attributes#278
gplanchat wants to merge 1 commit into
mainfrom
fix/asworkflow-autoconfigure

Conversation

@gplanchat

@gplanchat gplanchat commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Closes issue #255.

DurableBundle::build() autoconfigured AsActivityHandler, AsNexusServiceHandler and FulfilsNexusOperation — but not AsWorkflow. Three building blocks out of four declared themselves by attribute, the fourth by folder convention in the application's services.yaml. The inconsistency was in the bundle.

The trap #255 warned about does not bite

The issue warns — reasonably — that a workflow is instantiated by reflection by WorkflowDefinitionLoader, never by the container, and that its constructor receives a WorkflowEnvironment which is not a service. It concludes that autowire: false must also be set, without which compilation would fail on a class the container will never build.

Tested rather than assumed. One of the three cases actually compiles a ContainerBuilder carrying a workflow of exactly that shape — the one the getting-started guide teaches:

#[AsWorkflow('WithEnvironment')]
final class WorkflowWithEnvironment
{
    public function __construct(private readonly WorkflowEnvironment $environment) {}
}

It passes. The mechanism: WorkflowPass only reads $definition->getClass() and adds no reference. The definition therefore stays private and unreferenced, RemoveUnusedDefinitionsPass removes it, and DefinitionErrorExceptionPass — which only reports errors for definitions that survived — has nothing to say.

No setAutowired(false): that would be defensive noise against a problem that does not exist, and it would take autowiring away from a workflow whose author legitimately wants dependencies injected.

Worth noting for review all the same: the guard holds because the definition is private and unreferenced. An application declaring its workflows public would hit the autowiring failure — but it already did before this change, since the class was already a service via App\:. The tag is not what breaks.

Documentation

Two places had you write what becomes unnecessary:

  • The guide ("Tag workflows") gave the resource: + tags: block. It now says there is nothing to write, keeps the block while showing it is redundant, and states that the tag is still read — an application that writes it keeps working.
  • The packages page promised that #[AsWorkflow] and #[AsActivity] register themselves. The first becomes true with this PR; the second stays false — #[AsActivity] is a naming attribute placed on the contract.

Ordering with #276. That second sentence is also corrected by #276, which writes — rightly, at that point — that #[AsWorkflow] is not autoconfigured. Both PRs touch the same lines of packages/_index.md. Merge #276 first, then this one, whose version is the correct one once the code has changed. If the order is reversed, this PR's version is the one to keep.

A repository convention rediscovered along the way

The fixtures did not autoload: the test classes' namespace (unit\Gplanchat\DurableBundle\…) does not match their path, which is fine for tests PHPUnit loads file by file, but not for fixtures. The repository already has the answer — unit\DurablePhpstan\Fixtures, unit\DurableLaravel\Fixtures: under Fixtures/, the namespace follows the path. Mine now conform.

Verification

main this branch
unit suite 1073 tests, 4 errors 1076 tests, 4 errors
PHPStan 2 errors 2 errors

Pre-existing and identical errors. Three cases: a workflow with no dependency is registered, a workflow receiving the environment compiles and is registered, a class without the attribute does not join the registry.

…tributs

`DurableBundle::build()` autoconfigurait AsActivityHandler, AsNexusServiceHandler et
FulfilsNexusOperation, mais pas AsWorkflow. Trois briques sur quatre se déclaraient par
attribut, la quatrième par convention de dossier, dans le services.yaml de
l'application. L'incohérence était dans le bundle, pas dans les applications.

L'issue #255 annonçait un piège : un workflow est instancié par réflexion par
WorkflowDefinitionLoader, jamais par le conteneur, et son constructeur reçoit un
WorkflowEnvironment qui n'est pas un service — baliser sans plus ferait donc échouer la
compilation.

Éprouvé plutôt que supposé, et ça ne mord pas. Un cas compile réellement un conteneur
portant un workflow de cette forme exacte, celle que le guide enseigne. La raison :
WorkflowPass ne lit que le nom de classe et n'ajoute aucune référence, donc la définition
reste privée et non référencée, et RemoveUnusedDefinitionsPass la retire avant que
DefinitionErrorExceptionPass ne rapporte l'échec d'autowiring. Pas de setAutowired(false),
qui aurait été du bruit défensif contre un problème qui n'existe pas.

Le tag reste lu : une application qui l'écrit continue de marcher, il fait double emploi.
Le guide cesse de le faire écrire, et la page des paquets cesse d'annoncer que
`#[AsActivity]` enregistre — c'est un attribut de nommage posé sur le contrat.

Les fixtures suivent la convention du dépôt, qui n'est pas celle des tests : sous
Fixtures/, le namespace suit le chemin et la classe est autochargeable, comme
unit\DurablePhpstan\Fixtures. Les classes de test, elles, gardent leur namespace
unit\Gplanchat\… que PHPUnit charge par fichier.

Suite unit : 1076 tests contre 1073 sur main, mêmes 4 erreurs d'environnement. PHPStan :
2 erreurs, la base de main.

Refs: M17 de documentation/audit/, issue #255

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gplanchat gplanchat changed the title M17 / #255 — #[AsWorkflow] s'autoconfigure, comme les trois autres attributs M17 / #255 — #[AsWorkflow] autoconfigures, like the other three attributes Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant