Passed
Push — master ( 9576e2...0c2f2c )
by Ryan
46:26 queued 06:02
created

HandlerStack::prepend()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 3
dl 0
loc 17
ccs 6
cts 6
cp 1
crap 1
rs 10
c 0
b 0
f 0
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\Enum\FeedType;
15
use SimplePie\Exception\MiddlewareException;
16
use SimplePie\Middleware\Html\HtmlInterface;
17
use SimplePie\Middleware\Json\JsonInterface;
18
use SimplePie\Middleware\Xml\XmlInterface;
19
use SimplePie\Mixin as Tr;
20
use SimplePie\Util\Ns;
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
33
{
34
    use Tr\LoggerTrait;
35
36
    /**
37
     * The middleware stack, grouped by feed type.
38
     *
39
     * @var array
40
     */
41
    protected $stack;
42
43
    /**
44
     * Constructs a new instance of this class.
45
     */
46 564
    public function __construct()
47
    {
48 564
        $this->stack = [
49
            'html' => [],
50
            'json' => [],
51
            'xml'  => [],
52
        ];
53 564
    }
54
55
    /**
56
     * {@inheritdoc}
57
     *
58
     * @phpcs:disable Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine
59
     */
60 564
    public function append(
61
        callable $middleware,
62
        ?string $name = null,
63
        ?string $overrideType = null
64
    ): HandlerStackInterface {
65
        // @phpcs:enable
66
67 564
        $this->validateMiddleware(
68 564
            $middleware,
69
            $name,
70
            $overrideType,
71
            static function (&$arr) use ($middleware, $name): void {
72 563
                $arr[] = [$middleware, $name];
73 564
            }
74
        );
75
76 563
        return $this;
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     *
82
     * @phpcs:disable Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine
83
     */
84 3
    public function appendClosure(
85
        string $overrideType,
86
        callable $middleware,
87
        ?string $name = null
88
    ): HandlerStackInterface {
89
        // @phpcs:enable
90
91 3
        return $this->append($middleware, $name, $overrideType);
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     *
97
     * @phpcs:disable Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine
98
     */
99 1
    public function prepend(
100
        callable $middleware,
101
        ?string $name = null,
102
        ?string $overrideType = null
103
    ): HandlerStackInterface {
104
        // @phpcs:enable
105
106 1
        $this->validateMiddleware(
107 1
            $middleware,
108
            $name,
109
            $overrideType,
110
            static function (&$arr) use ($middleware, $name): void {
111 1
                \array_unshift($arr, [$middleware, $name]);
112 1
            }
113
        );
114
115 1
        return $this;
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     *
121
     * @phpcs:disable Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine
122
     */
123 1
    public function prependClosure(
124
        string $overrideType,
125
        callable $middleware,
126
        ?string $name = null
127
    ): HandlerStackInterface {
128
        // @phpcs:enable
129
130 1
        return $this->prepend($middleware, $name, $overrideType);
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136 560
    public function invoke(string $feedType, stdClass $feedRoot, ?string $namespaceAlias, DOMXPath $xpath): void
137
    {
138 560
        if (isset($this->stack[$feedType])) {
139 560
            foreach ($this->stack[$feedType] as $tuple) {
140 560
                $middleware = $tuple[0];
141 560
                $middleware->setLogger($this->getLogger());
142 560
                $middleware($feedRoot, $namespaceAlias ?? '', $xpath);
143
            }
144
        } else {
145
            $allowedTypes = FeedType::introspectKeys();
146
            \array_shift($allowedTypes);
147
148
            throw new MiddlewareException(\sprintf(
149
                'Could not determine which handler stack to invoke. Stack `%s` was requested. [Allowed: %s]',
150
                $feedType,
151
                \implode(', ', \array_map(
152
                    static function ($type) {
153
                        return \sprintf('FeedType::%s', $type);
154
                    },
155
                    $allowedTypes
156
                ))
157
            ));
158
        }
159 560
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164 560
    public function registerNamespaces(Ns $ns): void
165
    {
166 560
        foreach ($this->stack[FeedType::XML] as $tuple) {
167 560
            $middleware = $tuple[0];
168 560
            $ns->addAliases($middleware->getSupportedNamespaces());
169
        }
170 560
    }
171
172
    /**
173
     * Returns information about the HandlerStack that is useful for debugging.
174
     *
175
     * @return array
176
     */
177 2
    public function debugStack(): array
178
    {
179
        $fn = static function ($mw) {
180 2
            return \sprintf(
181 2
                '[<%s: resource %s>, %s]',
182 2
                Types::getClassOrType($mw[0]),
183 2
                \md5(\spl_object_hash($mw[0])),
184 2
                isset($mw[1]) ? \sprintf('"%s"', $mw[1]) : 'null'
185
            );
186 2
        };
187
188
        return [
189 2
            'html' => \array_map($fn, $this->stack['html']),
190 2
            'json' => \array_map($fn, $this->stack['json']),
191 2
            'xml'  => \array_map($fn, $this->stack['xml']),
192
        ];
193
    }
194
195
    /**
196
     * Validates the middleware and applies it to the right stack.
197
     *
198
     * @param callable    $middleware   The middleware to add to the stack.
199
     * @param string|null $name         A name for the middleware. Can be used with `pushBefore()` and `pushAfter()`.
200
     * @param string|null $overrideType Override our best guess for which stack to apply the middleware to. By default
201
     *                                  the appropriate stack will be determined by which
202
     *                                  `SimplePie\Middleware\*\*Interface` the middleware extends from. If the
203
     *                                  middleware is a closure, this parameter is required. If the appropriate stack
204
     *                                  cannot be determined, a `SimplePie\Exception\MiddlewareException` exception
205
     *                                  will be thrown.
206
     * @param callable    $fn           A callable which receives the stack by-reference as a parameter, and chooses
207
     *                                  how to add the middleware to that stack.
208
     *
209
     * @throws MiddlewareException
210
     *
211
     * @phpcs:disable Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine
212
     */
213 564
    protected function validateMiddleware(
214
        callable $middleware,
215
        ?string $name = null,
216
        ?string $overrideType = null,
217
        callable $fn
218
    ): void {
219
        // @phpcs:enable
220
221 564
        if (FeedType::ALL === $overrideType) {
222 2
            $fn($this->stack['html']);
223 2
            $fn($this->stack['json']);
224 2
            $fn($this->stack['xml']);
225 564
        } elseif (FeedType::JSON === $overrideType || $middleware instanceof JsonInterface) {
226 2
            $fn($this->stack['json']);
227 564
        } elseif (FeedType::XML === $overrideType || $middleware instanceof XmlInterface) {
228 563
            $fn($this->stack['xml']);
229 3
        } elseif (FeedType::HTML === $overrideType || $middleware instanceof HtmlInterface) {
230 2
            $fn($this->stack['html']);
231
        } else {
232 1
            throw new MiddlewareException($this->exceptionMessage($middleware, $name));
233
        }
234 563
    }
235
236
    /**
237
     * Log that the registration of the middleware occurred.
238
     *
239
     * @param callable    $middleware The middleware to add to the stack.
240
     * @param string|null $name       A name for the middleware. Can be used with `pushBefore()` and `pushAfter()`.
241
     */
242
    protected function logRegistration(callable $middleware, ?string $name = null): void
243
    {
244
        $this->logger->info(\sprintf(
245
            'Registered `%s` as middleware%s.',
246
            Types::getClassOrType($middleware),
247
            (null !== $name ? \sprintf(' with the name `%s`', $name) : '')
248
        ));
249
    }
250
251
    /**
252
     * Generate the most appropriate error message based on the parameters that were passed.
253
     *
254
     * @param callable    $middleware The middleware to add to the stack.
255
     * @param string|null $name       A name for the middleware. Can be used with `pushBefore()` and `pushAfter()`.
256
     *
257
     * @return string
258
     */
259 1
    protected function exceptionMessage(callable $middleware, ?string $name = null): string
260
    {
261 1
        return \sprintf(
262 1
            'The middleware `%s`%s could not be assigned to a feed type.',
263 1
            Types::getClassOrType($middleware),
264 1
            (null !== $name ? \sprintf(' with the name `%s`', $name) : '')
265
        );
266
    }
267
}
268