Issues (3)

src/Pipeline.php (1 issue)

1
<?php
2
/*
3
 * This file is part of the Scrawler package.
4
 *
5
 * (c) Pranjal Pandey <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Scrawler;
12
13
use Scrawler\Interfaces\MiddlewareInterface;
14
15
final class Pipeline
16
{
17
    /**
18
     * Create a new pipeline.
19
     *
20
     * @param array<\Closure> $middlewares
21
     */
22
    public function __construct(
23
        private array $middlewares = [],
24
    ) {
25
    }
26
27
    /**
28
     * Add middleware(s) or Pipeline.
29
     *
30
     * @template T of MiddlewareInterface
31
     *
32
     * @param array<callable|\Closure|class-string<T>> $middlewares
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<callable|\Closure|class-string<T>> at position 6 could not be parsed: Unknown type name 'class-string' at position 6 in array<callable|\Closure|class-string<T>>.
Loading history...
33
     *
34
     * @return array<\Closure>
35
     */
36
    public function validateMiddleware(array $middlewares): array
37
    {
38
        $validated = [];
39
        foreach ($middlewares as $middleware) {
40
            if (is_string($middleware)) {
41
                if (class_exists($middleware)) {
42
                    $middlewareObj = new $middleware();
43
                    if ($middlewareObj instanceof MiddlewareInterface) {
44
                        $callable = $middlewareObj->run(...);
45
                        $middleware = \Closure::fromCallable(callback: $callable);
46
                    } else {
47
                        throw new Exception\InvalidMiddlewareException('Middleware class does not implement MiddlewareInterface');
48
                    }
49
                } else {
50
                    throw new Exception\InvalidMiddlewareException('Middleware class does not exist');
51
                }
52
            }
53
            $middleware = \Closure::fromCallable(callback: $middleware);
54
            $this->validateClosure($middleware);
55
            $validated[] = $middleware;
56
        }
57
58
        return $validated;
59
    }
60
61
    private function validateClosure(\Closure $middleware): void
62
    {
63
        $refFunction = new \ReflectionFunction($middleware);
64
        $parameters = $refFunction->getParameters();
65
        foreach ($parameters as $parameter) {
66
            if ('request' === $parameter->getName() && Http\Request::class != $parameter->getType()) {
67
                throw new Exception\InvalidMiddlewareException('First parameter of middleware must be of type Scrawler\Http\Request');
68
            }
69
            if ('next' === $parameter->getName() && 'Closure' != $parameter->getType()) {
70
                throw new Exception\InvalidMiddlewareException('Second parameter of middleware must be of type Closure');
71
            }
72
            if ('request' !== $parameter->getName() && 'next' !== $parameter->getName()) {
73
                throw new Exception\InvalidMiddlewareException('Invalid parameter name in middleware');
74
            }
75
        }
76
    }
77
78
    /**
79
     * Add middleware(s) or Pipeline.
80
     *
81
     * @param array<\Closure> $middlewares
82
     */
83
    public function middleware(array $middlewares): self
84
    {
85
        $this->middlewares = $middlewares;
86
87
        return $this;
88
    }
89
90
    /**
91
     * Run middleware around core function and pass an
92
     * object through it.
93
     */
94
    public function run(mixed $object, \Closure $core): mixed
95
    {
96
        $coreFunction = $this->createCoreFunction($core);
97
98
        $middlewares = array_reverse($this->middlewares);
99
100
        $completePipeline = array_reduce($middlewares, fn ($nextMiddleware, $middleware): \Closure => $this->createMiddleware($nextMiddleware, $middleware), $coreFunction);
101
102
        return $completePipeline($object);
103
    }
104
105
    /**
106
     * The inner function of the onion.
107
     * This function will be wrapped on layers.
108
     *
109
     * @param \Closure $core the core function
110
     *
111
     * @return \Closure
112
     */
113
    private function createCoreFunction(\Closure $core)
114
    {
115
        return fn ($object) => $core($object);
116
    }
117
118
    /**
119
     * Get an pipeline middleware function.
120
     * This function will get the object from a previous layer and pass it inwards.
121
     */
122
    private function createMiddleware(\Closure $nextMiddleware, \Closure $middleware): \Closure
123
    {
124
        return fn ($object) => $middleware($object, $nextMiddleware);
125
    }
126
}
127