Generic   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 194
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 8
Bugs 1 Features 1
Metric Value
wmc 33
c 8
b 1
f 1
lcom 1
cbo 10
dl 0
loc 194
rs 9.3999

3 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 32 5
C match() 0 65 19
D assemble() 0 34 9
1
<?php
2
/**
3
 * Dash
4
 *
5
 * @link      http://github.com/DASPRiD/Dash For the canonical source repository
6
 * @copyright 2013-2015 Ben Scholzen 'DASPRiD'
7
 * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
8
 */
9
10
namespace Dash\Route;
11
12
use Dash\Exception;
13
use Dash\MatchResult;
14
use Dash\Parser\ParserInterface;
15
use Dash\RouteCollection\RouteCollectionInterface;
16
use Dash\RouteCollection\RouteCollectionMatcher;
17
use Psr\Http\Message\ServerRequestInterface;
18
19
/**
20
 * A generic route which takes care of all HTTP aspects.
21
 */
22
class Generic implements RouteInterface
23
{
24
    /**
25
     * Allowed methods on this route.
26
     *
27
     * @var array|null
28
     */
29
    protected $methods;
30
31
    /**
32
     * Whether to force the route to be HTTPS or HTTP.
33
     *
34
     * @var bool|null
35
     */
36
    protected $secure;
37
38
    /**
39
     * @var int|null
40
     */
41
    protected $port;
42
43
    /**
44
     * @var ParserInterface|null
45
     */
46
    protected $pathParser;
47
48
    /**
49
     * @var ParserInterface|null
50
     */
51
    protected $hostnameParser;
52
53
    /**
54
     * @var array
55
     */
56
    protected $defaults = [];
57
58
    /**
59
     * @var RouteCollectionInterface|null
60
     */
61
    protected $children;
62
63
    /**
64
     * @param ParserInterface|null          $pathParser
65
     * @param ParserInterface|null          $hostnameParser
66
     * @param array|null                    $methods
67
     * @param bool|null                     $secure
68
     * @param int|null                      $port
69
     * @param array                         $defaults
70
     * @param RouteCollectionInterface|null $children
71
     */
72
    public function __construct(
73
        ParserInterface $pathParser = null,
74
        ParserInterface $hostnameParser = null,
75
        array $methods = null,
76
        $secure = null,
77
        $port = null,
78
        array $defaults = [],
79
        RouteCollectionInterface $children = null
80
    ) {
81
        $this->pathParser     = $pathParser;
82
        $this->hostnameParser = $hostnameParser;
83
        $this->defaults       = $defaults;
84
        $this->children       = $children;
85
86
        if (null !== $secure) {
87
            $this->secure = (bool) $secure;
88
        }
89
90
        if (null !== $port) {
91
            $this->port = (int) $port;
92
        }
93
94
        if (null !== $methods) {
95
            $this->methods = array_map('strtoupper', array_values($methods));
96
97
            if (in_array('GET', $this->methods) ^ in_array('HEAD', $this->methods)) {
98
                // Implicitly enable HEAD on GET, and vise versa.
99
                $this->methods['GET']  = true;
100
                $this->methods['HEAD'] = true;
101
            }
102
        }
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     *
108
     * @throws Exception\UnexpectedValueException
109
     */
110
    public function match(ServerRequestInterface $request, $pathOffset)
111
    {
112
        $uri    = $request->getUri();
113
        $params = $this->defaults;
114
115
        // Verify scheme first, if set.
116
        if (true === $this->secure && 'https' !== $uri->getScheme()) {
117
            return MatchResult::fromSchemeFailure($uri->withScheme('https'));
118
        } elseif (false === $this->secure && 'http' !== $uri->getScheme()) {
119
            return MatchResult::fromSchemeFailure($uri->withScheme('http'));
120
        }
121
122
        // Then match hostname, if parser is set.
123
        if (null !== $this->hostnameParser) {
124
            $hostnameResult = $this->hostnameParser->parse($uri->getHost(), 0);
125
126
            if (null === $hostnameResult || strlen($uri->getHost()) !== $hostnameResult->getMatchLength()) {
127
                return null;
128
            }
129
130
            $params = $hostnameResult->getParams() + $params;
131
        }
132
133
        // Then match port, if set.
134
        if (null !== $this->port) {
135
            $port = $uri->getPort() ?: ('http' === $uri->getScheme() ? 80 : 443);
136
137
            if ($port !== $this->port) {
138
                return null;
139
            }
140
        }
141
142
        // Next match the path.
143
        $completePathMatched = false;
144
145
        if (null !== $this->pathParser) {
146
            if (null === ($pathResult = $this->pathParser->parse($uri->getPath(), $pathOffset))) {
147
                return null;
148
            }
149
150
            $pathOffset += $pathResult->getMatchLength();
151
            $completePathMatched = ($pathOffset === strlen($uri->getPath()));
152
            $params = $pathResult->getParams() + $params;
153
        }
154
155
        if ($completePathMatched) {
156
            if (null === $this->methods || in_array($request->getMethod(), $this->methods)) {
157
                return MatchResult::fromSuccess($params);
158
            }
159
160
            if (empty($this->methods)) {
161
                // Special case: when no methods are defined at all, this route may simply not terminate.
162
                return null;
163
            }
164
165
            return MatchResult::fromMethodFailure($this->methods);
166
        }
167
168
        // The path was not completely matched yet, so we check the children.
169
        if (null === $this->children) {
170
            return null;
171
        }
172
173
        return RouteCollectionMatcher::matchRouteCollection($this->children, $request, $pathOffset, $params);
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     *
179
     * @throws Exception\RuntimeException
180
     */
181
    public function assemble(array $params, $childName = null)
182
    {
183
        if ($childName !== null) {
184
            $nameParts  = explode('/', $childName, 2);
185
            $parentName = $nameParts[0];
186
            $childName  = isset($nameParts[1]) ? $nameParts[1] : null;
187
188
            if ($this->children === null) {
189
                throw new Exception\RuntimeException('Route has no children to assemble');
190
            }
191
192
            $assemblyResult = $this->children->get($parentName)->assemble($params, $childName);
193
        } else {
194
            $assemblyResult = new AssemblyResult();
195
        }
196
197
        if (null !== $this->secure) {
198
            $assemblyResult->scheme = $this->secure ? 'https' : 'http';
199
        }
200
201
        if (null !== $this->port) {
202
            $assemblyResult->port = $this->port;
203
        }
204
205
        if ($this->hostnameParser !== null) {
206
            $assemblyResult->host = $this->hostnameParser->compile($params, $this->defaults);
207
        }
208
209
        if ($this->pathParser !== null) {
210
            $assemblyResult->path = $this->pathParser->compile($params, $this->defaults) . $assemblyResult->path;
211
        }
212
213
        return $assemblyResult;
214
    }
215
}
216