Passed
Push — main ( 60a739...15ef8d )
by Iain
16:43
created

Funnel::isLiveEntity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * Copyright (C) 2020-2024 Iain Cambridge
7
 *
8
 * This program is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE as published by
10
 * the Free Software Foundation, either version 2.1 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20
 */
21
22
namespace Parthenon\Funnel;
23
24
use Parthenon\Common\Exception\NoRepositorySetException;
25
use Parthenon\Common\LoggerAwareTrait;
26
use Parthenon\Common\Repository\RepositoryAwareInterface;
27
use Parthenon\Common\Repository\RepositoryInterface;
28
use Parthenon\Funnel\Exception\InvalidStepException;
29
use Parthenon\Funnel\Exception\NoEntitySetException;
30
use Parthenon\Funnel\Exception\NoSkipHandlerException;
31
use Parthenon\Funnel\Exception\NoSuccessHandlerSetException;
32
use Symfony\Component\Form\FormFactoryInterface;
33
use Symfony\Component\HttpFoundation\Request;
34
use Symfony\Component\HttpFoundation\RequestStack;
35
use Symfony\Component\HttpFoundation\Session\SessionInterface;
36
37
final class Funnel implements FunnelInterface
38
{
39
    use LoggerAwareTrait;
40
41
    /**
42
     * @var StepInterface[]
43
     */
44
    private array $steps = [];
45
    private $entity;
46
    private ?RepositoryInterface $repository;
47
    private FormFactoryInterface $formFactory;
48
    private SuccessHandlerInterface $successHandler;
49
    private SkipHandlerInterface $skipHandler;
50
    private SessionInterface $session;
51
    private bool $isLiveEntity = false;
52
53
    public function __construct(FormFactoryInterface $formFactory, RequestStack $requestStack)
54
    {
55
        $this->formFactory = $formFactory;
56
        $this->session = $requestStack->getSession();
57
    }
58
59
    public function setSkipHandler(SkipHandlerInterface $skipHandler): FunnelInterface
60
    {
61
        $this->skipHandler = $skipHandler;
62
63
        return $this;
64
    }
65
66
    public function setRepository(RepositoryInterface $repository): self
67
    {
68
        $this->repository = $repository;
69
70
        return $this;
71
    }
72
73
    public function setEntity($entity): FunnelInterface
74
    {
75
        $this->entity = $entity;
76
77
        return $this;
78
    }
79
80
    public function addStep(StepInterface $step): FunnelInterface
81
    {
82
        $this->steps[] = $step;
83
84
        return $this;
85
    }
86
87
    public function setSuccessHandler(SuccessHandlerInterface $successHandler): self
88
    {
89
        $this->successHandler = $successHandler;
90
91
        return $this;
92
    }
93
94
    public function isLiveEntity(bool $preloaded): self
95
    {
96
        $this->isLiveEntity = $preloaded;
97
98
        return $this;
99
    }
100
101
    public function process(Request $request)
102
    {
103
        if (!is_object($this->entity)) {
104
            $this->getLogger()->error('There is no error defined for funnel');
105
            throw new NoEntitySetException();
106
        }
107
108
        $newState = (null !== $request->get('clear', null) && $request->isMethod(Request::METHOD_GET));
109
110
        $funnelState = $this->getState($newState);
111
112
        $entity = $this->isLiveEntity ? $this->entity : $funnelState->getEntity();
113
114
        if (null !== $request->get('skip', null)) {
115
            return $this->handleSkip($entity);
116
        }
117
118
        $stepNumber = $funnelState->getStep();
119
        $step = $this->getStep($stepNumber);
120
121
        $this->getLogger()->info('Checking if step is complete', ['step' => $funnelState->getStep(), 'entity' => $this->getEntityName()]);
122
        if ($step->isComplete($request, $this->formFactory, $entity)) {
123
            ++$stepNumber;
124
            try {
125
                $step = $this->getStep($stepNumber);
126
                $funnelState->setStep($stepNumber);
127
            } catch (InvalidStepException $e) {
128
                $output = $this->handleSuccess($entity);
129
                $this->saveState($this->createState());
130
131
                return $output;
132
            }
133
        }
134
135
        $this->getLogger()->info('Getting output for step', ['step' => $funnelState->getStep(), 'entity' => $this->getEntityName()]);
136
        $output = $step->getOutput($request, $this->formFactory, $entity);
137
        $this->saveState($funnelState);
138
139
        return $output;
140
    }
141
142
    private function getStep(int $step): StepInterface
143
    {
144
        if (!isset($this->steps[$step])) {
145
            $this->getLogger()->error('Step not found.', ['step' => $step, 'entity' => $this->getEntityName()]);
146
            throw new InvalidStepException('Invalid step');
147
        }
148
149
        $step = $this->steps[$step];
150
151
        if ($step instanceof RepositoryAwareInterface) {
152
            if (null === $this->repository) {
153
                $this->getLogger()->error('There is no repository set to inject into step', ['entity' => $this->getEntityName()]);
154
                throw new NoRepositorySetException();
155
            }
156
            $step->setRepository($this->repository);
157
        }
158
159
        return $step;
160
    }
161
162
    private function handleSkip($entity)
163
    {
164
        if (!isset($this->skipHandler)) {
165
            $this->getLogger()->error('There is no skip handler set', ['entity' => $this->getEntityName()]);
166
            throw new NoSkipHandlerException();
167
        }
168
169
        if ($this->skipHandler instanceof RepositoryAwareInterface) {
170
            if (null === $this->repository) {
171
                $this->getLogger()->error('There is no repository set to inject into skip handler', ['entity' => $this->getEntityName()]);
172
                throw new NoRepositorySetException();
173
            }
174
            $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

174
            $this->skipHandler->/** @scrutinizer ignore-call */ 
175
                                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...
175
        }
176
177
        $this->getLogger()->info('Calling skip handler', ['entity' => $this->getEntityName()]);
178
179
        return $this->skipHandler->handleSkip($entity);
180
    }
181
182
    private function handleSuccess($entity)
183
    {
184
        if (!isset($this->successHandler)) {
185
            $this->getLogger()->error('There is no success handler set', ['entity' => $this->getEntityName()]);
186
            throw new NoSuccessHandlerSetException();
187
        }
188
189
        if ($this->successHandler instanceof RepositoryAwareInterface) {
190
            if (null === $this->repository) {
191
                $this->getLogger()->error('There is no repository set to inject into success handler', ['entity' => $this->getEntityName()]);
192
                throw new NoRepositorySetException();
193
            }
194
            $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

194
            $this->successHandler->/** @scrutinizer ignore-call */ 
195
                                   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...
195
        }
196
197
        $this->getLogger()->info('Calling success handler', ['entity' => $this->getEntityName()]);
198
199
        return $this->successHandler->handleSuccess($entity);
200
    }
201
202
    private function getState(bool $newState): FunnelState
203
    {
204
        $this->getLogger()->info('Fetching funnel state from session', ['entity' => $this->getEntityName()]);
205
        $state = $this->session->get($this->entity->getId().'_funnel');
206
207
        if (!$state instanceof FunnelState || $newState) {
208
            if ($newState) {
209
                $this->getLogger()->info('Clear flag sent. Creating a new funnel state.', ['entity' => $this->getEntityName()]);
210
            } else {
211
                $this->getLogger()->info('No state found. Creating new state', ['entity' => $this->getEntityName()]);
212
            }
213
            $state = $this->createState();
214
        }
215
216
        return $state;
217
    }
218
219
    private function saveState(FunnelState $funnelState)
220
    {
221
        $this->getLogger()->info('Saving funnel state', ['entity' => get_class($this->entity)]);
222
        $this->session->set($this->entity->getId().'_funnel', $funnelState);
223
    }
224
225
    private function getEntityName(): string
226
    {
227
        static $className;
228
        if (!$className) {
229
            $className = get_class($this->entity);
230
        }
231
232
        return $className;
233
    }
234
235
    private function createState(): FunnelState
236
    {
237
        $state = new FunnelState();
238
        $state->setEntity($this->entity)
239
            ->setStep(0);
240
241
        return $state;
242
    }
243
}
244