WorkflowServiceFacade::triggerWorkflow()   B
last analyzed

Complexity

Conditions 11
Paths 38

Size

Total Lines 55
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 35
nc 38
nop 2
dl 0
loc 55
rs 7.3166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Kaliop\eZWorkflowEngineBundle\Core;
4
5
use Kaliop\eZMigrationBundle\API\Collection\MigrationDefinitionCollection;
6
use Kaliop\eZMigrationBundle\API\DefinitionParserInterface;
7
use Kaliop\eZMigrationBundle\API\ExecutorInterface;
8
use Kaliop\eZMigrationBundle\API\Value\MigrationDefinition;
9
use Kaliop\eZMigrationBundle\Core\MigrationService;
10
use Kaliop\eZWorkflowEngineBundle\API\Value\WorkflowDefinition;
11
use Psr\Log\LoggerInterface;
12
use Symfony\Component\Config\ConfigCache;
13
use Symfony\Component\Config\Resource\FileResource;
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
     * @param string $signalName must use the same format as we extract from signal class names
39
     * @param array $signalParameters must follow what is found in eZ5 signals
40
     * @throws \Exception
41
     */
42
    public function triggerWorkflow($signalName, array $signalParameters)
43
    {
44
        $workflowDefinitions = $this->getValidWorkflowsDefinitionsForSignal($signalName);
45
46
        if ($this->logger) $this->logger->debug("Found " . count($workflowDefinitions) . " workflow definitions for signal '$signalName'");
47
48
        if (count($workflowDefinitions)) {
49
50
            $workflowParameters = array();
51
            foreach($signalParameters as $parameter => $value) {
52
                $convertedParameter = $this->convertSignalMember($signalName, $parameter);
53
                $workflowParameters['signal:' . $convertedParameter] = $value;
54
            }
55
56
            /** @var WorkflowDefinition $workflowDefinition */
57
            foreach ($workflowDefinitions as $workflowDefinition) {
58
59
                if (self::$workflowExecuting > 0 && $workflowDefinition->avoidRecursion) {
60
                    if ($this->logger) $this->logger->debug("Skipping workflow '{$workflowDefinition->name}' to avoid recursion (workflow already executing)");
61
                    return;
62
                }
63
64
                if (self::$workflowExecuting >= $this->recursionLimit) {
65
                    throw new \Exception("Workflow execution halted as we reached {$this->recursionLimit} nested workflows");
66
                }
67
68
                $wfd = new WorkflowDefinition(
69
                    $workflowDefinition->name,
70
                    $workflowDefinition->path,
71
                    $workflowDefinition->rawDefinition,
72
                    $workflowDefinition->status,
73
                    $workflowDefinition->steps->getArrayCopy(),
74
                    null,
75
                    $signalName,
76
                    $workflowDefinition->runAs,
77
                    $workflowDefinition->useTransaction,
78
                    $workflowDefinition->avoidRecursion
79
                );
80
81
                self::$workflowExecuting += 1;
82
                try {
83
84
                    if ($this->logger) $this->logger->debug("Executing workflow '{$workflowDefinition->name}' with parameters: " . preg_replace("/\n+/s", ' ', preg_replace('/^(Array| +|\(|\))/m', '', print_r($workflowParameters, true))));
85
86
                    /// @todo allow setting of default lang, user and userGroup content types ?
87
                    $this->innerService->executeMigration($wfd, array(
88
                        'useTransaction' => $workflowDefinition->useTransaction,
89
                        'adminUserLogin' => $workflowDefinition->runAs,
90
                        'workflow' => $workflowParameters,
91
                    ));
92
93
                    self::$workflowExecuting -= 1;
94
                } catch (\Exception $e) {
95
                    self::$workflowExecuting -= 1;
96
                    throw $e;
97
                }
98
            }
99
        }
100
    }
101
102
    /**
103
     * @param string $signalName
104
     * @param string $parameter
105
     * @return string
106
     */
107
    protected function convertSignalMember($signalName, $parameter)
108
    {
109
        // CamelCase to snake_case using negative look-behind in regexp
110
        return strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $parameter));
111
    }
112
113
    /**
114
     * Unlike its parent's similar function, this one only deals with *parsed* definitions.
115
     * NB: this function, unlike getValidWorkflowsDefinitionsForSignal, does not cache its results, which might lead to
116
     * some hard-to troubleshoot weirdness...
117
     * @param string[] $paths
118
     * @return MigrationDefinitionCollection
119
     */
120
    public function getWorkflowsDefinitions($paths = array())
121
    {
122
        $defs = array();
123
124
        foreach($this->innerService->getMigrationsDefinitions() as $key => $definition) {
125
            if ($definition->status == MigrationDefinition::STATUS_TO_PARSE) {
126
                $definition = $this->innerService->parseMigrationDefinition($definition);
127
            }
128
            $defs[$key] = $definition;
129
        }
130
131
        return new MigrationDefinitionCollection($defs);
132
    }
133
134
    /**
135
     * Returns VALID definitions for a given signal.
136
     * Uses the Sf cache to speed up the process (recipe taken from http://symfony.com/doc/2.7/components/config/caching.html)
137
     * @param $signalName
138
     * @param string[] $paths
139
     * @return MigrationDefinitionCollection
140
     */
141
    public function getValidWorkflowsDefinitionsForSignal($signalName, $paths = array())
142
    {
143
        $cacheFile = $this->cacheDir . '/' . md5($signalName) . '/' . md5(serialize($paths)) . '.php';
144
145
        $cache = new ConfigCache($cacheFile, $this->debugMode);
146
        if ($cache->isFresh()) {
147
            return require $cacheFile;
148
        }
149
150
        $defs = array();
151
        $resources = array();
152
153
        foreach($this->getWorkflowsDefinitions($paths) as $key => $definition) {
154
            /// @todo add safety check that we got back in fact a WorkflowDefinition
155
            if ($definition->signalName === $signalName && $definition->status == MigrationDefinition::STATUS_PARSED) {
156
                $defs[$key] = $definition;
157
                $resources[] = new FileResource($definition->path);
158
            }
159
        }
160
161
        $collection = new MigrationDefinitionCollection($defs);
162
163
        $code = '<?php return '.var_export($collection, true).';';
164
        $cache->write($code, $resources);
165
166
        return $collection;
167
    }
168
169
    public function __call($name, array $arguments)
170
    {
171
        $name = str_replace('Workflow', 'Migration', $name);
172
        return call_user_func_array(array($this->innerService, $name), $arguments);
173
    }
174
175
    // try to keep all Sf versions happy: some do apparently complain if this method is only available via __call
176
    public function addDefinitionParser(DefinitionParserInterface $executor)
177
    {
178
        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...
179
    }
180
181
    // same
182
    public function addExecutor(ExecutorInterface $executor)
183
    {
184
        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...
185
    }
186
}
187