Completed
Push — master ( 18ec14...287c96 )
by Kamil
14:09
created

Coordinator   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 251
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 27
lcom 1
cbo 10
dl 0
loc 251
rs 10
c 1
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A start() 0 14 2
A display() 0 18 3
A forward() 0 18 3
B processStepResult() 0 29 6
A registerScenario() 0 10 2
A loadScenario() 0 8 2
B redirectToStepDisplayAction() 0 31 4
A goToLastValidStep() 0 20 3
A buildProcess() 0 7 1
1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
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 Sylius\Bundle\FlowBundle\Process\Coordinator;
13
14
use FOS\RestBundle\View\View;
15
use Sylius\Bundle\FlowBundle\Process\Builder\ProcessBuilderInterface;
16
use Sylius\Bundle\FlowBundle\Process\Context\ProcessContextInterface;
17
use Sylius\Bundle\FlowBundle\Process\ProcessInterface;
18
use Sylius\Bundle\FlowBundle\Process\Scenario\ProcessScenarioInterface;
19
use Sylius\Bundle\FlowBundle\Process\Step\ActionResult;
20
use Sylius\Bundle\FlowBundle\Process\Step\StepInterface;
21
use Symfony\Component\HttpFoundation\ParameterBag;
22
use Symfony\Component\HttpFoundation\RedirectResponse;
23
use Symfony\Component\HttpFoundation\Response;
24
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
25
use Symfony\Component\Routing\RouterInterface;
26
27
/**
28
 * Default coordinator implementation.
29
 *
30
 * @author Paweł Jędrzejewski <[email protected]>
31
 */
32
class Coordinator implements CoordinatorInterface
33
{
34
    /**
35
     * Router.
36
     *
37
     * @var RouterInterface
38
     */
39
    protected $router;
40
41
    /**
42
     * Process builder.
43
     *
44
     * @var ProcessBuilderInterface
45
     */
46
    protected $builder;
47
48
    /**
49
     * Process context.
50
     *
51
     * @var ProcessContextInterface
52
     */
53
    protected $context;
54
55
    /**
56
     * Registered scenarios.
57
     *
58
     * @var array
59
     */
60
    protected $scenarios = array();
61
62
    /**
63
     * Constructor.
64
     *
65
     * @param RouterInterface         $router
66
     * @param ProcessBuilderInterface $builder
67
     * @param ProcessContextInterface $context
68
     */
69
    public function __construct(RouterInterface $router, ProcessBuilderInterface $builder, ProcessContextInterface $context)
70
    {
71
        $this->router = $router;
72
        $this->builder = $builder;
73
        $this->context = $context;
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    public function start($scenarioAlias, ParameterBag $queryParameters = null)
80
    {
81
        $process = $this->buildProcess($scenarioAlias);
82
        $step = $process->getFirstStep();
83
84
        $this->context->initialize($process, $step);
85
        $this->context->close();
86
87
        if (!$this->context->isValid()) {
88
            return $this->processStepResult($process, $this->context->getProcess()->getValidator()->getResponse($step));
89
        }
90
91
        return $this->redirectToStepDisplayAction($process, $step, $queryParameters);
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     */
97
    public function display($scenarioAlias, $stepName, ParameterBag $queryParameters = null)
98
    {
99
        $process = $this->buildProcess($scenarioAlias);
100
        $step = $process->getStepByName($stepName);
101
102
        $this->context->initialize($process, $step);
103
104
        try {
105
            $this->context->rewindHistory();
106
        } catch (NotFoundHttpException $e) {
107
            return $this->goToLastValidStep($process, $scenarioAlias);
108
        }
109
110
        return $this->processStepResult(
111
            $process,
112
            $this->context->isValid() ? $step->displayAction($this->context) : $this->context->getProcess()->getValidator()->getResponse($step)
113
        );
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function forward($scenarioAlias, $stepName)
120
    {
121
        $process = $this->buildProcess($scenarioAlias);
122
        $step = $process->getStepByName($stepName);
123
124
        $this->context->initialize($process, $step);
125
126
        try {
127
            $this->context->rewindHistory();
128
        } catch (NotFoundHttpException $e) {
129
            return $this->goToLastValidStep($process, $scenarioAlias);
130
        }
131
132
        return $this->processStepResult(
133
            $process,
134
            $this->context->isValid() ? $step->forwardAction($this->context) : $this->context->getProcess()->getValidator()->getResponse($step)
135
        );
136
    }
137
138
    /**
139
     * @param ProcessInterface $process
140
     * @param $result
141
     *
142
     * @return RedirectResponse
143
     */
144
    public function processStepResult(ProcessInterface $process, $result)
145
    {
146
        if ($result instanceof Response || $result instanceof View) {
147
            return $result;
148
        }
149
150
        if ($result instanceof ActionResult) {
151
            // Handle explicit jump to step.
152
            if ($result->getNextStepName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result->getNextStepName() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
153
                $this->context->setNextStepByName($result->getNextStepName());
154
155
                return $this->redirectToStepDisplayAction($process, $this->context->getNextStep());
156
            }
157
158
            // Handle last step.
159
            if ($this->context->isLastStep()) {
160
                $this->context->close();
161
162
                return new RedirectResponse(
163
                    $this->router->generate($process->getRedirect(), $process->getRedirectParams())
164
                );
165
            }
166
167
            // Handle default linear behaviour.
168
            return $this->redirectToStepDisplayAction($process, $this->context->getNextStep());
169
        }
170
171
        throw new \RuntimeException('Wrong action result, expected Response or ActionResult');
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177
    public function registerScenario($alias, ProcessScenarioInterface $scenario)
178
    {
179
        if (isset($this->scenarios[$alias])) {
180
            throw new InvalidArgumentException(
181
                sprintf('Process scenario with alias "%s" is already registered', $alias)
182
            );
183
        }
184
185
        $this->scenarios[$alias] = $scenario;
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     */
191
    public function loadScenario($alias)
192
    {
193
        if (!isset($this->scenarios[$alias])) {
194
            throw new InvalidArgumentException(sprintf('Process scenario with alias "%s" is not registered', $alias));
195
        }
196
197
        return $this->scenarios[$alias];
198
    }
199
200
    /**
201
     * Redirect to step display action.
202
     *
203
     * @param ProcessInterface $process
204
     * @param StepInterface    $step
205
     * @param ParameterBag     $queryParameters
206
     *
207
     * @return RedirectResponse
208
     */
209
    protected function redirectToStepDisplayAction(
210
        ProcessInterface $process,
211
        StepInterface $step,
212
        ParameterBag $queryParameters = null
213
    ) {
214
        $this->context->addStepToHistory($step->getName());
215
216
        if (null !== $route = $process->getDisplayRoute()) {
217
            $url = $this->router->generate($route, array_merge(
218
                $process->getDisplayRouteParams(),
219
                array('stepName' => $step->getName()),
220
                $queryParameters ? $queryParameters->all() : array()
221
            ));
222
223
            return new RedirectResponse($url);
224
        }
225
226
        // Default parameters for display route
227
        $routeParameters = array(
228
            'scenarioAlias' => $process->getScenarioAlias(),
229
            'stepName'      => $step->getName(),
230
        );
231
232
        if (null !== $queryParameters) {
233
            $routeParameters = array_merge($queryParameters->all(), $routeParameters);
234
        }
235
236
        return new RedirectResponse(
237
            $this->router->generate('sylius_flow_display', $routeParameters)
238
        );
239
    }
240
241
    /**
242
     * @param ProcessInterface $process
243
     * @param string           $scenarioAlias
244
     *
245
     * @return RedirectResponse
246
     */
247
    protected function goToLastValidStep(ProcessInterface $process, $scenarioAlias)
248
    {
249
        //the step we are supposed to display was not found in the history.
250
        if (null === $this->context->getPreviousStep()) {
251
            //there is no previous step go to start
252
            return $this->start($scenarioAlias);
253
        }
254
255
        //we will go back to previous step...
256
        $history = $this->context->getStepHistory();
257
        if (empty($history)) {
258
            //there is no history
259
            return $this->start($scenarioAlias);
260
        }
261
        $step = $process->getStepByName(end($history));
262
263
        $this->context->initialize($process, $step);
264
265
        return $this->redirectToStepDisplayAction($process, $step);
266
    }
267
268
    /**
269
     * Builds process for given scenario alias.
270
     *
271
     * @param string $scenarioAlias
272
     *
273
     * @return ProcessInterface
274
     */
275
    protected function buildProcess($scenarioAlias)
276
    {
277
        $process = $this->builder->build($this->loadScenario($scenarioAlias));
278
        $process->setScenarioAlias($scenarioAlias);
279
280
        return $process;
281
    }
282
}
283