Completed
Push — feature/middleware-tmp ( 8f1e4d )
by Romain
01:57
created

AbstractMiddleware::getSignalObject()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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