Passed
Branch main (b6a268)
by Iain
04:11
created

Funnel   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 198
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 94
c 1
b 0
f 0
dl 0
loc 198
rs 9.84
wmc 32

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getEntityName() 0 8 2
A setSkipHandler() 0 5 1
A handleSuccess() 0 18 4
A getState() 0 15 4
A setEntity() 0 5 1
A setRepository() 0 5 1
A handleSkip() 0 18 4
B process() 0 39 6
A getStep() 0 18 4
A setSuccessHandler() 0 5 1
A addStep() 0 5 1
A createState() 0 7 1
A __construct() 0 4 1
A saveState() 0 4 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * Copyright Humbly Arrogant Ltd 2020-2022.
7
 *
8
 * Use of this software is governed by the Business Source License included in the LICENSE file and at https://getparthenon.com/docs/next/license.
9
 *
10
 * Change Date: TBD ( 3 years after 2.0.0 release )
11
 *
12
 * On the date above, in accordance with the Business Source License, use of this software will be governed by the open source license specified in the LICENSE file.
13
 */
14
15
namespace Parthenon\Funnel;
16
17
use Parthenon\Common\Exception\NoRepositorySetException;
18
use Parthenon\Common\LoggerAwareTrait;
19
use Parthenon\Common\Repository\RepositoryAwareInterface;
20
use Parthenon\Common\Repository\RepositoryInterface;
21
use Parthenon\Funnel\Exception\InvalidStepException;
22
use Parthenon\Funnel\Exception\NoEntitySetException;
23
use Parthenon\Funnel\Exception\NoSkipHandlerException;
24
use Parthenon\Funnel\Exception\NoSuccessHandlerSetException;
25
use Symfony\Component\Form\FormFactoryInterface;
26
use Symfony\Component\HttpFoundation\Request;
27
use Symfony\Component\HttpFoundation\RequestStack;
28
use Symfony\Component\HttpFoundation\Session\SessionInterface;
29
30
final class Funnel implements FunnelInterface
31
{
32
    use LoggerAwareTrait;
33
34
    /**
35
     * @var StepInterface[]
36
     */
37
    private array $steps = [];
38
    private $entity;
39
    private ?RepositoryInterface $repository;
40
    private FormFactoryInterface $formFactory;
41
    private SuccessHandlerInterface $successHandler;
42
    private SkipHandlerInterface $skipHandler;
43
44
    private SessionInterface $session;
45
46
    public function __construct(FormFactoryInterface $formFactory, RequestStack $requestStack)
47
    {
48
        $this->formFactory = $formFactory;
49
        $this->session = $requestStack->getSession();
50
    }
51
52
    public function setSkipHandler(SkipHandlerInterface $skipHandler): FunnelInterface
53
    {
54
        $this->skipHandler = $skipHandler;
55
56
        return $this;
57
    }
58
59
    public function setRepository(RepositoryInterface $repository): self
60
    {
61
        $this->repository = $repository;
62
63
        return $this;
64
    }
65
66
    public function setEntity($entity): FunnelInterface
67
    {
68
        $this->entity = $entity;
69
70
        return $this;
71
    }
72
73
    public function addStep(StepInterface $step): FunnelInterface
74
    {
75
        $this->steps[] = $step;
76
77
        return $this;
78
    }
79
80
    public function setSuccessHandler(SuccessHandlerInterface $successHandler): self
81
    {
82
        $this->successHandler = $successHandler;
83
84
        return $this;
85
    }
86
87
    public function process(Request $request)
88
    {
89
        if (!is_object($this->entity)) {
90
            $this->getLogger()->error('There is no error defined for funnel');
91
            throw new NoEntitySetException();
92
        }
93
94
        $newState = (null !== $request->get('clear', null) && $request->isMethod(Request::METHOD_GET));
95
96
        $funnelState = $this->getState($newState);
97
98
        $entity = $funnelState->getEntity();
99
100
        if (null !== $request->get('skip', null)) {
101
            return $this->handleSkip($entity);
102
        }
103
104
        $stepNumber = $funnelState->getStep();
105
        $step = $this->getStep($stepNumber);
106
107
        $this->getLogger()->info('Checking if step is complete', ['step' => $funnelState->getStep(), 'entity' => $this->getEntityName()]);
108
        if ($step->isComplete($request, $this->formFactory, $entity)) {
109
            ++$stepNumber;
110
            try {
111
                $step = $this->getStep($stepNumber);
112
                $funnelState->setStep($stepNumber);
113
            } catch (InvalidStepException $e) {
114
                $output = $this->handleSuccess($entity);
115
                $this->saveState($this->createState());
116
117
                return $output;
118
            }
119
        }
120
121
        $this->getLogger()->info('Getting output for step', ['step' => $funnelState->getStep(), 'entity' => $this->getEntityName()]);
122
        $output = $step->getOutput($request, $this->formFactory, $entity);
123
        $this->saveState($funnelState);
124
125
        return $output;
126
    }
127
128
    private function getStep(int $step): StepInterface
129
    {
130
        if (!isset($this->steps[$step])) {
131
            $this->getLogger()->error('Step not found.', ['step' => $step, 'entity' => $this->getEntityName()]);
132
            throw new InvalidStepException('Invalid step');
133
        }
134
135
        $step = $this->steps[$step];
136
137
        if ($step instanceof RepositoryAwareInterface) {
138
            if (null === $this->repository) {
139
                $this->getLogger()->error('There is no repository set to inject into step', ['entity' => $this->getEntityName()]);
140
                throw new NoRepositorySetException();
141
            }
142
            $step->setRepository($this->repository);
143
        }
144
145
        return $step;
146
    }
147
148
    private function handleSkip($entity)
149
    {
150
        if (!isset($this->skipHandler)) {
151
            $this->getLogger()->error('There is no skip handler set', ['entity' => $this->getEntityName()]);
152
            throw new NoSkipHandlerException();
153
        }
154
155
        if ($this->skipHandler instanceof RepositoryAwareInterface) {
156
            if (null === $this->repository) {
157
                $this->getLogger()->error('There is no repository set to inject into skip handler', ['entity' => $this->getEntityName()]);
158
                throw new NoRepositorySetException();
159
            }
160
            $this->skipHandler->setRepository($this->repository);
0 ignored issues
show
Bug introduced by
The method setRepository() does not exist on Parthenon\Funnel\SkipHandlerInterface. ( Ignorable by Annotation )

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

160
            $this->skipHandler->/** @scrutinizer ignore-call */ 
161
                                setRepository($this->repository);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
161
        }
162
163
        $this->getLogger()->info('Calling skip handler', ['entity' => $this->getEntityName()]);
164
165
        return $this->skipHandler->handleSkip($entity);
166
    }
167
168
    private function handleSuccess($entity)
169
    {
170
        if (!isset($this->successHandler)) {
171
            $this->getLogger()->error('There is no success handler set', ['entity' => $this->getEntityName()]);
172
            throw new NoSuccessHandlerSetException();
173
        }
174
175
        if ($this->successHandler instanceof RepositoryAwareInterface) {
176
            if (null === $this->repository) {
177
                $this->getLogger()->error('There is no repository set to inject into success handler', ['entity' => $this->getEntityName()]);
178
                throw new NoRepositorySetException();
179
            }
180
            $this->successHandler->setRepository($this->repository);
0 ignored issues
show
Bug introduced by
The method setRepository() does not exist on Parthenon\Funnel\SuccessHandlerInterface. ( Ignorable by Annotation )

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

180
            $this->successHandler->/** @scrutinizer ignore-call */ 
181
                                   setRepository($this->repository);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
181
        }
182
183
        $this->getLogger()->info('Calling success handler', ['entity' => $this->getEntityName()]);
184
185
        return $this->successHandler->handleSuccess($entity);
186
    }
187
188
    private function getState(bool $newState): FunnelState
189
    {
190
        $this->getLogger()->info('Fetching funnel state from session', ['entity' => $this->getEntityName()]);
191
        $state = $this->session->get(get_class($this->entity).'_funnel');
192
193
        if (!$state instanceof FunnelState || $newState) {
194
            if ($newState) {
195
                $this->getLogger()->info('Clear flag sent. Creating a new funnel state.', ['entity' => $this->getEntityName()]);
196
            } else {
197
                $this->getLogger()->info('No state found. Creating new state', ['entity' => $this->getEntityName()]);
198
            }
199
            $state = $this->createState();
200
        }
201
202
        return $state;
203
    }
204
205
    private function saveState(FunnelState $funnelState)
206
    {
207
        $this->getLogger()->info('Saving funnel state', ['entity' => get_class($this->entity)]);
208
        $this->session->set(get_class($this->entity).'_funnel', $funnelState);
209
    }
210
211
    private function getEntityName(): string
212
    {
213
        static $className;
214
        if (!$className) {
215
            $className = get_class($this->entity);
216
        }
217
218
        return $className;
219
    }
220
221
    private function createState(): FunnelState
222
    {
223
        $state = new FunnelState();
224
        $state->setEntity($this->entity)
225
            ->setStep(0);
226
227
        return $state;
228
    }
229
}
230