Passed
Push — master ( 115a96...ff3cf0 )
by Ryan
13:16
created

HandlerStack   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 237
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 237
rs 10
c 0
b 0
f 0
wmc 24

11 Methods

Rating   Name   Duplication   Size   Complexity  
A prependClosure() 0 8 1
A logRegistration() 0 6 2
A invoke() 0 20 3
A __construct() 0 6 1
A exceptionMessage() 0 6 2
A appendClosure() 0 8 1
A append() 0 17 1
B validateMiddleware() 0 20 8
A registerNamespaces() 0 5 2
A debugStack() 0 15 2
A prepend() 0 17 1
1
<?php
2
/**
3
 * Copyright (c) 2017–2018 Ryan Parman <http://ryanparman.com>.
4
 * Copyright (c) 2017–2018 Contributors.
5
 *
6
 * http://opensource.org/licenses/Apache2.0
7
 */
8
9
declare(strict_types=1);
10
11
namespace SimplePie;
12
13
use DOMXPath;
14
use SimplePie\Configuration\SetLoggerInterface;
15
use SimplePie\Enum\FeedType;
16
use SimplePie\Exception\MiddlewareException;
17
use SimplePie\Middleware\Html\HtmlInterface;
18
use SimplePie\Middleware\Json\JsonInterface;
19
use SimplePie\Middleware\Xml\XmlInterface;
20
use SimplePie\Mixin as T;
21
use SimplePie\Util\Ns;
22
use Skyzyx\UtilityPack\Types;
23
use stdClass;
24
25
/**
26
 * `SimplePie\HandlerStack` is a middleware stack system which is modeled after
27
 * [Guzzle's middleware handler stack system](http://docs.guzzlephp.org/en/latest/handlers-and-middleware.html),
28
 * but is designed specifically for SimplePie's use-cases.
29
 *
30
 * Its primary job is to (a) allow the registration and priority of middleware,
31
 * and (b) provide the interface for SimplePie NG to trigger middleware.
32
 */
33
class HandlerStack implements HandlerStackInterface, SetLoggerInterface
34
{
35
    use T\LoggerTrait;
36
37
    /**
38
     * The middleware stack, grouped by feed type.
39
     *
40
     * @var string
41
     */
42
    protected $stack;
43
44
    /**
45
     * Constructs a new instance of this class.
46
     */
47
    public function __construct()
48
    {
49
        $this->stack = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('html' => array(),...ay(), 'xml' => array()) of type array<string,array> is incompatible with the declared type string of property $stack.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
50
            'html' => [],
51
            'json' => [],
52
            'xml'  => [],
53
        ];
54
    }
55
56
    /**
57
     * {@inheritdoc}
58
     *
59
     * phpcs:disable Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine
60
     */
61
    public function append(
62
        callable $middleware,
63
        ?string $name = null,
64
        ?string $overrideType = null
65
    ): HandlerStackInterface {
66
        // phpcs:enable
67
68
        $this->validateMiddleware(
69
            $middleware,
70
            $name,
71
            $overrideType,
72
            static function (&$arr) use ($middleware, $name): void {
73
                $arr[] = [$middleware, $name];
74
            }
75
        );
76
77
        return $this;
78
    }
79
80
    /**
81
     * {@inheritdoc}
82
     *
83
     * phpcs:disable Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine
84
     */
85
    public function appendClosure(
86
        string $overrideType,
87
        callable $middleware,
88
        ?string $name = null
89
    ): HandlerStackInterface {
90
        // phpcs:enable
91
92
        return $this->append($middleware, $name, $overrideType);
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     *
98
     * phpcs:disable Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine
99
     */
100
    public function prepend(
101
        callable $middleware,
102
        ?string $name = null,
103
        ?string $overrideType = null
104
    ): HandlerStackInterface {
105
        // phpcs:enable
106
107
        $this->validateMiddleware(
108
            $middleware,
109
            $name,
110
            $overrideType,
111
            static function (&$arr) use ($middleware, $name): void {
112
                \array_unshift($arr, [$middleware, $name]);
113
            }
114
        );
115
116
        return $this;
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     *
122
     * phpcs:disable Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine
123
     */
124
    public function prependClosure(
125
        string $overrideType,
126
        callable $middleware,
127
        ?string $name = null
128
    ): HandlerStackInterface {
129
        // phpcs:enable
130
131
        return $this->prepend($middleware, $name, $overrideType);
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137
    public function invoke(string $feedType, stdClass $feedRoot, ?string $namespaceAlias, DOMXPath $xpath): void
138
    {
139
        if (isset($this->stack[$feedType])) {
140
            foreach ($this->stack[$feedType] as $tuple) {
141
                $middleware = $tuple[0];
142
                $middleware->setLogger($this->getLogger());
143
                $middleware($feedRoot, $namespaceAlias, $xpath);
144
            }
145
        } else {
146
            $allowedTypes = FeedType::introspectKeys();
147
            \array_shift($allowedTypes);
148
149
            throw new MiddlewareException(\sprintf(
150
                'Could not determine which handler stack to invoke. Stack `%s` was requested. [Allowed: %s]',
151
                $feedType,
152
                \implode(', ', \array_map(
153
                    static function ($type) {
154
                        return \sprintf('FeedType::%s', $type);
155
                    },
156
                    $allowedTypes
157
                ))
158
            ));
159
        }
160
    }
161
162
    /**
163
     * Collects all of the supported namespaces from the registered middleware.
164
     *
165
     * **NOTE:** Only significant for XML-based feed types.
166
     *
167
     * @param Ns $ns [description]
168
     */
169
    public function registerNamespaces(Ns $ns): void
170
    {
171
        foreach ($this->stack[FeedType::XML] as $tuple) {
172
            $middleware = $tuple[0];
173
            $ns->addAliases($middleware->getSupportedNamespaces());
174
        }
175
    }
176
177
    /**
178
     * [debugStack description].
179
     *
180
     * @return [type] [description]
181
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment [type] at position 0 could not be parsed: Unknown type name '[' at position 0 in [type].
Loading history...
182
    public function debugStack(): array
183
    {
184
        $fn = static function ($mw) {
185
            return \sprintf(
186
                '[<%s: resource %s>, %s]',
187
                Types::getClassOrType($mw[0]),
188
                \md5(\spl_object_hash($mw[0])),
189
                isset($mw[1]) ? \sprintf('"%s"', $mw[1]) : 'null'
190
            );
191
        };
192
193
        return [
194
            'html' => \array_map($fn, $this->stack['html']),
195
            'json' => \array_map($fn, $this->stack['json']),
196
            'xml'  => \array_map($fn, $this->stack['xml']),
197
        ];
198
    }
199
200
    /**
201
     * Validates the middleware and applies it to the right stack.
202
     *
203
     * @param callable    $middleware   The middleware to add to the stack.
204
     * @param string|null $name         A name for the middleware. Can be used with `pushBefore()` and `pushAfter()`.
205
     * @param string|null $overrideType Override our best guess for which stack to apply the middleware to. By default
206
     *                                  the appropriate stack will be determined by which
207
     *                                  `SimplePie\Middleware\*\*Interface` the middleware extends from. If the
208
     *                                  middleware is a closure, this parameter is required. If the appropriate stack
209
     *                                  cannot be determined, a `SimplePie\Exception\MiddlewareException` exception
210
     *                                  will be thrown.
211
     * @param callable    $fn           A callable which receives the stack by-reference as a parameter, and chooses
212
     *                                  how to add the middleware to that stack.
213
     *
214
     * @throws MiddlewareException
215
     *
216
     * phpcs:disable Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine
217
     */
218
    protected function validateMiddleware(
219
        callable $middleware,
220
        ?string $name = null,
221
        ?string $overrideType = null,
222
        callable $fn
223
    ): void {
224
        // phpcs:enable
225
226
        if (FeedType::ALL === $overrideType) {
227
            $fn($this->stack['html']);
228
            $fn($this->stack['json']);
229
            $fn($this->stack['xml']);
230
        } elseif (FeedType::JSON === $overrideType || $middleware instanceof JsonInterface) {
231
            $fn($this->stack['json']);
232
        } elseif (FeedType::XML === $overrideType || $middleware instanceof XmlInterface) {
233
            $fn($this->stack['xml']);
234
        } elseif (FeedType::HTML === $overrideType || $middleware instanceof HtmlInterface) {
235
            $fn($this->stack['html']);
236
        } else {
237
            throw new MiddlewareException($this->exceptionMessage($middleware, $name, $overrideType));
0 ignored issues
show
Unused Code introduced by
The call to SimplePie\HandlerStack::exceptionMessage() has too many arguments starting with $overrideType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

237
            throw new MiddlewareException($this->/** @scrutinizer ignore-call */ exceptionMessage($middleware, $name, $overrideType));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
238
        }
239
    }
240
241
    /**
242
     * Log that the registration of the middleware occurred.
243
     *
244
     * @param callable    $middleware The middleware to add to the stack.
245
     * @param string|null $name       A name for the middleware. Can be used with `pushBefore()` and `pushAfter()`.
246
     */
247
    protected function logRegistration(callable $middleware, ?string $name = null): void
248
    {
249
        $this->logger->info(\sprintf(
250
            'Registered `%s` as middleware%s.',
251
            Types::getClassOrType($middleware),
252
            (null !== $name ? \sprintf(' with the name `%s`', $name) : '')
253
        ));
254
    }
255
256
    /**
257
     * Generate the most appropriate error message based on the parameters that were passed.
258
     *
259
     * @param callable    $middleware The middleware to add to the stack.
260
     * @param string|null $name       A name for the middleware. Can be used with `pushBefore()` and `pushAfter()`.
261
     *
262
     * @return string
263
     */
264
    protected function exceptionMessage(callable $middleware, ?string $name = null): string
265
    {
266
        return \sprintf(
267
            'The middleware `%s`%s could not be assigned to a feed type.',
268
            Types::getClassOrType($middleware),
269
            (null !== $name ? \sprintf(' with the name `%s`', $name) : '')
270
        );
271
    }
272
}
273