Skip to content

Commit 4748259

Browse files
committed
[Worflow] Add a TraceableWorkflow
1 parent 3327326 commit 4748259

File tree

4 files changed

+205
-1
lines changed

4 files changed

+205
-1
lines changed

DataCollector/WorkflowDataCollector.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
use Symfony\Component\HttpFoundation\Response;
1616
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
1717
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
18+
use Symfony\Component\VarDumper\Caster\Caster;
19+
use Symfony\Component\VarDumper\Cloner\Stub;
20+
use Symfony\Component\Workflow\Debug\TraceableWorkflow;
1821
use Symfony\Component\Workflow\Dumper\MermaidDumper;
22+
use Symfony\Component\Workflow\Marking;
23+
use Symfony\Component\Workflow\TransitionBlocker;
1924

2025
/**
2126
* @author Grégoire Pineau <lyrixx@lyrixx.info>
@@ -34,11 +39,17 @@ public function collect(Request $request, Response $response, \Throwable $except
3439
public function lateCollect(): void
3540
{
3641
foreach ($this->workflows as $workflow) {
42+
$calls = [];
43+
if ($workflow instanceof TraceableWorkflow) {
44+
$calls = $this->cloneVar($workflow->getCalls());
45+
}
46+
3747
// We always use a workflow type because we want to mermaid to
3848
// create a node for transitions
3949
$dumper = new MermaidDumper(MermaidDumper::TRANSITION_TYPE_WORKFLOW);
4050
$this->data['workflows'][$workflow->getName()] = [
4151
'dump' => $dumper->dump($workflow->getDefinition()),
52+
'calls' => $calls,
4253
];
4354
}
4455
}
@@ -57,4 +68,38 @@ public function getWorkflows(): array
5768
{
5869
return $this->data['workflows'] ?? [];
5970
}
71+
72+
public function getCallsCount(): int
73+
{
74+
$i = 0;
75+
foreach ($this->getWorkflows() as $workflow) {
76+
$i += \count($workflow['calls']);
77+
}
78+
79+
return $i;
80+
}
81+
82+
protected function getCasters(): array
83+
{
84+
$casters = [
85+
...parent::getCasters(),
86+
TransitionBlocker::class => function ($v, array $a, Stub $s, $isNested) {
87+
unset(
88+
$a[sprintf(Caster::PATTERN_PRIVATE, $v::class, 'code')],
89+
$a[sprintf(Caster::PATTERN_PRIVATE, $v::class, 'parameters')],
90+
);
91+
92+
$s->cut += 2;
93+
94+
return $a;
95+
},
96+
Marking::class => function ($v, array $a, Stub $s, $isNested) {
97+
$a[Caster::PREFIX_VIRTUAL.'.places'] = array_keys($v->getPlaces());
98+
99+
return $a;
100+
},
101+
];
102+
103+
return $casters;
104+
}
60105
}

Debug/TraceableWorkflow.php

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Workflow\Debug;
13+
14+
use Symfony\Component\Stopwatch\Stopwatch;
15+
use Symfony\Component\Workflow\Definition;
16+
use Symfony\Component\Workflow\Marking;
17+
use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface;
18+
use Symfony\Component\Workflow\Metadata\MetadataStoreInterface;
19+
use Symfony\Component\Workflow\TransitionBlockerList;
20+
use Symfony\Component\Workflow\WorkflowInterface;
21+
22+
/**
23+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
24+
*/
25+
class TraceableWorkflow implements WorkflowInterface
26+
{
27+
private array $calls = [];
28+
29+
public function __construct(
30+
private readonly WorkflowInterface $workflow,
31+
private readonly Stopwatch $stopwatch,
32+
) {
33+
}
34+
35+
public function getMarking(object $subject, array $context = []): Marking
36+
{
37+
return $this->callInner(__FUNCTION__, \func_get_args());
38+
}
39+
40+
public function can(object $subject, string $transitionName): bool
41+
{
42+
return $this->callInner(__FUNCTION__, \func_get_args());
43+
}
44+
45+
public function buildTransitionBlockerList(object $subject, string $transitionName): TransitionBlockerList
46+
{
47+
return $this->callInner(__FUNCTION__, \func_get_args());
48+
}
49+
50+
public function apply(object $subject, string $transitionName, array $context = []): Marking
51+
{
52+
return $this->callInner(__FUNCTION__, \func_get_args());
53+
}
54+
55+
public function getEnabledTransitions(object $subject): array
56+
{
57+
return $this->callInner(__FUNCTION__, \func_get_args());
58+
}
59+
60+
public function getName(): string
61+
{
62+
return $this->workflow->getName();
63+
}
64+
65+
public function getDefinition(): Definition
66+
{
67+
return $this->workflow->getDefinition();
68+
}
69+
70+
public function getMarkingStore(): MarkingStoreInterface
71+
{
72+
return $this->workflow->getMarkingStore();
73+
}
74+
75+
public function getMetadataStore(): MetadataStoreInterface
76+
{
77+
return $this->workflow->getMetadataStore();
78+
}
79+
80+
public function getCalls(): array
81+
{
82+
return $this->calls;
83+
}
84+
85+
private function callInner(string $method, array $args): mixed
86+
{
87+
$sMethod = $this->workflow::class.'::'.$method;
88+
$this->stopwatch->start($sMethod, 'workflow');
89+
90+
$previousMarking = null;
91+
if ('apply' === $method) {
92+
try {
93+
$previousMarking = $this->workflow->getMarking($args[0]);
94+
} catch (\Throwable) {
95+
}
96+
}
97+
98+
try {
99+
$return = $this->workflow->{$method}(...$args);
100+
101+
$this->calls[] = [
102+
'method' => $method,
103+
'duration' => $this->stopwatch->stop($sMethod)->getDuration(),
104+
'args' => $args,
105+
'previousMarking' => $previousMarking ?? null,
106+
'return' => $return,
107+
];
108+
109+
return $return;
110+
} catch (\Throwable $exception) {
111+
$this->calls[] = [
112+
'method' => $method,
113+
'duration' => $this->stopwatch->stop($sMethod)->getDuration(),
114+
'args' => $args,
115+
'previousMarking' => $previousMarking ?? null,
116+
'exception' => $exception,
117+
];
118+
119+
throw $exception;
120+
}
121+
}
122+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Workflow\DependencyInjection;
13+
14+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Reference;
17+
use Symfony\Component\Workflow\Debug\TraceableWorkflow;
18+
19+
/**
20+
* Adds all configured security voters to the access decision manager.
21+
*
22+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
23+
*/
24+
class WorkflowDebugPass implements CompilerPassInterface
25+
{
26+
public function process(ContainerBuilder $container): void
27+
{
28+
foreach ($container->findTaggedServiceIds('workflow') as $id => $attributes) {
29+
$container->register("debug.{$id}", TraceableWorkflow::class)
30+
->setDecoratedService($id)
31+
->setArguments([
32+
new Reference("debug.{$id}.inner"),
33+
new Reference('debug.stopwatch'),
34+
]);
35+
}
36+
}
37+
}

Registry.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function has(object $subject, string $workflowName = null): bool
4141
return false;
4242
}
4343

44-
public function get(object $subject, string $workflowName = null): Workflow
44+
public function get(object $subject, string $workflowName = null): WorkflowInterface
4545
{
4646
$matched = [];
4747

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy