Issues (3641)

Oms/src/Spryker/Zed/Oms/Business/Util/Drawer.php (1 issue)

1
<?php
2
3
/**
4
 * Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
5
 * Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
6
 */
7
8
namespace Spryker\Zed\Oms\Business\Util;
9
10
use Generated\Shared\Transfer\OmsEventTransfer;
11
use Spryker\Shared\Graph\GraphInterface;
12
use Spryker\Zed\Oms\Business\Exception\StatemachineException;
13
use Spryker\Zed\Oms\Business\Process\EventInterface;
14
use Spryker\Zed\Oms\Business\Process\ProcessInterface;
15
use Spryker\Zed\Oms\Business\Process\StateInterface;
16
use Spryker\Zed\Oms\Business\Process\TransitionInterface;
17
use Spryker\Zed\Oms\Communication\Plugin\Oms\Command\CommandCollection;
18
use Spryker\Zed\Oms\Communication\Plugin\Oms\Condition\ConditionCollection;
19
use Spryker\Zed\Oms\Dependency\Plugin\Command\CommandByOrderInterface;
20
use Spryker\Zed\Oms\Dependency\Plugin\Command\CommandCollectionInterface;
21
use Spryker\Zed\Oms\Dependency\Plugin\Condition\ConditionCollectionInterface;
22
use Spryker\Zed\Oms\Dependency\Plugin\Condition\HasAwareCollectionInterface;
23
use Spryker\Zed\Oms\Dependency\Service\OmsToUtilTextInterface;
24
25
class Drawer implements DrawerInterface
26
{
27
    /**
28
     * @var string
29
     */
30
    public const ATTRIBUTE_FONT_SIZE = 'fontsize';
31
32
    /**
33
     * @var string
34
     */
35
    public const EDGE_UPPER_HALF = 'upper half';
36
37
    /**
38
     * @var string
39
     */
40
    public const EDGE_LOWER_HALF = 'lower half';
41
42
    /**
43
     * @var string
44
     */
45
    public const EDGE_FULL = 'edge full';
46
47
    /**
48
     * @var array
49
     */
50
    protected $attributesProcess = ['fontname' => 'Verdana', 'fillcolor' => '#cfcfcf', 'style' => 'filled', 'color' => '#ffffff', 'fontsize' => 12, 'fontcolor' => 'black'];
51
52
    /**
53
     * @var array
54
     */
55
    protected $attributesState = ['fontname' => 'Verdana', 'fontsize' => 14, 'style' => 'filled', 'fillcolor' => '#f9f9f9'];
56
57
    /**
58
     * @var array
59
     */
60
    protected $attributesDiamond = ['fontname' => 'Verdana', 'label' => '?', 'shape' => 'diamond', 'fontcolor' => 'white', 'fontsize' => '1', 'style' => 'filled', 'fillcolor' => '#f9f9f9'];
61
62
    /**
63
     * @var array
64
     */
65
    protected $attributesTransition = ['fontname' => 'Verdana', 'fontsize' => 12];
66
67
    /**
68
     * @var string
69
     */
70
    protected $brLeft = '<br align="left" />  ';
71
72
    /**
73
     * @var string
74
     */
75
    protected $notImplemented = '<font color="red">(not implemented)</font>';
76
77
    /**
78
     * @var string
79
     */
80
    protected $br = '<br/>';
81
82
    /**
83
     * @var string
84
     */
85
    protected $format = 'svg';
86
87
    /**
88
     * @var int|null
89
     */
90
    protected $fontSizeBig;
91
92
    /**
93
     * @var int|null
94
     */
95
    protected $fontSizeSmall;
96
97
    /**
98
     * @var \Spryker\Zed\Oms\Dependency\Plugin\Condition\ConditionCollectionInterface
99
     */
100
    protected $conditions;
101
102
    /**
103
     * @var \Spryker\Zed\Oms\Dependency\Plugin\Command\CommandCollectionInterface
104
     */
105
    protected $commands;
106
107
    /**
108
     * @var \Spryker\Shared\Graph\GraphInterface
109
     */
110
    protected $graph;
111
112
    /**
113
     * @var \Spryker\Zed\Oms\Dependency\Service\OmsToUtilTextInterface
114
     */
115
    protected $utilTextService;
116
117
    /**
118
     * @var \Spryker\Zed\Oms\Business\Util\TimeoutProcessorCollectionInterface
119
     */
120
    protected $timeoutProcessorCollection;
121
122
    /**
123
     * @param \Spryker\Zed\Oms\Dependency\Plugin\Command\CommandCollectionInterface|array $commands
124
     * @param \Spryker\Zed\Oms\Dependency\Plugin\Condition\ConditionCollectionInterface|array $conditions
125
     * @param \Spryker\Shared\Graph\GraphInterface $graph
126
     * @param \Spryker\Zed\Oms\Dependency\Service\OmsToUtilTextInterface $utilTextService
127
     * @param \Spryker\Zed\Oms\Business\Util\TimeoutProcessorCollectionInterface $timeoutProcessorCollection
128
     */
129
    public function __construct(
130
        $commands,
131
        $conditions,
132
        GraphInterface $graph,
133
        OmsToUtilTextInterface $utilTextService,
134
        TimeoutProcessorCollectionInterface $timeoutProcessorCollection
135
    ) {
136
        $this->setCommands($commands);
137
        $this->setConditions($conditions);
138
139
        $this->graph = $graph;
140
        $this->utilTextService = $utilTextService;
141
        $this->timeoutProcessorCollection = $timeoutProcessorCollection;
142
    }
143
144
    /**
145
     * Converts array to collection for BC
146
     *
147
     * @param \Spryker\Zed\Oms\Dependency\Plugin\Condition\ConditionCollectionInterface|array $conditions
148
     *
149
     * @return void
150
     */
151
    protected function setConditions($conditions)
152
    {
153
        if ($conditions instanceof ConditionCollectionInterface) {
154
            $this->conditions = $conditions;
155
156
            return;
157
        }
158
159
        $conditionCollection = new ConditionCollection();
160
        foreach ($conditions as $name => $condition) {
161
            $conditionCollection->add($condition, $name);
162
        }
163
164
        $this->conditions = $conditionCollection;
165
    }
166
167
    /**
168
     * Converts array to collection for BC
169
     *
170
     * @param \Spryker\Zed\Oms\Dependency\Plugin\Command\CommandCollectionInterface|array $commands
171
     *
172
     * @return void
173
     */
174
    protected function setCommands($commands)
175
    {
176
        if ($commands instanceof CommandCollectionInterface) {
177
            $this->commands = $commands;
178
179
            return;
180
        }
181
182
        $commandCollection = new CommandCollection();
183
        foreach ($commands as $name => $command) {
184
            $commandCollection->add($command, $name);
185
        }
186
187
        $this->commands = $commandCollection;
188
    }
189
190
    /**
191
     * @param \Spryker\Zed\Oms\Business\Process\ProcessInterface $process
192
     * @param string|null $highlightState
193
     * @param string|null $format
194
     * @param int|null $fontSize
195
     *
196
     * @return string
197
     */
198
    public function draw(ProcessInterface $process, $highlightState = null, $format = null, $fontSize = null)
199
    {
200
        $this->init($format, $fontSize);
201
202
        $this->drawClusters($process);
203
        $this->drawStates($process, $highlightState);
204
        $this->drawTransitions($process);
205
206
        return $this->graph->render($this->format);
207
    }
208
209
    /**
210
     * @param \Spryker\Zed\Oms\Business\Process\ProcessInterface $process
211
     * @param string|null $highlightState
212
     *
213
     * @return void
214
     */
215
    public function drawStates(ProcessInterface $process, $highlightState = null)
216
    {
217
        $states = $process->getAllStates();
218
        foreach ($states as $state) {
219
            $isHighlighted = $highlightState === $state->getName();
220
            $this->addNode($state, [], null, $isHighlighted);
221
        }
222
    }
223
224
    /**
225
     * @param \Spryker\Zed\Oms\Business\Process\ProcessInterface $process
226
     *
227
     * @return void
228
     */
229
    public function drawTransitions(ProcessInterface $process)
230
    {
231
        $states = $process->getAllStates();
232
        foreach ($states as $state) {
233
            $this->drawTransitionsEvents($state);
234
            $this->drawTransitionsConditions($state);
235
        }
236
    }
237
238
    /**
239
     * @return string
240
     */
241
    protected function getDiamondId()
242
    {
243
        return $this->utilTextService->generateRandomString(32);
244
    }
245
246
    /**
247
     * @param \Spryker\Zed\Oms\Business\Process\StateInterface $state
248
     *
249
     * @throws \Spryker\Zed\Oms\Business\Exception\StatemachineException
250
     *
251
     * @return void
252
     */
253
    public function drawTransitionsEvents(StateInterface $state)
254
    {
255
        $events = $state->getEvents();
256
        foreach ($events as $event) {
257
            $transitions = $state->getOutgoingTransitionsByEvent($event);
258
259
            $currentTransition = current($transitions);
260
            if (!$currentTransition) {
261
                throw new StatemachineException('Transitions container seems to be empty.');
262
            }
263
264
            if (count($transitions) > 1) {
265
                $diamondId = $this->getDiamondId();
266
267
                $this->graph->addNode($diamondId, $this->attributesDiamond, $state->getProcess()->getName());
268
                $this->addEdge($currentTransition, static::EDGE_UPPER_HALF, [], null, $diamondId);
269
270
                foreach ($transitions as $transition) {
271
                    $this->addEdge($transition, static::EDGE_LOWER_HALF, [], $diamondId);
272
                }
273
            } else {
274
                $this->addEdge($currentTransition, static::EDGE_FULL);
275
            }
276
        }
277
    }
278
279
    /**
280
     * @param \Spryker\Zed\Oms\Business\Process\StateInterface $state
281
     *
282
     * @return void
283
     */
284
    public function drawTransitionsConditions(StateInterface $state)
285
    {
286
        $transitions = $state->getOutgoingTransitions();
287
        foreach ($transitions as $transition) {
288
            if ($transition->hasEvent()) {
289
                continue;
290
            }
291
            $this->addEdge($transition);
292
        }
293
    }
294
295
    /**
296
     * @param \Spryker\Zed\Oms\Business\Process\ProcessInterface $process
297
     *
298
     * @return void
299
     */
300
    public function drawClusters(ProcessInterface $process)
301
    {
302
        $processes = $process->getAllProcesses();
303
        foreach ($processes as $subProcess) {
304
            $group = $subProcess->getName();
305
            $attributes = $this->attributesProcess;
306
            $attributes['label'] = $group;
307
308
            $this->graph->addCluster($group, $attributes);
309
        }
310
    }
311
312
    /**
313
     * @param \Spryker\Zed\Oms\Business\Process\StateInterface $state
314
     * @param array $attributes
315
     * @param string|null $name
316
     * @param bool $highlighted
317
     *
318
     * @return void
319
     */
320
    protected function addNode(StateInterface $state, $attributes = [], $name = null, $highlighted = false)
321
    {
322
        $name = $name ?? $state->getName();
323
324
        $label = [];
325
        $label[] = str_replace(' ', $this->br, trim($name));
326
327
        if ($state->isReserved()) {
328
            $label[] = '<font color="blue" point-size="' . $this->fontSizeSmall . '">' . 'reserved' . '</font>';
329
        }
330
331
        if ($state->hasFlags()) {
332
            $flags = implode(', ', $state->getFlags());
333
            $label[] = '<font color="violet" point-size="' . $this->fontSizeSmall . '">' . $flags . '</font>';
334
        }
335
336
        $attributes['label'] = implode($this->br, $label);
337
338
        if (!$state->hasOutgoingTransitions() || $this->hasOnlySelfReferences($state)) {
339
            $attributes['peripheries'] = 2;
340
        }
341
342
        if ($highlighted) {
343
            $attributes['fillcolor'] = '#FFFFCC';
344
        }
345
346
        $attributes = array_merge($this->attributesState, $attributes);
347
        $this->graph->addNode($name, $attributes, $state->getProcess()->getName());
348
    }
349
350
    /**
351
     * @param \Spryker\Zed\Oms\Business\Process\StateInterface $state
352
     *
353
     * @return bool
354
     */
355
    protected function hasOnlySelfReferences(StateInterface $state)
356
    {
357
        $hasOnlySelfReferences = true;
358
        $transitions = $state->getOutgoingTransitions();
359
        foreach ($transitions as $transition) {
360
            if ($transition->getTarget()->getName() !== $state->getName()) {
361
                $hasOnlySelfReferences = false;
362
363
                break;
364
            }
365
        }
366
367
        return $hasOnlySelfReferences;
368
    }
369
370
    /**
371
     * @param \Spryker\Zed\Oms\Business\Process\TransitionInterface $transition
372
     * @param string $type
373
     * @param array $attributes
374
     * @param string|null $fromName
375
     * @param string|null $toName
376
     *
377
     * @return void
378
     */
379
    protected function addEdge(TransitionInterface $transition, $type = self::EDGE_FULL, $attributes = [], $fromName = null, $toName = null)
380
    {
381
        $label = [];
382
383
        if ($type !== static::EDGE_LOWER_HALF) {
384
            $label = $this->addEdgeEventText($transition, $label);
385
        }
386
387
        if ($type !== static::EDGE_UPPER_HALF) {
388
            $label = $this->addEdgeConditionText($transition, $label);
389
        }
390
391
        $label = $this->addEdgeElse($label);
392
        $fromName = $this->addEdgeFromState($transition, $fromName);
393
        $toName = $this->addEdgeToState($transition, $toName);
394
        $attributes = $this->addEdgeAttributes($transition, $attributes, $label, $type);
395
396
        $this->graph->addEdge($fromName, $toName, $attributes);
397
    }
398
399
    /**
400
     * @param \Spryker\Zed\Oms\Business\Process\TransitionInterface $transition
401
     * @param array<string> $label
402
     *
403
     * @return array<string>
404
     */
405
    protected function addEdgeConditionText(TransitionInterface $transition, $label)
406
    {
407
        if ($transition->hasCondition()) {
408
            $conditionLabel = $transition->getCondition();
409
410
            if (!$this->inCollection($this->conditions, $transition->getCondition())) {
411
                $conditionLabel .= ' ' . $this->notImplemented;
412
            }
413
414
            $label[] = $conditionLabel;
415
        }
416
417
        return $label;
418
    }
419
420
    /**
421
     * @param \Spryker\Zed\Oms\Business\Process\TransitionInterface $transition
422
     * @param array<string> $label
423
     *
424
     * @return array<string>
425
     */
426
    protected function addEdgeEventText(TransitionInterface $transition, $label)
427
    {
428
        if ($transition->hasEvent()) {
429
            $event = $transition->getEvent();
430
431
            if ($event->isOnEnter()) {
432
                $label[] = '<b>' . $event->getName() . ' (on enter)</b>';
433
            } else {
434
                $label[] = '<b>' . $event->getName() . '</b>';
435
            }
436
437
            if ($event->hasTimeout()) {
438
                $label[] = 'timeout: ' . $event->getTimeout();
439
            }
440
441
            if ($event->hasCommand()) {
442
                $commandLabel = 'c:' . $event->getCommand();
443
444
                if ($this->inCollection($this->commands, $event->getCommand())) {
445
                    $commandModel = $this->commands->get($event->getCommand());
446
                    if ($commandModel instanceof CommandByOrderInterface) {
447
                        $commandLabel .= ' (by order)';
448
                    } else {
449
                        $commandLabel .= ' (by item)';
450
                    }
451
                } else {
452
                    $commandLabel .= ' ' . $this->notImplemented;
453
                }
454
                $label[] = $commandLabel;
455
            }
456
457
            if ($event->hasTimeoutProcessor()) {
458
                $label[] = sprintf('timeout processor: %s', $this->getTimeoutProcessorLabel($event));
459
            }
460
461
            if ($event->isManual()) {
462
                $label[] = 'manually executable';
463
            }
464
        } else {
465
            $label[] = '&infin;';
466
        }
467
468
        return $label;
469
    }
470
471
    /**
472
     * @param \Spryker\Zed\Oms\Dependency\Plugin\Condition\HasAwareCollectionInterface|mixed $collection
473
     * @param string $commandName
474
     *
475
     * @return bool
476
     */
477
    protected function inCollection($collection, $commandName)
478
    {
479
        if ($collection instanceof HasAwareCollectionInterface) {
480
            return $collection->has($commandName);
481
        }
482
483
        return false;
484
    }
485
486
    /**
487
     * @param array<string> $label
488
     *
489
     * @return string
490
     */
491
    protected function addEdgeElse($label)
492
    {
493
        if ($label) {
494
            $label = implode($this->brLeft, $label);
495
        } else {
496
            $label = 'else';
497
        }
498
499
        return $label;
500
    }
501
502
    /**
503
     * @param \Spryker\Zed\Oms\Business\Process\TransitionInterface $transition
504
     * @param array $attributes
505
     * @param string $label
506
     * @param string $type
507
     *
508
     * @return array
509
     */
510
    protected function addEdgeAttributes(TransitionInterface $transition, array $attributes, $label, $type = self::EDGE_FULL)
511
    {
512
        $attributes = array_merge($this->attributesTransition, $attributes);
513
        $attributes['label'] = '  ' . $label;
514
515
        if ($transition->hasEvent() === false) {
516
            $attributes['style'] = 'dashed';
517
        }
518
519
        if ($type === static::EDGE_FULL || $type === static::EDGE_UPPER_HALF) {
520
            if ($transition->hasEvent() && $transition->getEvent()->isOnEnter()) {
521
                $attributes['arrowtail'] = 'crow';
522
                $attributes['dir'] = 'both';
523
            }
524
        }
525
526
        if ($transition->isHappy()) {
527
            $attributes['weight'] = '100';
528
            $attributes['color'] = '#70ab28'; // TODO eindeutig?
529
        } elseif ($transition->hasEvent()) {
530
            $attributes['weight'] = '10';
531
        } else {
532
            $attributes['weight'] = '1';
533
        }
534
535
        return $attributes;
536
    }
537
538
    /**
539
     * @param \Spryker\Zed\Oms\Business\Process\TransitionInterface $transition
540
     * @param string|null $fromName
541
     *
542
     * @return string
543
     */
544
    protected function addEdgeFromState(TransitionInterface $transition, $fromName)
545
    {
546
        $fromName = $fromName ?? $transition->getSource()->getName();
547
548
        return $fromName;
549
    }
550
551
    /**
552
     * @param \Spryker\Zed\Oms\Business\Process\TransitionInterface $transition
553
     * @param string|null $toName
554
     *
555
     * @return string
556
     */
557
    protected function addEdgeToState(TransitionInterface $transition, $toName)
558
    {
559
        $toName = $toName ?? $transition->getTarget()->getName();
560
561
        return $toName;
562
    }
563
564
    /**
565
     * @param string|null $format
566
     * @param int|null $fontSize
567
     *
568
     * @return void
569
     */
570
    protected function init($format, $fontSize)
571
    {
572
        if ($format !== null) {
573
            $this->format = $format;
574
        }
575
576
        if ($fontSize !== null) {
577
            $this->attributesState[static::ATTRIBUTE_FONT_SIZE] = $fontSize;
578
            $this->attributesProcess[static::ATTRIBUTE_FONT_SIZE] = $fontSize - 2;
579
            $this->attributesTransition[static::ATTRIBUTE_FONT_SIZE] = $fontSize - 2;
580
            $this->fontSizeBig = $fontSize;
581
            $this->fontSizeSmall = $fontSize - 2;
582
        }
583
    }
584
585
    /**
586
     * @param \Spryker\Zed\Oms\Business\Process\EventInterface $event
587
     *
588
     * @return string
589
     */
590
    protected function getTimeoutProcessorLabel(EventInterface $event): string
591
    {
592
        if (!$this->inCollection($this->timeoutProcessorCollection, $event->getTimeoutProcessor())) {
593
            return $this->notImplemented;
594
        }
595
596
        $timeoutProcessor = $this->timeoutProcessorCollection->get($event->getTimeoutProcessor());
0 ignored issues
show
It seems like $event->getTimeoutProcessor() can also be of type null; however, parameter $name of Spryker\Zed\Oms\Business...lectionInterface::get() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

596
        $timeoutProcessor = $this->timeoutProcessorCollection->get(/** @scrutinizer ignore-type */ $event->getTimeoutProcessor());
Loading history...
597
        $omsEventTransfer = (new OmsEventTransfer())->setTimeout($event->getTimeout());
598
599
        return $timeoutProcessor->getLabel($omsEventTransfer);
600
    }
601
}
602