1919use Temporal \Internal \Declaration \Prototype \WorkflowPrototype ;
2020
2121/**
22+ * @psalm-type Consumer = callable(ValuesInterface): mixed
23+ * @psalm-type OnSignalCallable = \Closure(non-empty-string $name, Consumer $handler, ValuesInterface $arguments): void
24+ *
2225 * @internal
2326 */
2427final class SignalDispatcher implements Destroyable
2528{
2629 /** @var array<non-empty-string, SignalMethod> */
2730 private array $ signalHandlers = [];
2831
29- private readonly SignalQueue $ signalQueue ;
32+ /**
33+ * @var OnSignalCallable
34+ */
35+ private \Closure $ onSignal ;
36+
37+ /**
38+ * @var array<int, SignalQueueItem>
39+ */
40+ private array $ queue = [];
41+
42+ /**
43+ * @var array<non-empty-string, Consumer>
44+ */
45+ private array $ consumers = [];
46+
47+ /**
48+ * A fallback consumer to handle signals when no consumer is attached.
49+ *
50+ * @var null|\Closure(non-empty-string, ValuesInterface): mixed
51+ */
52+ private ?\Closure $ dynamicConsumer = null ;
3053
3154 /**
3255 * @param object $context Workflow instance.
@@ -35,16 +58,17 @@ public function __construct(
3558 WorkflowPrototype $ prototype ,
3659 private readonly object $ context ,
3760 ) {
38- $ this ->signalQueue = new SignalQueue ();
39-
4061 foreach ($ prototype ->getSignalHandlers () as $ definition ) {
4162 $ this ->addFromSignalDefinition ($ definition );
4263 }
4364 }
4465
45- public function getSignalQueue (): SignalQueue
66+ /**
67+ * @param OnSignalCallable $handler
68+ */
69+ public function onSignal (\Closure $ handler ): void
4670 {
47- return $ this ->signalQueue ;
71+ $ this ->onSignal = $ handler ;
4872 }
4973
5074 /**
@@ -53,7 +77,7 @@ public function getSignalQueue(): SignalQueue
5377 */
5478 public function getSignalHandler (string $ name ): \Closure
5579 {
56- return fn (ValuesInterface $ values ) => $ this ->signalQueue -> push ($ name , $ values );
80+ return fn (ValuesInterface $ values ) => $ this ->push ($ name , $ values );
5781 }
5882
5983 /**
@@ -67,7 +91,7 @@ public function addSignalHandler(string $name, callable $handler, string $descri
6791 $ handler ,
6892 $ description ,
6993 );
70- $ this ->signalQueue -> attach ($ name , $ handler );
94+ $ this ->attach ($ name , $ handler );
7195 }
7296
7397 public function addFromSignalDefinition (SignalDefinition $ definition ): void
@@ -79,20 +103,32 @@ public function addFromSignalDefinition(SignalDefinition $definition): void
79103 $ handler ,
80104 $ definition ->description ,
81105 );
82- $ this ->signalQueue -> attach ($ name , $ handler );
106+ $ this ->attach ($ name , $ handler );
83107 }
84108
85109 /**
86110 * @param callable(non-empty-string, ValuesInterface): mixed $handler
87111 */
88112 public function setDynamicSignalHandler (callable $ handler ): void
89113 {
90- $ this ->signalQueue ->setFallback ($ handler (...));
114+ $ consumer = $ handler (...);
115+
116+ $ this ->dynamicConsumer = $ consumer ;
117+
118+ // Flush all signals that have no consumer
119+ foreach ($ this ->queue as $ k => $ item ) {
120+ if (\array_key_exists ($ item ->name , $ this ->consumers )) {
121+ continue ;
122+ }
123+
124+ unset($ this ->queue [$ k ]);
125+ $ this ->consumeFallback ($ item ->name , $ item ->values );
126+ }
91127 }
92128
93129 public function clearSignalQueue (): void
94130 {
95- $ this ->signalQueue -> clear () ;
131+ $ this ->queue = [] ;
96132 }
97133
98134 /**
@@ -108,11 +144,10 @@ public function getSignalHandlers(): array
108144 ->setDescription ($ handler ->description );
109145 }
110146
111- // todo
112- // if ($this->dynamic !== null) {
113- // $handlers[] = (new WorkflowInteractionDefinition())
114- // ->setDescription('Dynamic signal handler');
115- // }
147+ if ($ this ->dynamicConsumer !== null ) {
148+ $ handlers [] = (new WorkflowInteractionDefinition ())
149+ ->setDescription ('Dynamic signal handler ' );
150+ }
116151
117152 \usort (
118153 $ handlers ,
@@ -127,8 +162,66 @@ public function getSignalHandlers(): array
127162
128163 public function destroy (): void
129164 {
130- $ this ->signalQueue ->clear ();
131- $ this ->signalQueue ->destroy ();
165+ $ this ->queue = [];
166+ $ this ->consumers = [];
167+ $ this ->dynamicConsumer = null ;
168+ unset($ this ->onSignal );
132169 $ this ->signalHandlers = [];
133170 }
171+
172+ /**
173+ * @param non-empty-string $signal
174+ */
175+ private function push (string $ signal , ValuesInterface $ values ): void
176+ {
177+ if (isset ($ this ->consumers [$ signal ])) {
178+ $ this ->consume ($ signal , $ values , $ this ->consumers [$ signal ]);
179+ return ;
180+ }
181+
182+ if ($ this ->dynamicConsumer !== null ) {
183+ $ this ->consumeFallback ($ signal , $ values );
184+ return ;
185+ }
186+
187+ $ this ->queue [] = new SignalQueueItem ($ signal , $ values );
188+ }
189+
190+ /**
191+ * @param non-empty-string $signal
192+ * @param Consumer $consumer
193+ */
194+ private function attach (string $ signal , callable $ consumer ): void
195+ {
196+ $ this ->consumers [$ signal ] = $ consumer ; // overwrite
197+
198+ foreach ($ this ->queue as $ k => $ item ) {
199+ if ($ item ->name === $ signal ) {
200+ unset($ this ->queue [$ k ]);
201+ $ this ->consume ($ signal , $ item ->values , $ consumer );
202+ }
203+ }
204+ }
205+
206+ /**
207+ * @param non-empty-string $signal
208+ * @param Consumer $consumer
209+ */
210+ private function consume (string $ signal , ValuesInterface $ values , callable $ consumer ): void
211+ {
212+ ($ this ->onSignal )($ signal , $ consumer , $ values );
213+ }
214+
215+ /**
216+ * @param non-empty-string $signal
217+ */
218+ private function consumeFallback (string $ signal , ValuesInterface $ values ): void
219+ {
220+ $ handler = $ this ->dynamicConsumer ;
221+ \assert ($ handler !== null );
222+
223+ // Wrap the fallback consumer to call interceptors
224+ $ consumer = static fn (ValuesInterface $ values ): mixed => $ handler ($ signal , $ values );
225+ ($ this ->onSignal )($ signal , $ consumer , $ values );
226+ }
134227}
0 commit comments