Passed
Branch main (b0ee7b)
by Gaetano
09:00
created

WorkflowServiceFacade   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 164
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 69
c 3
b 1
f 0
dl 0
loc 164
rs 10
wmc 24

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getWorkflowsDefinitions() 0 12 3
A addExecutor() 0 3 1
B triggerWorkflow() 0 50 11
A convertSignalMember() 0 4 1
A __construct() 0 8 1
A getValidWorkflowsDefinitionsForSignal() 0 26 5
A addDefinitionParser() 0 3 1
A __call() 0 4 1
1
<?php
2
3
namespace Kaliop\eZWorkflowEngineBundle\Core;
4
5
use Symfony\Component\Config\ConfigCache;
6
use Symfony\Component\Config\Resource\FileResource;
7
use Psr\Log\LoggerInterface;
8
use Kaliop\eZMigrationBundle\Core\MigrationService;
9
use Kaliop\eZMigrationBundle\API\Value\MigrationDefinition;
10
use Kaliop\eZMigrationBundle\API\Collection\MigrationDefinitionCollection;
11
use Kaliop\eZMigrationBundle\API\DefinitionParserInterface;
12
use Kaliop\eZMigrationBundle\API\ExecutorInterface;
13
use Kaliop\eZWorkflowEngineBundle\API\Value\WorkflowDefinition;
14
15
/**
16
 * @todo add phpdoc to help IDEs
17
 */
18
class WorkflowServiceFacade
19
{
20
    protected $innerService;
21
    protected $cacheDir;
22
    protected $debugMode;
23
    protected $logger;
24
    protected $recursionLimit;
25
    protected static $workflowExecuting = 0;
26
27
    public function __construct(MigrationService $innerService, $recursionLimit, $cacheDir, $debugMode = false,
28
        LoggerInterface $logger = null)
29
    {
30
        $this->innerService = $innerService;
31
        $this->recursionLimit = $recursionLimit;
32
        $this->cacheDir = $cacheDir;
33
        $this->debugMode = $debugMode;
34
        $this->logger = $logger;
35
    }
36
37
    /**
38
     * q: should this method be moved to WorkflowService instead of the Slot ?
39
     *
40
     * @param string $signalName must use the same format as we extract from signal class names
41
     * @param array $signalParameters must follow what is found in eZ5 signals
42
     * @throws \Exception
43
     */
44
    public function triggerWorkflow($signalName, array $signalParameters)
45
    {
46
        $workflowDefinitions = $this->getValidWorkflowsDefinitionsForSignal($signalName);
47
48
        if ($this->logger) $this->logger->debug("Found " . count($workflowDefinitions) . " workflow definitions for signal '$signalName'");
49
50
        if (count($workflowDefinitions)) {
51
52
            $workflowParameters = array();
53
            foreach($signalParameters as $parameter => $value) {
54
                $convertedParameter = $this->convertSignalMember($signalName, $parameter);
55
                $workflowParameters['signal:' . $convertedParameter] = $value;
56
            }
57
58
            /** @var WorkflowDefinition $workflowDefinition */
59
            foreach ($workflowDefinitions as $workflowDefinition) {
60
61
                if (self::$workflowExecuting > 0 && $workflowDefinition->avoidRecursion) {
62
                    if ($this->logger) $this->logger->debug("Skipping workflow '{$workflowDefinition->name}' to avoid recursion (workflow already executing)");
63
                    return;
64
                }
65
66
                if (self::$workflowExecuting >= $this->recursionLimit) {
67
                    throw new \Exception("Workflow execution halted as we reached {$this->recursionLimit} nested workflows");
68
                }
69
70
                $wfd = new WorkflowDefinition(
71
                    $workflowDefinition->name,
72
                    $workflowDefinition->path,
73
                    $workflowDefinition->rawDefinition,
74
                    $workflowDefinition->status,
75
                    $workflowDefinition->steps->getArrayCopy(),
76
                    null,
77
                    $signalName,
78
                    $workflowDefinition->runAs,
79
                    $workflowDefinition->useTransaction,
80
                    $workflowDefinition->avoidRecursion
81
                );
82
83
                self::$workflowExecuting += 1;
84
                try {
85
86
                    if ($this->logger) $this->logger->debug("Executing workflow '{$workflowDefinition->name}' with parameters: " . preg_replace("/\n+/s", ' ', preg_replace('/^(Array| +|\(|\))/m', '', print_r($workflowParameters, true))));
87
88
                    /// @todo allow setting of default lang ?
89
                    $this->innerService->executeMigration($wfd, $workflowDefinition->useTransaction, null, $workflowDefinition->runAs, false, null, $workflowParameters);
0 ignored issues
show
Unused Code introduced by
The call to Kaliop\eZMigrationBundle...ice::executeMigration() has too many arguments starting with $workflowParameters. ( Ignorable by Annotation )

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

89
                    $this->innerService->/** @scrutinizer ignore-call */ 
90
                                         executeMigration($wfd, $workflowDefinition->useTransaction, null, $workflowDefinition->runAs, false, null, $workflowParameters);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
90
                    self::$workflowExecuting -= 1;
91
                } catch (\Exception $e) {
92
                    self::$workflowExecuting -= 1;
93
                    throw $e;
94
                }
95
            }
96
        }
97
    }
98
99
    /**
100
     * @param string $signalName
101
     * @param string $parameter
102
     * @return string
103
     */
104
    protected function convertSignalMember($signalName, $parameter)
105
    {
106
        // CamelCase to snake_case using negative look-behind in regexp
107
        return strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $parameter));
108
    }
109
110
    /**
111
     * Unlike its parent's similar function, this one only deals with *parsed* definitions.
112
     * NB: this function, unlike getValidWorkflowsDefinitionsForSignal, does not cache its results, which might lead to
113
     * some hard-to troubleshoot weirdness...
114
     * @param string[] $paths
115
     * @return MigrationDefinitionCollection
116
     */
117
    public function getWorkflowsDefinitions($paths = array())
118
    {
119
        $defs = array();
120
121
        foreach($this->innerService->getMigrationsDefinitions() as $key => $definition) {
122
            if ($definition->status == MigrationDefinition::STATUS_TO_PARSE) {
123
                $definition = $this->innerService->parseMigrationDefinition($definition);
124
            }
125
            $defs[$key] = $definition;
126
        }
127
128
        return new MigrationDefinitionCollection($defs);
129
    }
130
131
    /**
132
     * Returns VALID definitions for a given signal.
133
     * Uses the Sf cache to speed up the process (recipe taken from http://symfony.com/doc/2.7/components/config/caching.html)
134
     * @param $signalName
135
     * @param string[] $paths
136
     * @return MigrationDefinitionCollection
137
     */
138
    public function getValidWorkflowsDefinitionsForSignal($signalName, $paths = array())
139
    {
140
        $cacheFile = $this->cacheDir . '/' . md5($signalName) . '/' . md5(serialize($paths)) . '.php';
141
142
        $cache = new ConfigCache($cacheFile, $this->debugMode);
143
        if ($cache->isFresh()) {
144
            return require $cacheFile;
145
        }
146
147
        $defs = array();
148
        $resources = array();
149
150
        foreach($this->getWorkflowsDefinitions($paths) as $key => $definition) {
151
            /// @todo add safety check that we got back in fact a WorkflowDefinition
152
            if ($definition->signalName === $signalName && $definition->status == MigrationDefinition::STATUS_PARSED) {
153
                $defs[$key] = $definition;
154
                $resources[] = new FileResource($definition->path);
155
            }
156
        }
157
158
        $collection = new MigrationDefinitionCollection($defs);
159
160
        $code = '<?php return '.var_export($collection, true).';';
161
        $cache->write($code, $resources);
162
163
        return $collection;
164
    }
165
166
    public function __call($name, array $arguments)
167
    {
168
        $name = str_replace('Workflow', 'Migration', $name);
169
        return call_user_func_array(array($this->innerService, $name), $arguments);
170
    }
171
172
    // try to keep all Sf versions happy: some do apparently complain if this method is only available via __call
173
    public function addDefinitionParser(DefinitionParserInterface $executor)
174
    {
175
        return $this->innerService->addDefinitionParser($executor);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->innerService->add...nitionParser($executor) targeting Kaliop\eZMigrationBundle...::addDefinitionParser() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
176
    }
177
178
    // same
179
    public function addExecutor(ExecutorInterface $executor)
180
    {
181
        return $this->innerService->addExecutor($executor);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->innerService->addExecutor($executor) targeting Kaliop\eZMigrationBundle...nService::addExecutor() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
182
    }
183
}
184