Completed
Push — middleware-wip ( 668c38...d5b291 )
by Romain
02:54
created

AbstractMiddleware::getOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
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\Configuration\Form\Step\Step\StepItem;
19
use Romm\Formz\Error\FormResult;
20
use Romm\Formz\Exceptions\InvalidArgumentValueException;
21
use Romm\Formz\Exceptions\InvalidEntryException;
22
use Romm\Formz\Exceptions\MissingArgumentException;
23
use Romm\Formz\Exceptions\SignalNotFoundException;
24
use Romm\Formz\Form\FormObject\FormObject;
25
use Romm\Formz\Middleware\Option\AbstractOptionDefinition;
26
use Romm\Formz\Middleware\Request\Exception\StopPropagationException;
27
use Romm\Formz\Middleware\MiddlewareInterface;
28
use Romm\Formz\Middleware\Request\Forward;
29
use Romm\Formz\Middleware\Request\Redirect;
30
use Romm\Formz\Middleware\Signal\After;
31
use Romm\Formz\Middleware\Signal\Before;
32
use Romm\Formz\Middleware\Signal\MiddlewareSignal;
33
use Romm\Formz\Middleware\Signal\SendsMiddlewareSignal;
34
use Romm\Formz\Middleware\Signal\SignalObject;
35
use Romm\Formz\Middleware\State\MiddlewareState;
36
use TYPO3\CMS\Extbase\Mvc\Controller\Arguments;
37
use TYPO3\CMS\Extbase\Mvc\Web\Request;
38
39
/**
40
 * Abstract class that must be extended by middlewares.
41
 *
42
 * Child middleware must implement their own signals.
43
 */
44
abstract class AbstractMiddleware implements MiddlewareInterface, DataPreProcessorInterface
45
{
46
    /**
47
     * @var MiddlewareState
48
     */
49
    private $state;
50
51
    /**
52
     * This is the default option class, this property can be overridden in
53
     * child classes to be mapped to another option definition.
54
     *
55
     * @var \Romm\Formz\Middleware\Option\DefaultOptionDefinition
56
     */
57
    protected $options;
58
59
    /**
60
     * Can be overridden in child class with custom priority value.
61
     *
62
     * The higher the priority is, the earlier the middleware is called.
63
     *
64
     * Note that you can also override the method `getPriority()` for advanced
65
     * priority calculation.
66
     *
67
     * @var int
68
     */
69
    protected $priority = 0;
70
71
    /**
72
     * @param AbstractOptionDefinition $options
73
     */
74
    final public function __construct(AbstractOptionDefinition $options)
75
    {
76
        $this->options = $options;
0 ignored issues
show
Documentation Bug introduced by
$options is of type object<Romm\Formz\Middle...stractOptionDefinition>, 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...
77
    }
78
79
    /**
80
     * Abstraction for processing the middleware initialization.
81
     *
82
     * For own initialization, @see initializeMiddleware()
83
     */
84
    final public function initialize()
85
    {
86
        $this->initializeMiddleware();
87
    }
88
89
    /**
90
     * You can override this method in your child class to initialize your
91
     * middleware correctly.
92
     */
93
    protected function initializeMiddleware()
94
    {
95
    }
96
97
    /**
98
     * @see \Romm\Formz\Middleware\Signal\SendsMiddlewareSignal::beforeSignal()
99
     *
100
     * @param string $signal
101
     * @return SignalObject
102
     */
103
    final public function beforeSignal($signal = null)
104
    {
105
        return $this->getSignalObject($signal, Before::class);
106
    }
107
108
    /**
109
     * @see \Romm\Formz\Middleware\Signal\SendsMiddlewareSignal::afterSignal()
110
     *
111
     * @param string $signal
112
     * @return SignalObject
113
     */
114
    final public function afterSignal($signal = null)
115
    {
116
        return $this->getSignalObject($signal, After::class);
117
    }
118
119
    /**
120
     * @return AbstractOptionDefinition
121
     */
122
    public function getOptions()
123
    {
124
        return $this->options;
125
    }
126
127
    /**
128
     * Returns a new forward dispatcher, on which you can add options by calling
129
     * its fluent methods.
130
     *
131
     * You must call the method `dispatch()` to actually dispatch the forward
132
     * signal.
133
     *
134
     * @return Forward
135
     */
136
    final protected function forward()
137
    {
138
        return new Forward($this->getRequest());
139
    }
140
141
    /**
142
     * Returns a new redirect dispatcher, on which you can add options by
143
     * calling its fluent methods.
144
     *
145
     * You must call the method `dispatch()` to actually dispatch the redirect
146
     * signal.
147
     *
148
     * @return Redirect
149
     */
150
    final protected function redirect() {
151
        return new Redirect($this->getRequest());
152
    }
153
154
    /**
155
     * Will stop the propagation of middlewares: the next middlewares wont be
156
     * processed.
157
     *
158
     * Use with caution!
159
     */
160
    final protected function stopPropagation()
161
    {
162
        throw new StopPropagationException;
163
    }
164
165
    /**
166
     * @return FormObject
167
     */
168
    final protected function getFormObject()
169
    {
170
        return $this->state->getFormObject();
171
    }
172
173
    /**
174
     * @return FormResult
175
     */
176
    final protected function getResult()
177
    {
178
        return $this->state->getResult();
179
    }
180
181
    /**
182
     * @return Request
183
     */
184
    final protected function getRequest()
185
    {
186
        return $this->state->getRequest();
187
    }
188
189
    /**
190
     * @return Arguments
191
     */
192
    final protected function getRequestArguments()
193
    {
194
        return $this->state->getRequestArguments();
195
    }
196
197
    /**
198
     * @return array
199
     */
200
    final protected function getSettings()
201
    {
202
        return $this->state->getSettings();
203
    }
204
205
    /**
206
     * @return StepItem|null
207
     */
208
    final protected function getCurrentStep()
209
    {
210
        $request = ($this->getFormObject()->formWasSubmitted())
211
            ? $this->getRequest()->getReferringRequest()
212
            : $this->getRequest();
213
214
        return $this->getFormObject()->getCurrentStep($request);
215
    }
216
217
    /**
218
     * @return int
219
     */
220
    public function getPriority()
221
    {
222
        return (int)$this->priority;
223
    }
224
225
    /**
226
     * @param MiddlewareState $middlewareState
227
     */
228
    final public function bindMiddlewareState(MiddlewareState $middlewareState)
229
    {
230
        $this->state = $middlewareState;
231
    }
232
233
    /**
234
     * Returns the name of the signal on which this middleware is bound.
235
     *
236
     * @return string
237
     * @throws SignalNotFoundException
238
     */
239
    final public function getBoundSignalName()
240
    {
241
        $interfaces = class_implements($this);
242
243
        foreach ($interfaces as $interface) {
244
            if (in_array(MiddlewareSignal::class, class_implements($interface))) {
245
                return $interface;
246
            }
247
        }
248
249
        throw SignalNotFoundException::signalNotFoundInMiddleware($this);
250
    }
251
252
    /**
253
     * Will inject empty options if no option has been defined at all.
254
     *
255
     * @param DataPreProcessor $processor
256
     */
257
    public static function dataPreProcessor(DataPreProcessor $processor)
258
    {
259
        $data = $processor->getData();
260
261
        if (false === isset($data['options'])) {
262
            $data['options'] = [];
263
        }
264
265
        $processor->setData($data);
266
    }
267
268
    /**
269
     * @param string $signal
270
     * @param string $type
271
     * @return SignalObject
272
     * @throws InvalidArgumentValueException
273
     * @throws InvalidEntryException
274
     * @throws MissingArgumentException
275
     */
276
    private function getSignalObject($signal, $type)
277
    {
278
        if (false === $this instanceof SendsMiddlewareSignal) {
279
            throw InvalidEntryException::middlewareNotSendingSignals($this);
280
        }
281
282
        /** @var SendsMiddlewareSignal $this */
283
        if (null === $signal) {
284
            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. 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...
285
                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...
286
            }
287
288
            $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. 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...
289
        }
290
291
        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. 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...
292
            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...
293
        }
294
295
        return new SignalObject($this->state, $signal, $type);
296
    }
297
}
298