Completed
Push — feature/middleware ( 180b74...67e214 )
by Romain
04:26
created

AbstractMiddleware::getScopes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
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\Application;
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\Middleware\MiddlewareScopes;
23
use Romm\Formz\Form\FormObject\FormObject;
24
use Romm\Formz\Middleware\MiddlewareInterface;
25
use Romm\Formz\Middleware\MiddlewareFactory;
26
use Romm\Formz\Middleware\Option\OptionInterface;
27
use Romm\Formz\Middleware\Processor\MiddlewareProcessor;
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\Element\MiddlewareSignalInterface;
33
use Romm\Formz\Middleware\Signal\SendsSignal;
34
use Romm\Formz\Middleware\Signal\Element\SignalObject;
35
use TYPO3\CMS\Core\Utility\GeneralUtility;
36
use TYPO3\CMS\Extbase\Mvc\Controller\Arguments;
37
use TYPO3\CMS\Extbase\Mvc\Web\Request;
38
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
39
40
/**
41
 * Default abstraction layout that can be extended by middlewares. It contains
42
 * basic implementation needed by a middleware to work properly.
43
 *
44
 * The middleware class must still implement its 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
     * children classes to be mapped to another option definition.
56
     *
57
     * Please note that the full class name of the option must be written.
58
     *
59
     * @var \Romm\Formz\Middleware\Option\DefaultOption
60
     */
61
    protected $options;
62
63
    /**
64
     * @var \Romm\Formz\Form\Definition\Middleware\MiddlewareScopes
65
     */
66
    protected $scopes = [];
67
68
    /**
69
     * Can be overridden in child class with custom priority value.
70
     *
71
     * The higher the priority is, the earlier the middleware is called.
72
     *
73
     * Note that you can also override the method `getPriority()` for advanced
74
     * priority calculation.
75
     *
76
     * @var int
77
     */
78
    protected $priority = 0;
79
80
    /**
81
     * @var ReflectionService
82
     */
83
    protected $reflectionService;
84
85
    /**
86
     * @param OptionInterface  $options
87
     * @param MiddlewareScopes $scopes
88
     */
89
    final public function __construct(OptionInterface $options, MiddlewareScopes $scopes)
90
    {
91
        $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...e\Option\DefaultOption>. 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...
92
        $this->scopes = $scopes;
93
    }
94
95
    /**
96
     * Abstraction for processing the middleware initialization.
97
     *
98
     * For own initialization, @see initializeMiddleware()
99
     */
100
    final public function initialize()
101
    {
102
        $this->initializeMiddleware();
103
    }
104
105
    /**
106
     * You can override this method in your child class to initialize your
107
     * middleware correctly.
108
     */
109
    protected function initializeMiddleware()
110
    {
111
    }
112
113
    /**
114
     * @see \Romm\Formz\Middleware\Signal\SendsSignal::beforeSignal()
115
     *
116
     * @param string $signal
117
     * @return SignalObject
118
     */
119
    final public function beforeSignal($signal = null)
120
    {
121
        return $this->getSignalObject($signal, Before::class);
122
    }
123
124
    /**
125
     * @see \Romm\Formz\Middleware\Signal\SendsSignal::afterSignal()
126
     *
127
     * @param string $signal
128
     * @return SignalObject
129
     */
130
    final public function afterSignal($signal = null)
131
    {
132
        return $this->getSignalObject($signal, After::class);
133
    }
134
135
    /**
136
     * @return OptionInterface
137
     */
138
    public function getOptions()
139
    {
140
        return $this->options;
141
    }
142
143
    /**
144
     * @return string
145
     */
146
    public static function getOptionsClassName()
147
    {
148
        return MiddlewareFactory::get()->getOptionsClassNameFromProperty(self::class);
0 ignored issues
show
Bug introduced by
It seems like getOptionsClassNameFromProperty() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
149
    }
150
151
    /**
152
     * @return MiddlewareScopes
153
     */
154
    public function getScopes()
155
    {
156
        return $this->scopes;
157
    }
158
159
    /**
160
     * Returns a new forward dispatcher, on which you can add options by calling
161
     * its fluent methods.
162
     *
163
     * You must call the method `dispatch()` to actually dispatch the forward
164
     * signal.
165
     *
166
     * @return Forward
167
     */
168
    final protected function forward()
169
    {
170
        return new Forward($this->getRequest(), $this->getFormObject());
171
    }
172
173
    /**
174
     * Returns a new redirect dispatcher, on which you can add options by
175
     * calling its fluent methods.
176
     *
177
     * You must call the method `dispatch()` to actually dispatch the redirect
178
     * signal.
179
     *
180
     * @return Redirect
181
     */
182
    final protected function redirect()
183
    {
184
        return new Redirect($this->getRequest(), $this->getFormObject());
185
    }
186
187
    /**
188
     * @return FormObject
189
     */
190
    final protected function getFormObject()
191
    {
192
        return $this->processor->getFormObject();
193
    }
194
195
    /**
196
     * @return Request
197
     */
198
    final protected function getRequest()
199
    {
200
        return $this->processor->getRequest();
201
    }
202
203
    /**
204
     * @return Arguments
205
     */
206
    final protected function getRequestArguments()
207
    {
208
        return $this->processor->getRequestArguments();
209
    }
210
211
    /**
212
     * @return int
213
     */
214
    public function getPriority()
215
    {
216
        return (int)$this->priority;
217
    }
218
219
    /**
220
     * @param MiddlewareProcessor $middlewareProcessor
221
     */
222
    final public function bindMiddlewareProcessor(MiddlewareProcessor $middlewareProcessor)
223
    {
224
        $this->processor = $middlewareProcessor;
225
    }
226
227
    /**
228
     * Returns the name of the signal on which this middleware is bound.
229
     *
230
     * @return string
231
     * @throws SignalNotFoundException
232
     */
233
    final public function getBoundSignalName()
234
    {
235
        $interfaces = class_implements($this);
236
237
        foreach ($interfaces as $interface) {
238
            if (in_array(MiddlewareSignalInterface::class, class_implements($interface))) {
239
                return $interface;
240
            }
241
        }
242
243
        throw SignalNotFoundException::signalNotFoundInMiddleware($this);
244
    }
245
246
    /**
247
     * Will inject empty options if no option has been defined at all.
248
     *
249
     * @param DataPreProcessor $processor
250
     */
251
    public static function dataPreProcessor(DataPreProcessor $processor)
252
    {
253
        $data = $processor->getData();
254
255
        if (false === isset($data['options'])) {
256
            $data['options'] = [];
257
        }
258
259
        if (false === isset($data['scopes'])) {
260
            $data['scopes'] = [];
261
        }
262
263
        $processor->setData($data);
264
    }
265
266
    /**
267
     * Returns a signal object, that will be used to dispatch a signal coming
268
     * from this middleware.
269
     *
270
     * @param string $signal
271
     * @param string $type
272
     * @return SignalObject
273
     * @throws InvalidArgumentValueException
274
     * @throws InvalidEntryException
275
     * @throws MissingArgumentException
276
     */
277
    private function getSignalObject($signal, $type)
278
    {
279
        if (false === $this instanceof SendsSignal) {
280
            throw InvalidEntryException::middlewareNotSendingSignals($this);
281
        }
282
283
        /** @var SendsSignal $this */
284
        if (null === $signal) {
285
            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\Ap...tion\AbstractMiddleware as the method getAllowedSignals() does only exist in the following sub-classes of Romm\Formz\Middleware\Ap...tion\AbstractMiddleware: Romm\Formz\Domain\Middle...FormInjectionMiddleware, Romm\Formz\Domain\Middle...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...
286
                throw MissingArgumentException::signalNameArgumentMissing($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<Romm\Formz\Middlewa...ion\AbstractMiddleware>, but the function expects a object<Romm\Formz\Middleware\Signal\SendsSignal>.

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...
287
            }
288
289
            $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\Ap...tion\AbstractMiddleware as the method getAllowedSignals() does only exist in the following sub-classes of Romm\Formz\Middleware\Ap...tion\AbstractMiddleware: Romm\Formz\Domain\Middle...FormInjectionMiddleware, Romm\Formz\Domain\Middle...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...
290
        }
291
292
        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\Ap...tion\AbstractMiddleware as the method getAllowedSignals() does only exist in the following sub-classes of Romm\Formz\Middleware\Ap...tion\AbstractMiddleware: Romm\Formz\Domain\Middle...FormInjectionMiddleware, Romm\Formz\Domain\Middle...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...
293
            throw InvalidArgumentValueException::signalNotAllowed($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<Romm\Formz\Middlewa...ion\AbstractMiddleware>, but the function expects a object<Romm\Formz\Middleware\Signal\SendsSignal>.

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...
294
        }
295
296
        /** @var SignalObject $signalObject */
297
        $signalObject = GeneralUtility::makeInstance(SignalObject::class, $this->processor, $signal, $type);
298
299
        return $signalObject;
300
    }
301
302
    /**
303
     * @return array
304
     */
305
    public function __sleep()
306
    {
307
        return ['options', 'scopes'];
308
    }
309
310
    /**
311
     * @param ReflectionService $reflectionService
312
     */
313
    public function injectReflectionService(ReflectionService $reflectionService)
314
    {
315
        $this->reflectionService = $reflectionService;
316
    }
317
}
318