Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/aphront/site/AphrontRoutingMap.php
13395 views
1
<?php
2
3
/**
4
* Collection of routes on a site for an application.
5
*
6
* @task info Map Information
7
* @task routing Routing
8
*/
9
final class AphrontRoutingMap extends Phobject {
10
11
private $site;
12
private $application;
13
private $routes = array();
14
15
16
/* -( Map Info )----------------------------------------------------------- */
17
18
19
public function setSite(AphrontSite $site) {
20
$this->site = $site;
21
return $this;
22
}
23
24
public function getSite() {
25
return $this->site;
26
}
27
28
public function setApplication(PhabricatorApplication $application) {
29
$this->application = $application;
30
return $this;
31
}
32
33
public function getApplication() {
34
return $this->application;
35
}
36
37
public function setRoutes(array $routes) {
38
$this->routes = $routes;
39
return $this;
40
}
41
42
public function getRoutes() {
43
return $this->routes;
44
}
45
46
47
/* -( Routing )------------------------------------------------------------ */
48
49
50
/**
51
* Find the route matching a path, if one exists.
52
*
53
* @param string Path to route.
54
* @return AphrontRoutingResult|null Routing result, if path matches map.
55
* @task routing
56
*/
57
public function routePath($path) {
58
$map = $this->getRoutes();
59
60
foreach ($map as $route => $value) {
61
$match = $this->tryRoute($route, $value, $path);
62
if (!$match) {
63
continue;
64
}
65
66
$result = $this->newRoutingResult();
67
$application = $result->getApplication();
68
69
$controller_class = $match['class'];
70
$controller = newv($controller_class, array());
71
$controller->setCurrentApplication($application);
72
73
$result
74
->setController($controller)
75
->setURIData($match['data']);
76
77
return $result;
78
}
79
80
return null;
81
}
82
83
84
/**
85
* Test a sub-map to see if any routes match a path.
86
*
87
* @param string Path to route.
88
* @param string Pattern from the map.
89
* @param string Value from the map.
90
* @return dict<string, wild>|null Match details, if path matches sub-map.
91
* @task routing
92
*/
93
private function tryRoute($route, $value, $path) {
94
$has_submap = is_array($value);
95
96
if (!$has_submap) {
97
// If the value is a controller rather than a sub-map, any matching
98
// route must completely consume the path.
99
$pattern = '(^'.$route.'\z)';
100
} else {
101
$pattern = '(^'.$route.')';
102
}
103
104
$data = null;
105
$ok = preg_match($pattern, $path, $data);
106
if ($ok === false) {
107
throw new Exception(
108
pht(
109
'Routing fragment "%s" is not a valid regular expression.',
110
$route));
111
}
112
113
if (!$ok) {
114
return null;
115
}
116
117
$path_match = $data[0];
118
119
// Clean up the data. We only want to retain named capturing groups, not
120
// the duplicated numeric captures.
121
foreach ($data as $k => $v) {
122
if (is_numeric($k)) {
123
unset($data[$k]);
124
}
125
}
126
127
if (!$has_submap) {
128
return array(
129
'class' => $value,
130
'data' => $data,
131
);
132
}
133
134
$sub_path = substr($path, strlen($path_match));
135
foreach ($value as $sub_route => $sub_value) {
136
$result = $this->tryRoute($sub_route, $sub_value, $sub_path);
137
if ($result) {
138
$result['data'] += $data;
139
return $result;
140
}
141
}
142
143
return null;
144
}
145
146
147
/**
148
* Build a new routing result for this map.
149
*
150
* @return AphrontRoutingResult New, empty routing result.
151
* @task routing
152
*/
153
private function newRoutingResult() {
154
return id(new AphrontRoutingResult())
155
->setSite($this->getSite())
156
->setApplication($this->getApplication());
157
}
158
159
}
160
161