Passed
Pull Request — master (#370)
by Valentin
04:52
created

PipelineInterceptor::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Domain;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use Psr\Container\ContainerInterface;
9
use Spiral\Core\CoreInterceptorInterface;
10
use Spiral\Core\CoreInterface;
11
use Spiral\Core\InterceptableCore;
12
use Spiral\Core\InterceptorPipeline;
13
use Spiral\Domain\Annotation\Pipeline;
14
15
class PipelineInterceptor implements CoreInterceptorInterface
16
{
17
    /** @var array */
18
    private $cache = [];
19
20
    /** @var AnnotationReader */
21
    private $reader;
22
23
    /** @var ContainerInterface */
24
    private $container;
25
26
    /**
27
     * @param AnnotationReader   $reader
28
     * @param ContainerInterface $container
29
     */
30
    public function __construct(AnnotationReader $reader, ContainerInterface $container)
31
    {
32
        $this->reader = $reader;
33
        $this->container = $container;
34
    }
35
36
    /**
37
     * @param string        $controller
38
     * @param string        $action
39
     * @param array         $parameters
40
     * @param CoreInterface $core
41
     * @return mixed
42
     * @throws \Throwable
43
     */
44
    public function process(string $controller, string $action, array $parameters, CoreInterface $core)
45
    {
46
        $annotation = $this->readAnnotation($controller, $action);
47
        if ($core instanceof InterceptorPipeline && $annotation->skipNext) {
48
            $this->removeNextInterceptorsFromOriginalPipeline($core);
49
        }
50
51
        $pipeline = $this->getCachedPipeline($controller, $action, $annotation);
52
        if (!empty($pipeline)) {
53
            if ($core instanceof InterceptorPipeline) {
54
                $this->injectInterceptorsIntoOriginalPipeline($core, $pipeline);
55
            } else {
56
                $core = new InterceptableCore($core);
57
                foreach ($pipeline as $interceptor) {
58
                    $core->addInterceptor($interceptor);
59
                }
60
            }
61
        }
62
63
        return $core->callAction($controller, $action, $parameters);
64
    }
65
66
    /**
67
     * @param string $controller
68
     * @param string $action
69
     * @return Pipeline
70
     */
71
    private function readAnnotation(string $controller, string $action): Pipeline
72
    {
73
        try {
74
            $method = new \ReflectionMethod($controller, $action);
75
        } catch (\ReflectionException $e) {
76
            return new Pipeline();
77
        }
78
79
        /** @var Pipeline $annotation */
80
        $annotation = $this->reader->getMethodAnnotation($method, Pipeline::class);
81
        return $annotation ?? new Pipeline();
82
    }
83
84
    /**
85
     * @param InterceptorPipeline $pipeline
86
     */
87
    private function removeNextInterceptorsFromOriginalPipeline(InterceptorPipeline $pipeline): void
88
    {
89
        $pipelineReflection = new \ReflectionProperty(InterceptorPipeline::class, 'interceptors');
90
        $pipelineReflection->setAccessible(true);
91
92
        $oldInterceptors = $pipelineReflection->getValue($pipeline);
93
        $newInterceptors = [];
94
        foreach ($oldInterceptors as $interceptor) {
95
            $newInterceptors[] = $interceptor;
96
            if ($interceptor instanceof self) {
97
                break;
98
            }
99
        }
100
101
        if (count($newInterceptors) !== count($oldInterceptors)) {
102
            $pipelineReflection->setValue($pipeline, $newInterceptors);
103
        }
104
        $pipelineReflection->setAccessible(false);
105
    }
106
107
    private function injectInterceptorsIntoOriginalPipeline(InterceptorPipeline $pipeline, array $interceptors): void
108
    {
109
        $pipelineReflection = new \ReflectionProperty(InterceptorPipeline::class, 'interceptors');
110
        $pipelineReflection->setAccessible(true);
111
112
        $oldInterceptors = $pipelineReflection->getValue($pipeline);
113
        $newInterceptors = [];
114
        foreach ($oldInterceptors as $interceptor) {
115
            $newInterceptors[] = $interceptor;
116
            if ($interceptor instanceof self) {
117
                foreach ($interceptors as $newInterceptor) {
118
                    $newInterceptors[] = $newInterceptor;
119
                }
120
            }
121
        }
122
123
        if (count($newInterceptors) !== count($oldInterceptors)) {
124
            $pipelineReflection->setValue($pipeline, $newInterceptors);
125
        }
126
        $pipelineReflection->setAccessible(false);
127
    }
128
129
    /**
130
     * @param string   $controller
131
     * @param string   $action
132
     * @param Pipeline $annotation
133
     * @return array
134
     */
135
    private function getCachedPipeline(string $controller, string $action, Pipeline $annotation): array
136
    {
137
        $key = "{$controller}:{$action}";
138
        if (!array_key_exists($key, $this->cache)) {
139
            $this->cache[$key] = $this->extractAnnotationPipeline($annotation);
140
        }
141
142
        return $this->cache[$key];
143
    }
144
145
    /**
146
     * @param Pipeline $annotation
147
     * @return array
148
     */
149
    private function extractAnnotationPipeline(Pipeline $annotation): array
150
    {
151
        $interceptors = [];
152
        foreach ($annotation->pipeline as $interceptor) {
153
            try {
154
                $interceptors[] = $this->container->get($interceptor);
155
            } catch (\Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
156
            }
157
        }
158
159
        return $interceptors;
160
    }
161
}
162