Completed
Branch master (95b3ba)
by Ryan
28:02 queued 13:03
created

HandlerStack   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 259
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 5
dl 0
loc 259
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A append() 0 15 1
A appendClosure() 0 9 1
A prepend() 0 15 1
A prependClosure() 0 9 1
B invoke() 0 29 3
A debugStack() 0 17 2
C validateMiddleware() 0 22 8
A logRegistration() 0 13 2
A exceptionMessage() 0 13 2
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 Psr\Log\LoggerInterface;
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 Skyzyx\UtilityPack\Types;
21
use stdClass;
22
23
/**
24
 * `SimplePie\HandlerStack` is a middleware stack system which is modeled after
25
 * [Guzzle's middleware handler stack system](http://docs.guzzlephp.org/en/latest/handlers-and-middleware.html),
26
 * but is designed specifically for SimplePie's use-cases.
27
 *
28
 * Its primary job is to (a) allow the registration and priority of middleware,
29
 * and (b) provide the interface for SimplePie NG to trigger middleware.
30
 */
31
class HandlerStack implements HandlerStackInterface
32
{
33
    /**
34
     * A PSR-3 logger.
35
     *
36
     * @var LoggerInterface
37
     */
38
    protected $logger;
39
40
    /**
41
     * The middleware stack, grouped by feed type.
42
     *
43
     * @var string
44
     */
45
    protected $stack;
46
47
    /**
48
     * Constructs a new instance of this class.
49
     */
50
    public function __construct()
51
    {
52
        $this->logger = Configuration::getLogger();
53
54
        $this->stack = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('html' => array(),...ay(), 'xml' => array()) of type array<string,array,{"htm..."array","xml":"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...
55
            'html' => [],
56
            'json' => [],
57
            'xml'  => [],
58
        ];
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     *
64
     * @codingStandardsIgnoreStart
65
     */
66
    public function append(
67
        callable $middleware,
68
        ?string $name = null,
69
        ?string $overrideType = null
70
    ): HandlerStackInterface {
71
        // @codingStandardsIgnoreEnd
72
73
        $this->validateMiddleware($middleware, $name, $overrideType, function (&$arr) use ($middleware, $name): void {
74
            $arr[] = [$middleware, $name];
75
        });
76
77
        $this->logRegistration($middleware, $name, $overrideType);
78
79
        return $this;
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     *
85
     * @codingStandardsIgnoreStart
86
     */
87
    public function appendClosure(
88
        string $overrideType,
89
        callable $middleware,
90
        ?string $name = null
91
    ): HandlerStackInterface {
92
        // @codingStandardsIgnoreEnd
93
94
        return $this->append($middleware, $name, $overrideType);
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     *
100
     * @codingStandardsIgnoreStart
101
     */
102
    public function prepend(
103
        callable $middleware,
104
        ?string $name = null,
105
        ?string $overrideType = null
106
    ): HandlerStackInterface {
107
        // @codingStandardsIgnoreEnd
108
109
        $this->validateMiddleware($middleware, $name, $overrideType, function (&$arr) use ($middleware, $name): void {
110
            \array_unshift($arr, [$middleware, $name]);
111
        });
112
113
        $this->logRegistration($middleware, $name, $overrideType);
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) {
0 ignored issues
show
Bug introduced by
The expression $this->stack[$feedType] of type string is not traversable.
Loading history...
148
                $middleware = $tuple[0];
149
                $middleware($feedRoot, $namespaceAlias, $xpath);
150
            }
151
        } else {
152
            $allowedTypes = FeedType::introspectKeys();
153
            \array_shift($allowedTypes);
154
155
            throw new MiddlewareException(\sprintf(
156
                'Could not determine which handler stack to invoke. Stack `%s` was requested. [Allowed: %s]',
157
                $feedType,
158
                \implode(', ', \array_map(
159
                    function ($type) {
160
                        return \sprintf('FeedType::%s', $type);
161
                    },
162
                    $allowedTypes
163
                ))
164
            ));
165
        }
166
    }
167
168
    /**
169
     * [debugStack description].
170
     *
171
     * @return [type] [description]
0 ignored issues
show
Documentation introduced by
The doc-type [type] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
172
     */
173
    public function debugStack(): array
174
    {
175
        $fn = function ($mw) {
176
            return \sprintf(
177
                '[<%s: resource %s>, %s]',
178
                Types::getClassOrType($mw[0]),
179
                \md5(\spl_object_hash($mw[0])),
180
                isset($mw[1]) ? \sprintf('"%s"', $mw[1]) : 'null'
181
            );
182
        };
183
184
        return [
185
            'html' => \array_map($fn, $this->stack['html']),
186
            'json' => \array_map($fn, $this->stack['json']),
187
            'xml'  => \array_map($fn, $this->stack['xml']),
188
        ];
189
    }
190
191
    /**
192
     * Validates the middleware and applies it to the right stack.
193
     *
194
     * @param callable    $middleware   The middleware to add to the stack.
195
     * @param string|null $name         A name for the middleware. Can be used with `pushBefore()` and `pushAfter()`.
196
     * @param string|null $overrideType Override our best guess for which stack to apply the middleware to. By default
197
     *                                  the appropriate stack will be determined by which
198
     *                                  `SimplePie\Middleware\*\*Interface` the middleware extends from. If the
199
     *                                  middleware is a closure, this parameter is required. If the appropriate stack
200
     *                                  cannot be determined, a `SimplePie\Exception\MiddlewareException` exception
201
     *                                  will be thrown.
202
     * @param callable    $fn           A callable which receives the stack by-reference as a parameter, and chooses
203
     *                                  how to add the middleware to that stack.
204
     *
205
     * @throws MiddlewareException
206
     *
207
     * @codingStandardsIgnoreStart
208
     */
209
    protected function validateMiddleware(
210
        callable $middleware,
211
        ?string $name = null,
212
        ?string $overrideType = null,
213
        callable $fn
214
    ): void {
215
        // @codingStandardsIgnoreEnd
216
217
        if ($overrideType === FeedType::ALL) {
218
            $fn($this->stack['html']);
219
            $fn($this->stack['json']);
220
            $fn($this->stack['xml']);
221
        } elseif ($overrideType === FeedType::JSON || $middleware instanceof JsonInterface) {
222
            $fn($this->stack['json']);
223
        } elseif ($overrideType === FeedType::XML || $middleware instanceof XmlInterface) {
224
            $fn($this->stack['xml']);
225
        } elseif ($overrideType === FeedType::HTML || $middleware instanceof HtmlInterface) {
226
            $fn($this->stack['html']);
227
        } else {
228
            throw new MiddlewareException($this->exceptionMessage($middleware, $name, $overrideType));
229
        }
230
    }
231
232
    /**
233
     * Log that the registration of the middleware occurred.
234
     *
235
     * @param callable    $middleware   The middleware to add to the stack.
236
     * @param string|null $name         A name for the middleware. Can be used with `pushBefore()` and `pushAfter()`.
237
     * @param string|null $overrideType Override our best guess for which stack to apply the middleware to. By default
238
     *                                  the appropriate stack will be determined by which
239
     *                                  `SimplePie\Middleware\*\*Interface` the middleware extends from. If the
240
     *                                  middleware is a closure, this parameter is required. If the appropriate stack
241
     *                                  cannot be determined, a `SimplePie\Exception\MiddlewareException` exception
242
     *                                  will be thrown.
243
     *
244
     * @codingStandardsIgnoreStart
245
     */
246
    protected function logRegistration(
247
        callable $middleware,
248
        ?string $name = null,
249
        ?string $overrideType = null
0 ignored issues
show
Unused Code introduced by
The parameter $overrideType is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
250
    ): void {
251
        // @codingStandardsIgnoreEnd
252
253
        $this->logger->info(\sprintf(
254
            'Registered `%s` as middleware%s.',
255
            Types::getClassOrType($middleware),
256
            (null !== $name ? \sprintf(' with the name `%s`', $name) : '')
257
        ));
258
    }
259
260
    /**
261
     * Generate the most appropriate error message based on the parameters that were passed.
262
     *
263
     * @param callable    $middleware   The middleware to add to the stack.
264
     * @param string|null $name         A name for the middleware. Can be used with `pushBefore()` and `pushAfter()`.
265
     * @param string|null $overrideType Override our best guess for which stack to apply the middleware to. By default
266
     *                                  the appropriate stack will be determined by which
267
     *                                  `SimplePie\Middleware\*\*Interface` the middleware extends from. If the
268
     *                                  middleware is a closure, this parameter is required. If the appropriate stack
269
     *                                  cannot be determined, a `SimplePie\Exception\MiddlewareException` exception
270
     *                                  will be thrown.
271
     *
272
     * @return string
273
     *
274
     * @codingStandardsIgnoreStart
275
     */
276
    protected function exceptionMessage(
277
        callable $middleware,
278
        ?string $name = null,
279
        ?string $overrideType = null
0 ignored issues
show
Unused Code introduced by
The parameter $overrideType is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
280
    ): string {
281
        // @codingStandardsIgnoreEnd
282
283
        return \sprintf(
284
            'The middleware `%s`%s could not be assigned to a feed type.',
285
            Types::getClassOrType($middleware),
286
            (null !== $name ? \sprintf(' with the name `%s`', $name) : '')
287
        );
288
    }
289
}
290