Test Failed
Push — main ( c2f646...176aa4 )
by Pranjal
02:22
created

Pipeline   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 128
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 40
c 3
b 0
f 0
dl 0
loc 128
rs 10
wmc 18

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A validateMiddleware() 0 25 5
A createMiddleware() 0 4 1
A middleware() 0 4 1
B validateClosure() 0 13 8
A run() 0 14 1
A createCoreFunction() 0 4 1
1
<?php
2
/**
3
 * Pipeline for middleware
4
 *
5
 * @package: Scrawler
6
 * @author: Pranjal Pandey
7
 */
8
namespace Scrawler;
9
10
use PhpParser\Error;
11
use Scrawler\Interfaces\MiddlewareInterface;
12
13
final class Pipeline
14
{
15
16
    /**
17
     * The array of middleware
18
     * @var array<\Closure>
19
     */
20
    private array $middlewares;
21
22
    /**
23
     * Create a new pipeline
24
     * @param array<\Closure> $middlewares
25
     */
26
    public function __construct(array $middlewares = [])
27
    {
28
        $this->middlewares = $middlewares;
29
    }
30
31
    /**
32
     * Add middleware(s) or Pipeline
33
     * @template T of MiddlewareInterface
34
     * @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...
35
     * @return array<\Closure>
36
     */
37
    public function validateMiddleware(array $middlewares): array
38
    {
39
        $validated = [];
40
        foreach ($middlewares as $middleware) {
41
            if (is_string($middleware)) {
42
                if (class_exists($middleware)) {
43
                    $middlewareObj = new $middleware;
44
                    if ($middlewareObj instanceof MiddlewareInterface) {
45
                        $callable = [$middlewareObj, 'run'];
46
                        $middleware = \Closure::fromCallable(callback: $callable);
47
                    } else {
48
                        throw new \Scrawler\Exception\InvalidMiddlewareException('Middleware class does not implement MiddlewareInterface');
49
                    }
50
                } else {
51
                    throw new \Scrawler\Exception\InvalidMiddlewareException('Middleware class does not exist');
52
                }
53
            }
54
            $middleware = \Closure::fromCallable(callback: $middleware);
55
            $this->validateClosure($middleware);
56
            $validated[] = $middleware;
57
        }
58
59
60
61
        return $validated;
62
    }
63
64
65
    private function validateClosure(\Closure $middleware): void
66
    {
67
        $refFunction = new \ReflectionFunction($middleware);
68
        $parameters = $refFunction->getParameters();
69
        foreach ($parameters as $parameter) {
70
            if ($parameter->getName() == 'request' && $parameter->getType() != 'Scrawler\Http\Request') {
71
                throw new \Scrawler\Exception\InvalidMiddlewareException('First parameter of middleware must be of type Scrawler\Http\Request');
72
            }
73
            if ($parameter->getName() == 'next' && $parameter->getType() != 'Closure') {
74
                throw new \Scrawler\Exception\InvalidMiddlewareException('Second parameter of middleware must be of type Closure');
75
            }
76
            if ($parameter->getName() != 'request' && $parameter->getName() != 'next') {
77
                throw new \Scrawler\Exception\InvalidMiddlewareException('Invalid parameter name in middleware');
78
            }
79
        }
80
    }
81
82
    /**
83
     * Add middleware(s) or Pipeline
84
     * @param array<\Closure> $middlewares
85
     * @return \Scrawler\Pipeline
86
     */
87
    public function middleware(array $middlewares): self
88
    {
89
        $this->middlewares = $middlewares;
90
        return $this;
91
    }
92
93
    /**
94
     * Run middleware around core function and pass an
95
     * object through it
96
     * @param  mixed  $object
97
     * @param  \Closure $core
98
     * @return mixed         
99
     */
100
    public function run($object, $core)
101
    {
102
        $coreFunction = $this->createCoreFunction($core);
103
104
105
        $middlewares = array_reverse($this->middlewares);
106
107
108
        $completePipeline = array_reduce($middlewares, function ($nextMiddleware, $middleware) {
109
            return $this->createMiddleware($nextMiddleware, $middleware);
110
        }, $coreFunction);
111
112
113
        return $completePipeline($object);
114
    }
115
116
117
    /**
118
     * The inner function of the onion.
119
     * This function will be wrapped on layers
120
     * @param  \Closure $core the core function
121
     * @return \Closure
122
     */
123
    private function createCoreFunction(\Closure $core)
124
    {
125
        return function ($object) use ($core) {
126
            return $core($object);
127
        };
128
    }
129
130
    /**
131
     * Get an pipeline middleware function.
132
     * This function will get the object from a previous layer and pass it inwards
133
     * @param  \Closure $nextMiddleware
134
     * @param  \Closure $middleware
135
     * @return \Closure
136
     */
137
    private function createMiddleware(\Closure $nextMiddleware, \Closure $middleware): \Closure
138
    {
139
        return function ($object) use ($nextMiddleware, $middleware) {
140
            return $middleware($object, $nextMiddleware);
141
        };
142
    }
143
144
145
}