Passed
Push — master ( bf1af5...e52ce1 )
by Ryan
14:07
created

HandlerStack::validateMiddleware()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 12
nc 5
nop 4
dl 0
loc 20
rs 7.7777
c 0
b 0
f 0
1
<?php
2
/**
3
 * Copyright (c) 2017 Ryan Parman <http://ryanparman.com>.
4
 * Copyright (c) 2017 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 Skyzyx\UtilityPack\Types;
22
use stdClass;
23
24
/**
25
 * `SimplePie\HandlerStack` is a middleware stack system which is modeled after
26
 * [Guzzle's middleware handler stack system](http://docs.guzzlephp.org/en/latest/handlers-and-middleware.html),
27
 * but is designed specifically for SimplePie's use-cases.
28
 *
29
 * Its primary job is to (a) allow the registration and priority of middleware,
30
 * and (b) provide the interface for SimplePie NG to trigger middleware.
31
 */
32
class HandlerStack implements HandlerStackInterface, SetLoggerInterface
33
{
34
    use T\LoggerTrait;
35
36
    /**
37
     * The middleware stack, grouped by feed type.
38
     *
39
     * @var string
40
     */
41
    protected $stack;
42
43
    /**
44
     * Constructs a new instance of this class.
45
     */
46
    public function __construct()
47
    {
48
        $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...
49
            'html' => [],
50
            'json' => [],
51
            'xml'  => [],
52
        ];
53
    }
54
55
    /**
56
     * {@inheritdoc}
57
     *
58
     * @codingStandardsIgnoreStart
59
     */
60
    public function append(
61
        callable $middleware,
62
        ?string $name = null,
63
        ?string $overrideType = null
64
    ): HandlerStackInterface {
65
        // @codingStandardsIgnoreEnd
66
67
        $this->validateMiddleware(
68
            $middleware,
69
            $name,
70
            $overrideType,
71
            static function (&$arr) use ($middleware, $name): void {
72
                $arr[] = [$middleware, $name];
73
            }
74
        );
75
76
        return $this;
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     *
82
     * @codingStandardsIgnoreStart
83
     */
84
    public function appendClosure(
85
        string $overrideType,
86
        callable $middleware,
87
        ?string $name = null
88
    ): HandlerStackInterface {
89
        // @codingStandardsIgnoreEnd
90
91
        return $this->append($middleware, $name, $overrideType);
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     *
97
     * @codingStandardsIgnoreStart
98
     */
99
    public function prepend(
100
        callable $middleware,
101
        ?string $name = null,
102
        ?string $overrideType = null
103
    ): HandlerStackInterface {
104
        // @codingStandardsIgnoreEnd
105
106
        $this->validateMiddleware(
107
            $middleware,
108
            $name,
109
            $overrideType,
110
            static function (&$arr) use ($middleware, $name): void {
111
                \array_unshift($arr, [$middleware, $name]);
112
            }
113
        );
114
115
        return $this;
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     *
121
     * @codingStandardsIgnoreStart
122
     */
123
    public function prependClosure(
124
        string $overrideType,
125
        callable $middleware,
126
        ?string $name = null
127
    ): HandlerStackInterface {
128
        // @codingStandardsIgnoreEnd
129
130
        return $this->prepend($middleware, $name, $overrideType);
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     *
136
     * @codingStandardsIgnoreStart
137
     */
138
    public function invoke(
139
        string $feedType,
140
        stdClass $feedRoot,
141
        ?string $namespaceAlias,
142
        DOMXPath $xpath
143
    ): void {
144
        // @codingStandardsIgnoreEnd
145
146
        if (isset($this->stack[$feedType])) {
147
            foreach ($this->stack[$feedType] as $tuple) {
148
                $middleware = $tuple[0];
149
                $middleware->setLogger($this->getLogger());
150
                $middleware($feedRoot, $namespaceAlias, $xpath);
151
            }
152
        } else {
153
            $allowedTypes = FeedType::introspectKeys();
154
            \array_shift($allowedTypes);
155
156
            throw new MiddlewareException(\sprintf(
157
                'Could not determine which handler stack to invoke. Stack `%s` was requested. [Allowed: %s]',
158
                $feedType,
159
                \implode(', ', \array_map(
160
                    static function ($type) {
161
                        return \sprintf('FeedType::%s', $type);
162
                    },
163
                    $allowedTypes
164
                ))
165
            ));
166
        }
167
    }
168
169
    /**
170
     * [debugStack description].
171
     *
172
     * @return [type] [description]
173
     */
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...
174
    public function debugStack(): array
175
    {
176
        $fn = static function ($mw) {
177
            return \sprintf(
178
                '[<%s: resource %s>, %s]',
179
                Types::getClassOrType($mw[0]),
180
                \md5(\spl_object_hash($mw[0])),
181
                isset($mw[1]) ? \sprintf('"%s"', $mw[1]) : 'null'
182
            );
183
        };
184
185
        return [
186
            'html' => \array_map($fn, $this->stack['html']),
187
            'json' => \array_map($fn, $this->stack['json']),
188
            'xml'  => \array_map($fn, $this->stack['xml']),
189
        ];
190
    }
191
192
    /**
193
     * Validates the middleware and applies it to the right stack.
194
     *
195
     * @param callable    $middleware   The middleware to add to the stack.
196
     * @param string|null $name         A name for the middleware. Can be used with `pushBefore()` and `pushAfter()`.
197
     * @param string|null $overrideType Override our best guess for which stack to apply the middleware to. By default
198
     *                                  the appropriate stack will be determined by which
199
     *                                  `SimplePie\Middleware\*\*Interface` the middleware extends from. If the
200
     *                                  middleware is a closure, this parameter is required. If the appropriate stack
201
     *                                  cannot be determined, a `SimplePie\Exception\MiddlewareException` exception
202
     *                                  will be thrown.
203
     * @param callable    $fn           A callable which receives the stack by-reference as a parameter, and chooses
204
     *                                  how to add the middleware to that stack.
205
     *
206
     * @throws MiddlewareException
207
     *
208
     * @codingStandardsIgnoreStart
209
     */
210
    protected function validateMiddleware(
211
        callable $middleware,
212
        ?string $name = null,
213
        ?string $overrideType = null,
214
        callable $fn
215
    ): void {
216
        // @codingStandardsIgnoreEnd
217
218
        if (FeedType::ALL === $overrideType) {
219
            $fn($this->stack['html']);
220
            $fn($this->stack['json']);
221
            $fn($this->stack['xml']);
222
        } elseif (FeedType::JSON === $overrideType || $middleware instanceof JsonInterface) {
223
            $fn($this->stack['json']);
224
        } elseif (FeedType::XML === $overrideType || $middleware instanceof XmlInterface) {
225
            $fn($this->stack['xml']);
226
        } elseif (FeedType::HTML === $overrideType || $middleware instanceof HtmlInterface) {
227
            $fn($this->stack['html']);
228
        } else {
229
            throw new MiddlewareException($this->exceptionMessage($middleware, $name, $overrideType));
230
        }
231
    }
232
233
    /**
234
     * Log that the registration of the middleware occurred.
235
     *
236
     * @param callable    $middleware   The middleware to add to the stack.
237
     * @param string|null $name         A name for the middleware. Can be used with `pushBefore()` and `pushAfter()`.
238
     * @param string|null $overrideType Override our best guess for which stack to apply the middleware to. By default
239
     *                                  the appropriate stack will be determined by which
240
     *                                  `SimplePie\Middleware\*\*Interface` the middleware extends from. If the
241
     *                                  middleware is a closure, this parameter is required. If the appropriate stack
242
     *                                  cannot be determined, a `SimplePie\Exception\MiddlewareException` exception
243
     *                                  will be thrown.
244
     *
245
     * @codingStandardsIgnoreStart
246
     */
247
    protected function logRegistration(
248
        callable $middleware,
249
        ?string $name = null,
250
        ?string $overrideType = null
251
    ): void {
252
        // @codingStandardsIgnoreEnd
253
254
        $this->logger->info(\sprintf(
255
            'Registered `%s` as middleware%s.',
256
            Types::getClassOrType($middleware),
257
            (null !== $name ? \sprintf(' with the name `%s`', $name) : '')
258
        ));
259
    }
260
261
    /**
262
     * Generate the most appropriate error message based on the parameters that were passed.
263
     *
264
     * @param callable    $middleware   The middleware to add to the stack.
265
     * @param string|null $name         A name for the middleware. Can be used with `pushBefore()` and `pushAfter()`.
266
     * @param string|null $overrideType Override our best guess for which stack to apply the middleware to. By default
267
     *                                  the appropriate stack will be determined by which
268
     *                                  `SimplePie\Middleware\*\*Interface` the middleware extends from. If the
269
     *                                  middleware is a closure, this parameter is required. If the appropriate stack
270
     *                                  cannot be determined, a `SimplePie\Exception\MiddlewareException` exception
271
     *                                  will be thrown.
272
     *
273
     * @return string
274
     *
275
     * @codingStandardsIgnoreStart
276
     */
277
    protected function exceptionMessage(
278
        callable $middleware,
279
        ?string $name = null,
280
        ?string $overrideType = null
281
    ): string {
282
        // @codingStandardsIgnoreEnd
283
284
        return \sprintf(
285
            'The middleware `%s`%s could not be assigned to a feed type.',
286
            Types::getClassOrType($middleware),
287
            (null !== $name ? \sprintf(' with the name `%s`', $name) : '')
288
        );
289
    }
290
}
291