Passed
Push — master ( 035a69...ef3bdc )
by Kirill
03:22
created

Route   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 124
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 17
eloc 48
c 1
b 0
f 0
dl 0
loc 124
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
B requestHandler() 0 35 9
A handle() 0 9 2
A withUriHandler() 0 12 2
A __construct() 0 9 2
A withContainer() 0 12 2
1
<?php
2
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Router;
13
14
use Psr\Container\ContainerExceptionInterface;
15
use Psr\Container\ContainerInterface;
16
use Psr\Http\Message\ResponseFactoryInterface;
17
use Psr\Http\Message\ResponseInterface;
18
use Psr\Http\Message\ServerRequestInterface;
19
use Psr\Http\Server\RequestHandlerInterface;
20
use Spiral\Http\CallableHandler;
21
use Spiral\Router\Exception\RouteException;
22
use Spiral\Router\Exception\TargetException;
23
use Spiral\Router\Traits\PipelineTrait;
24
25
/**
26
 * Default route provides ability to route request to a given callable handler.
27
 *
28
 * Examples:
29
 *
30
 * new Route("/login", function(){
31
 *   return "hello";
32
 * });
33
 * new Route("/login", new Handler());
34
 * new Route("/login", \App\Handlers\Handler::class);
35
 *
36
 * new Route("/login", new Action(\App\Controllers|HomeController::class, "login");
37
 * new Route("/<controller>/<action>/<id>", new Namespaced("\App\Controllers");
38
 * new Route("/signup/<action>", new Controller(\App\Controllers\SignupController::class);
39
 * new Route("://<domain>/info", new Action(\App\Controllers|ProfileController::class, "info");
40
 * new Route("/<controller>/<action>/<id>", new Group(["profile" =>
41
 * \App\Controllers|ProfileController::class]);
42
 */
43
final class Route extends AbstractRoute implements ContainerizedInterface
44
{
45
    use PipelineTrait;
46
47
    public const ROUTE_ATTRIBUTE = 'route';
48
49
    /** @var string|callable|RequestHandlerInterface|TargetInterface */
50
    private $target;
51
52
    /** @var RequestHandlerInterface */
53
    private $requestHandler;
54
55
    /**
56
     * @param string                                                  $pattern  Uri pattern.
57
     * @param string|callable|RequestHandlerInterface|TargetInterface $target   Callable route target.
58
     * @param array                                                   $defaults Default value set.
59
     */
60
    public function __construct(string $pattern, $target, array $defaults = [])
61
    {
62
        if ($target instanceof TargetInterface) {
63
            parent::__construct($pattern, array_merge($target->getDefaults(), $defaults));
64
        } else {
65
            parent::__construct($pattern, $defaults);
66
        }
67
68
        $this->target = $target;
69
    }
70
71
    /**
72
     * @param UriHandler $uriHandler
73
     * @return RouteInterface
74
     */
75
    public function withUriHandler(UriHandler $uriHandler): RouteInterface
76
    {
77
        /** @var self $route */
78
        $route = parent::withUriHandler($uriHandler);
79
        if ($this->target instanceof TargetInterface) {
80
            $route->uriHandler = $route->uriHandler->withConstrains(
81
                $this->target->getConstrains(),
82
                $this->defaults
83
            );
84
        }
85
86
        return $route;
87
    }
88
89
    /**
90
     * Associated route with given container.
91
     *
92
     * @param ContainerInterface $container
93
     * @return ContainerizedInterface|$this
94
     */
95
    public function withContainer(ContainerInterface $container): ContainerizedInterface
96
    {
97
        $route = clone $this;
98
        $route->container = $container;
99
100
        if ($route->target instanceof TargetInterface) {
101
            $route->target = clone $route->target;
102
        }
103
104
        $route->pipeline = $route->makePipeline();
105
106
        return $route;
107
    }
108
109
    /**
110
     * @param ServerRequestInterface $request
111
     * @return ResponseInterface
112
     *
113
     * @throws RouteException
114
     */
115
    public function handle(ServerRequestInterface $request): ResponseInterface
116
    {
117
        if (empty($this->requestHandler)) {
118
            $this->requestHandler = $this->requestHandler();
119
        }
120
121
        return $this->pipeline->process(
122
            $request->withAttribute(self::ROUTE_ATTRIBUTE, $this),
123
            $this->requestHandler
124
        );
125
    }
126
127
    /**
128
     * @return RequestHandlerInterface
129
     *
130
     * @throws RouteException
131
     */
132
    protected function requestHandler(): RequestHandlerInterface
133
    {
134
        if (!$this->hasContainer()) {
135
            throw new RouteException('Unable to configure route pipeline without associated container');
136
        }
137
138
        if ($this->target instanceof TargetInterface) {
139
            try {
140
                return $this->target->getHandler($this->container, $this->matches);
0 ignored issues
show
Bug introduced by
It seems like $this->matches can also be of type null; however, parameter $matches of Spiral\Router\TargetInterface::getHandler() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

140
                return $this->target->getHandler($this->container, /** @scrutinizer ignore-type */ $this->matches);
Loading history...
141
            } catch (TargetException $e) {
142
                throw new RouteException('Invalid target resolution', $e->getCode(), $e);
143
            }
144
        }
145
146
        if ($this->target instanceof RequestHandlerInterface) {
147
            return $this->target;
148
        }
149
150
        try {
151
            if (is_object($this->target) || is_array($this->target)) {
152
                $target = $this->target;
153
            } else {
154
                $target = $this->container->get($this->target);
0 ignored issues
show
Bug introduced by
It seems like $this->target can also be of type callable; however, parameter $id of Psr\Container\ContainerInterface::get() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

154
                $target = $this->container->get(/** @scrutinizer ignore-type */ $this->target);
Loading history...
155
            }
156
157
            if ($target instanceof RequestHandlerInterface) {
158
                return $target;
159
            }
160
161
            return new CallableHandler(
162
                $target,
0 ignored issues
show
Bug introduced by
It seems like $target can also be of type object; however, parameter $callable of Spiral\Http\CallableHandler::__construct() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

162
                /** @scrutinizer ignore-type */ $target,
Loading history...
163
                $this->container->get(ResponseFactoryInterface::class)
164
            );
165
        } catch (ContainerExceptionInterface $e) {
166
            throw new RouteException($e->getMessage(), $e->getCode(), $e);
0 ignored issues
show
Bug introduced by
The method getMessage() does not exist on Psr\Container\ContainerExceptionInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Psr\Container\NotFoundExceptionInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

166
            throw new RouteException($e->/** @scrutinizer ignore-call */ getMessage(), $e->getCode(), $e);
Loading history...
Bug introduced by
The method getCode() does not exist on Psr\Container\ContainerExceptionInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Psr\Container\NotFoundExceptionInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

166
            throw new RouteException($e->getMessage(), $e->/** @scrutinizer ignore-call */ getCode(), $e);
Loading history...
167
        }
168
    }
169
}
170