@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at upstream/main 166 lines 3.7 kB view raw
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 */ 9final 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 /** 25 * @return AphrontSite 26 */ 27 public function getSite() { 28 return $this->site; 29 } 30 31 public function setApplication(PhabricatorApplication $application) { 32 $this->application = $application; 33 return $this; 34 } 35 36 /** 37 * @return PhabricatorApplication 38 */ 39 public function getApplication() { 40 return $this->application; 41 } 42 43 public function setRoutes(array $routes) { 44 $this->routes = $routes; 45 return $this; 46 } 47 48 public function getRoutes() { 49 return $this->routes; 50 } 51 52 53/* -( Routing )------------------------------------------------------------ */ 54 55 56 /** 57 * Find the route matching a path, if one exists. 58 * 59 * @param string $path Path to route. 60 * @return AphrontRoutingResult|null Routing result, if path matches map. 61 * @task routing 62 */ 63 public function routePath($path) { 64 $map = $this->getRoutes(); 65 66 foreach ($map as $route => $value) { 67 $match = $this->tryRoute($route, $value, $path); 68 if (!$match) { 69 continue; 70 } 71 72 $result = $this->newRoutingResult(); 73 $application = $result->getApplication(); 74 75 $controller_class = $match['class']; 76 $controller = newv($controller_class, array()); 77 $controller->setCurrentApplication($application); 78 79 $result 80 ->setController($controller) 81 ->setURIData($match['data']); 82 83 return $result; 84 } 85 86 return null; 87 } 88 89 90 /** 91 * Test a sub-map to see if any routes match a path. 92 * 93 * @param string $route Pattern from the map. 94 * @param string $value Value from the map. 95 * @param string $path Path to route. 96 * @return array<string, array<string>|string>|null Match details, if path 97 * matches sub-map. 98 * @task routing 99 */ 100 private function tryRoute($route, $value, $path) { 101 $has_submap = is_array($value); 102 103 if (!$has_submap) { 104 // If the value is a controller rather than a sub-map, any matching 105 // route must completely consume the path. 106 $pattern = '(^'.$route.'\z)'; 107 } else { 108 $pattern = '(^'.$route.')'; 109 } 110 111 $data = null; 112 $ok = preg_match($pattern, $path, $data); 113 if ($ok === false) { 114 throw new Exception( 115 pht( 116 'Routing fragment "%s" is not a valid regular expression.', 117 $route)); 118 } 119 120 if (!$ok) { 121 return null; 122 } 123 124 $path_match = $data[0]; 125 126 // Clean up the data. We only want to retain named capturing groups, not 127 // the duplicated numeric captures. 128 foreach ($data as $k => $v) { 129 if (is_numeric($k)) { 130 unset($data[$k]); 131 } 132 } 133 134 if (!$has_submap) { 135 return array( 136 'class' => $value, 137 'data' => $data, 138 ); 139 } 140 141 $sub_path = substr($path, strlen($path_match)); 142 foreach ($value as $sub_route => $sub_value) { 143 $result = $this->tryRoute($sub_route, $sub_value, $sub_path); 144 if ($result) { 145 $result['data'] += $data; 146 return $result; 147 } 148 } 149 150 return null; 151 } 152 153 154 /** 155 * Build a new routing result for this map. 156 * 157 * @return AphrontRoutingResult New, empty routing result. 158 * @task routing 159 */ 160 private function newRoutingResult() { 161 return id(new AphrontRoutingResult()) 162 ->setSite($this->getSite()) 163 ->setApplication($this->getApplication()); 164 } 165 166}