Completed
Push — wip/steps ( eae10b...a8cc5a )
by Romain
02:22
created

AbstractMiddleware::dataPreProcessor()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 25
rs 8.439
cc 5
eloc 13
nc 16
nop 1
1
<?php
2
/*
3
 * 2017 Romain CANON <[email protected]>
4
 *
5
 * This file is part of the TYPO3 FormZ project.
6
 * It is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU General Public License, either
8
 * version 3 of the License, or any later version.
9
 *
10
 * For the full copyright and license information, see:
11
 * http://www.gnu.org/licenses/gpl-3.0.html
12
 */
13
14
namespace Romm\Formz\Middleware\Item;
15
16
use Romm\ConfigurationObject\Service\Items\DataPreProcessor\DataPreProcessor;
17
use Romm\ConfigurationObject\Service\Items\DataPreProcessor\DataPreProcessorInterface;
18
use Romm\Formz\Exceptions\InvalidArgumentValueException;
19
use Romm\Formz\Exceptions\InvalidEntryException;
20
use Romm\Formz\Exceptions\MissingArgumentException;
21
use Romm\Formz\Exceptions\SignalNotFoundException;
22
use Romm\Formz\Form\Definition\Step\Step\Step;
23
use Romm\Formz\Form\Definition\Middleware\MiddlewareScopes;
24
use Romm\Formz\Form\FormObject\FormObject;
25
use Romm\Formz\Middleware\Item\Step\Service\StepMiddlewareService;
26
use Romm\Formz\Middleware\MiddlewareInterface;
27
use Romm\Formz\Middleware\Option\AbstractOptionDefinition;
28
use Romm\Formz\Middleware\Option\OptionInterface;
29
use Romm\Formz\Middleware\Processor\MiddlewareProcessor;
30
use Romm\Formz\Middleware\Request\Forward;
31
use Romm\Formz\Middleware\Request\Redirect;
32
use Romm\Formz\Middleware\Scope\MainScope;
33
use Romm\Formz\Middleware\Signal\After;
34
use Romm\Formz\Middleware\Signal\Before;
35
use Romm\Formz\Middleware\Signal\MiddlewareSignal;
36
use Romm\Formz\Middleware\Signal\SendsMiddlewareSignal;
37
use Romm\Formz\Middleware\Signal\SignalObject;
38
use TYPO3\CMS\Extbase\Mvc\Controller\Arguments;
39
use TYPO3\CMS\Extbase\Mvc\Web\Request;
40
41
/**
42
 * Abstract class that must be extended by middlewares.
43
 *
44
 * Child middleware must implement their own signals.
45
 */
46
abstract class AbstractMiddleware implements MiddlewareInterface, DataPreProcessorInterface
47
{
48
    /**
49
     * @var MiddlewareProcessor
50
     */
51
    private $processor;
52
53
    /**
54
     * This is the default option class, this property can be overridden in
55
     * child classes to be mapped to another option definition.
56
     *
57
     * @var \Romm\Formz\Middleware\Option\DefaultOptionDefinition
58
     */
59
    protected $options;
60
61
    /**
62
     * @var \Romm\Formz\Form\Definition\Middleware\MiddlewareScopes
63
     */
64
    protected $scopes = [];
65
66
    /**
67
     * @var array
68
     */
69
    protected static $defaultScopesWhiteList = [];
70
71
    /**
72
     * @var array
73
     */
74
    protected static $defaultScopesBlackList = [];
75
76
    /**
77
     * Can be overridden in child class with custom priority value.
78
     *
79
     * The higher the priority is, the earlier the middleware is called.
80
     *
81
     * Note that you can also override the method `getPriority()` for advanced
82
     * priority calculation.
83
     *
84
     * @var int
85
     */
86
    protected $priority = 0;
87
88
    /**
89
     * @param OptionInterface  $options
90
     * @param MiddlewareScopes $scopes
91
     */
92
    final public function __construct(OptionInterface $options, MiddlewareScopes $scopes)
93
    {
94
        $this->options = $options;
0 ignored issues
show
Documentation Bug introduced by
$options is of type object<Romm\Formz\Middle...Option\OptionInterface>, but the property $options was declared to be of type object<Romm\Formz\Middle...efaultOptionDefinition>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
95
        $this->scopes = $scopes;
96
    }
97
98
    /**
99
     * Abstraction for processing the middleware initialization.
100
     *
101
     * For own initialization, @see initializeMiddleware()
102
     */
103
    final public function initialize()
104
    {
105
        $this->initializeMiddleware();
106
    }
107
108
    /**
109
     * You can override this method in your child class to initialize your
110
     * middleware correctly.
111
     */
112
    protected function initializeMiddleware()
113
    {
114
    }
115
116
    /**
117
     * @see \Romm\Formz\Middleware\Signal\SendsMiddlewareSignal::beforeSignal()
118
     *
119
     * @param string $signal
120
     * @return SignalObject
121
     */
122
    final public function beforeSignal($signal = null)
123
    {
124
        return $this->getSignalObject($signal, Before::class);
125
    }
126
127
    /**
128
     * @see \Romm\Formz\Middleware\Signal\SendsMiddlewareSignal::afterSignal()
129
     *
130
     * @param string $signal
131
     * @return SignalObject
132
     */
133
    final public function afterSignal($signal = null)
134
    {
135
        return $this->getSignalObject($signal, After::class);
136
    }
137
138
    /**
139
     * @return AbstractOptionDefinition
140
     */
141
    public function getOptions()
142
    {
143
        return $this->options;
144
    }
145
146
    /**
147
     * @todo
148
     */
149
    protected function redirectToNextStep()
150
    {
151
        $formObject = $this->getFormObject();
152
153
        if ($formObject->hasForm()
154
            && $formObject->isPersistent()
155
        ) {
156
            $formObject->getFormMetadata()->persist();
157
        }
158
159
        $service = StepMiddlewareService::get();
160
        $nextStep = $service->getNextStep($this->getCurrentStep());
161
162
        if ($nextStep) {
163
            $this->beforeSignal()->dispatch();
164
165
            $service->moveForwardToStep($nextStep, $this->redirect());
166
        }
167
    }
168
169
    /**
170
     * @return MiddlewareScopes
171
     */
172
    public function getScopes()
173
    {
174
        return $this->scopes;
175
    }
176
177
    /**
178
     * Returns a new forward dispatcher, on which you can add options by calling
179
     * its fluent methods.
180
     *
181
     * You must call the method `dispatch()` to actually dispatch the forward
182
     * signal.
183
     *
184
     * @return Forward
185
     */
186
    final protected function forward()
187
    {
188
        return new Forward($this->getRequest(), $this->getFormObject());
189
    }
190
191
    /**
192
     * Returns a new redirect dispatcher, on which you can add options by
193
     * calling its fluent methods.
194
     *
195
     * You must call the method `dispatch()` to actually dispatch the redirect
196
     * signal.
197
     *
198
     * @return Redirect
199
     */
200
    final protected function redirect()
201
    {
202
        return new Redirect($this->getRequest(), $this->getFormObject());
203
    }
204
205
    /**
206
     * @return FormObject
207
     */
208
    final protected function getFormObject()
209
    {
210
        return $this->processor->getFormObject();
211
    }
212
213
    /**
214
     * @return Request
215
     */
216
    final protected function getRequest()
217
    {
218
        return $this->processor->getRequest();
219
    }
220
221
    /**
222
     * @return Arguments
223
     */
224
    final protected function getRequestArguments()
225
    {
226
        return $this->processor->getRequestArguments();
227
    }
228
229
    /**
230
     * @return array
231
     */
232
    final protected function getSettings()
233
    {
234
        return $this->processor->getSettings();
235
    }
236
237
    /**
238
     * @return Step|null
239
     */
240
    final protected function getCurrentStep()
241
    {
242
        return $this->getFormObject()->getCurrentStep();
243
    }
244
245
    /**
246
     * @return int
247
     */
248
    public function getPriority()
249
    {
250
        return (int)$this->priority;
251
    }
252
253
    /**
254
     * @param MiddlewareProcessor $middlewareProcessor
255
     */
256
    final public function bindMiddlewareProcessor(MiddlewareProcessor $middlewareProcessor)
257
    {
258
        $this->processor = $middlewareProcessor;
259
    }
260
261
    /**
262
     * Returns the name of the signal on which this middleware is bound.
263
     *
264
     * @return string
265
     * @throws SignalNotFoundException
266
     */
267
    final public function getBoundSignalName()
268
    {
269
        $interfaces = class_implements($this);
270
271
        foreach ($interfaces as $interface) {
272
            if (in_array(MiddlewareSignal::class, class_implements($interface))) {
273
                return $interface;
274
            }
275
        }
276
277
        throw SignalNotFoundException::signalNotFoundInMiddleware($this);
278
    }
279
280
    /**
281
     * Will inject empty options if no option has been defined at all.
282
     *
283
     * @param DataPreProcessor $processor
284
     */
285
    public static function dataPreProcessor(DataPreProcessor $processor)
286
    {
287
        $data = $processor->getData();
288
289
        if (false === isset($data['options'])) {
290
            $data['options'] = [];
291
        }
292
293
        if (false === isset($data['scopes'])) {
294
            $data['scopes'] = [];
295
        }
296
297
        if (false === isset($data['scopes']['whiteList'])) {
298
            $data['scopes']['whiteList'] = [MainScope::class];
299
        }
300
301
        if (false === isset($data['scopes']['blackList'])) {
302
            $data['scopes']['blackList'] = [];
303
        }
304
305
        $data['scopes']['whiteList'] = array_unique(array_merge(static::$defaultScopesWhiteList, $data['scopes']['whiteList']));
306
        $data['scopes']['blackList'] = array_unique(array_merge(static::$defaultScopesBlackList, $data['scopes']['blackList']));
307
308
        $processor->setData($data);
309
    }
310
311
    /**
312
     * @param string $signal
313
     * @param string $type
314
     * @return SignalObject
315
     * @throws InvalidArgumentValueException
316
     * @throws InvalidEntryException
317
     * @throws MissingArgumentException
318
     */
319
    private function getSignalObject($signal, $type)
320
    {
321
        if (false === $this instanceof SendsMiddlewareSignal) {
322
            throw InvalidEntryException::middlewareNotSendingSignals($this);
323
        }
324
325
        /** @var SendsMiddlewareSignal $this */
326
        if (null === $signal) {
327
            if (count($this->getAllowedSignals()) > 1) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Romm\Formz\Middleware\Item\AbstractMiddleware as the method getAllowedSignals() does only exist in the following sub-classes of Romm\Formz\Middleware\Item\AbstractMiddleware: Romm\Formz\Middleware\It...our\BehaviourMiddleware, Romm\Formz\Middleware\It...FormInjectionMiddleware, Romm\Formz\Middleware\It...ormValidationMiddleware, Romm\Formz\Middleware\It...tenceFetchingMiddleware, Romm\Formz\Middleware\It...epDispatchingMiddleware. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
328
                throw MissingArgumentException::signalNameArgumentMissing($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<Romm\Formz\Middlewa...tem\AbstractMiddleware>, but the function expects a object<Romm\Formz\Middle...\SendsMiddlewareSignal>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
329
            }
330
331
            $signal = reset($this->getAllowedSignals());
0 ignored issues
show
Bug introduced by
$this->getAllowedSignals() cannot be passed to reset() as the parameter $array expects a reference.
Loading history...
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Romm\Formz\Middleware\Item\AbstractMiddleware as the method getAllowedSignals() does only exist in the following sub-classes of Romm\Formz\Middleware\Item\AbstractMiddleware: Romm\Formz\Middleware\It...our\BehaviourMiddleware, Romm\Formz\Middleware\It...FormInjectionMiddleware, Romm\Formz\Middleware\It...ormValidationMiddleware, Romm\Formz\Middleware\It...tenceFetchingMiddleware, Romm\Formz\Middleware\It...epDispatchingMiddleware. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
332
        }
333
334
        if (false === in_array($signal, $this->getAllowedSignals())) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Romm\Formz\Middleware\Item\AbstractMiddleware as the method getAllowedSignals() does only exist in the following sub-classes of Romm\Formz\Middleware\Item\AbstractMiddleware: Romm\Formz\Middleware\It...our\BehaviourMiddleware, Romm\Formz\Middleware\It...FormInjectionMiddleware, Romm\Formz\Middleware\It...ormValidationMiddleware, Romm\Formz\Middleware\It...tenceFetchingMiddleware, Romm\Formz\Middleware\It...epDispatchingMiddleware. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
335
            throw InvalidArgumentValueException::signalNotAllowed($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<Romm\Formz\Middlewa...tem\AbstractMiddleware>, but the function expects a object<Romm\Formz\Middle...\SendsMiddlewareSignal>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
336
        }
337
338
        return new SignalObject($this->processor, $signal, $type);
339
    }
340
341
    /**
342
     * @return array
343
     */
344
    public function __sleep()
345
    {
346
        return ['options', 'scopes'];
347
    }
348
}
349