Route::__unserialize()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
namespace pjpawel\LightApi\Route;
4
5
use Exception;
6
use pjpawel\LightApi\Container\Awareness\ContainerAwareInterface;
7
use pjpawel\LightApi\Container\ContainerLoader;
8
use pjpawel\LightApi\Container\ContainerNotFoundException;
9
use pjpawel\LightApi\Exception\KernelException;
10
use pjpawel\LightApi\Exception\ProgrammerException;
11
use pjpawel\LightApi\Http\Exception\HttpException;
12
use pjpawel\LightApi\Http\Request;
13
use pjpawel\LightApi\Http\Response;
14
use pjpawel\LightApi\Http\ResponseStatus;
15
use Psr\Container\ContainerExceptionInterface;
16
use Psr\Container\NotFoundExceptionInterface;
17
use ReflectionClass;
18
use ReflectionNamedType;
19
20
class Route
21
{
22
23
    private string $className;
24
    private string $methodName;
25
    public string $path;
26
    public array $httpMethods;
27
    public ?string $regexPath = null;
28
29
    public function __construct(string $className, string $methodName, string $path, array $httpMethods)
30
    {
31
        $this->className = $className;
32
        $this->methodName = $methodName;
33
        $this->path = $path;
34
        $this->httpMethods = $httpMethods;
35
    }
36
37
    /**
38
     * @return void
39
     * @throws \ReflectionException
40
     * @throws Exception
41
     */
42
    public function makeRegexPath(): void
43
    {
44
        $this->regexPath = $this->path;
45
        preg_match_all('/\{\w+\}/', $this->path, $pathParameters, PREG_SET_ORDER);
46
        $this->regexPath = '/^' . str_replace('/', '\\/', $this->regexPath) . '$/';
47
        if (empty($pathParameters)) {
48
            return;
49
        }
50
        $pathParameters = array_map(
51
            function($param) {
52
                return substr($param[0], 1, strlen($param[0])-2);
53
            },
54
            $pathParameters
55
        );
56
        $reflectionMethod = (new ReflectionClass($this->className))->getMethod($this->methodName);
57
        foreach ($reflectionMethod->getParameters() as $parameter) {
58
            if (($parameterIndex = array_search($parameter->getName(), $pathParameters, true)) !== false) {
59
                /** @var \ReflectionNamedType $parameterType */
60
                $parameterType = $parameter->getType();
61
                if ($parameterType->isBuiltin()) {
62
                    $parameterTypeName = $parameterType->getName();
63
                    if ($parameterTypeName == 'string') {
64
                        $this->regexPath = str_replace('{' . $pathParameters[$parameterIndex] . '}', '(\w+)', $this->regexPath);
65
                    } elseif ($parameterTypeName == 'int') {
66
                        $this->regexPath = str_replace('{' . $pathParameters[$parameterIndex] . '}', '(\d+)', $this->regexPath);
67
                    } else {
68
                        throw new ProgrammerException(sprintf('Not supported builtin type %s for parameter %s', $parameterTypeName, $parameter->getName()));
69
                    }
70
                    //$this->pathParams[] = $pathParameters[$parameterIndex];
71
                    unset($pathParameters[$parameterIndex]);
72
                } else {
73
                    throw new ProgrammerException('Invalid parameter type ' . $parameterType->getName());
74
                }
75
            }
76
        }
77
        if (!empty($pathParameters)) {
78
            throw new ProgrammerException('Missing value for :' . implode(', ', $pathParameters));
79
        }
80
    }
81
82
    /**
83
     * @param ContainerLoader $container
84
     * @param Request $request
85
     * @return Response
86
     * @throws \ReflectionException
87
     * @throws ContainerNotFoundException
88
     * @throws Exception
89
     */
90
    public function execute(ContainerLoader $container, Request $request): Response
91
    {
92
        try {
93
            $reflectionClass = new ReflectionClass($this->className);
94
            $constructor = $reflectionClass->getConstructor();
95
            $constructorArgs = [];
96
            if ($constructor != null) {
97
                $constructorArgs = $this->loadArguments($constructor->getParameters(), $container, $request); //, false
98
            }
99
            $class = $reflectionClass->newInstanceArgs($constructorArgs);
100
101
            $reflectionMethod = $reflectionClass->getMethod($this->methodName);
102
            $args = $this->loadArguments($reflectionMethod->getParameters(), $container, $request); //, true
103
104
            if (is_subclass_of($class, ContainerAwareInterface::class)) {
105
                $class->setContainer($container);
106
            }
107
108
            $result = $reflectionMethod->invokeArgs($class, $args);
109
            if ($result instanceof Response) {
110
                return $result;
111
            } elseif (is_array($result)) {
112
                return new Response($result);
113
            } elseif (is_string($result)) {
114
                return new Response($result);
115
            } else {
116
                throw new ProgrammerException('Invalid object type of response: ' . var_export($result, true));
117
            }
118
        } catch (HttpException $httpExc) {
119
            return new Response($httpExc->getMessage(), ResponseStatus::from($httpExc->getCode()));
120
        } catch (Exception $exc) {
121
            return new Response('Internal server error occurred', ResponseStatus::INTERNAL_SERVER_ERROR);
122
        }
123
    }
124
125
    /**
126
     * @param \ReflectionParameter[] $parameters
127
     * @param ContainerLoader $container
128
     * @param Request $request
129
     * @return array
130
     * @throws KernelException
131
     * @throws ContainerExceptionInterface
132
     * @throws NotFoundExceptionInterface
133
     * @throws \ReflectionException
134
     * @throws ContainerNotFoundException
135
     */
136
    private function loadArguments(array $parameters, ContainerLoader $container, Request $request): array
137
    {
138
        $pathParameters = [];
139
        if ($this->regexPath !== null && preg_match($this->regexPath, $request->path, $pathParameters) === false) {
140
            throw new KernelException('Cannot math regex path to requested path while loading parameters');
141
        }
142
        array_shift($pathParameters);
143
        $pathParameterIndex = 0;
144
        $args = [];
145
        foreach ($parameters as $parameter) {
146
            /** @var ReflectionNamedType $type */
147
            $type = $parameter->getType();
148
            if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
149
                if ($type->getName() == Request::class) {
150
                    $args[] = $request;
151
                } elseif ($type->getName() == Response::class) {
152
                    $args[] = new Response();
153
                } else {
154
                    $args[] = $container->get($type->getName());
155
                }
156
            } else {
157
                if ($type->getName() == 'int') {
158
                    $args[] = (int) $pathParameters[$pathParameterIndex];
159
                } else {
160
                    $args[] = $pathParameters[$pathParameterIndex];
161
                }
162
                $pathParameterIndex++;
163
            }
164
        }
165
        return $args;
166
    }
167
168
    public function __serialize(): array
169
    {
170
        return [
171
            $this->className,
172
            $this->methodName,
173
            $this->path,
174
            $this->httpMethods,
175
            $this->regexPath
176
        ];
177
    }
178
179
    public function __unserialize(array $data): void
180
    {
181
        $this->className = $data[0];
182
        $this->methodName = $data[1];
183
        $this->path = $data[2];
184
        $this->httpMethods = $data[3];
185
        $this->regexPath = $data[4];
186
    }
187
}