MediaWiki master
PathMatcher.php
Go to the documentation of this file.
1<?php
2
4
34 private $treesByLength = [];
35
42 public static function newFromCache( $data ) {
43 $matcher = new self;
44 $matcher->treesByLength = $data;
45 return $matcher;
46 }
47
57 public function getCacheData() {
58 return $this->treesByLength;
59 }
60
67 private function isParam( $part ) {
68 $partLength = strlen( $part );
69 return $partLength > 2 && $part[0] === '{' && $part[$partLength - 1] === '}';
70 }
71
79 private function getParamName( $part ) {
80 if ( $this->isParam( $part ) ) {
81 return substr( $part, 1, -1 );
82 } else {
83 return false;
84 }
85 }
86
106 private function findConflict( $node, $parts, $index = 0 ) {
107 if ( $index >= count( $parts ) ) {
108 // If we reached the leaf node then a conflict is detected
109 return $node;
110 }
111 $part = $parts[$index];
112 $result = false;
113
114 if ( $this->isParam( $part ) ) {
115 foreach ( $node as $childNode ) {
116 // Params and tree entries for trailing empty slashes ('=') do not conflict
117 if ( !isset( $node['='] ) ) {
118 $result = $this->findConflict( $childNode, $parts, $index + 1 );
119 if ( $result !== false ) {
120 break;
121 }
122 }
123 }
124 } else {
125 if ( isset( $node["=$part"] ) ) {
126 $result = $this->findConflict( $node["=$part"], $parts, $index + 1 );
127 }
128
129 // Trailing empty slashes (aka an empty $part) do not conflict with params
130 if ( $result === false && isset( $node['*'] ) && $part !== '' ) {
131 $result = $this->findConflict( $node['*'], $parts, $index + 1 );
132 }
133 }
134 return $result;
135 }
136
155 public function add( $template, $userData ) {
156 // This will produce an empty part before any leading slash and after any trailing slash
157 $parts = explode( '/', $template );
158 $length = count( $parts );
159 if ( !isset( $this->treesByLength[$length] ) ) {
160 $this->treesByLength[$length] = [];
161 }
162 $tree =& $this->treesByLength[$length];
163
164 // Disallow empty path parts within the path (but not leading or trailing).
165 for ( $i = 1; $i < $length - 1; $i++ ) {
166 if ( $parts[$i] == '' ) {
167 throw new PathSegmentException( $template, $userData );
168 }
169 }
170
171 $conflict = $this->findConflict( $tree, $parts );
172 if ( $conflict !== false ) {
173 throw new PathConflict( $template, $userData, $conflict );
174 }
175
176 $params = [];
177 foreach ( $parts as $index => $part ) {
178 $paramName = $this->getParamName( $part );
179 if ( $paramName !== false ) {
180 $params[] = $paramName;
181 $key = '*';
182 } else {
183 $key = "=$part";
184 }
185 if ( $index === $length - 1 ) {
186 $tree[$key] = [
187 'template' => $template,
188 'paramNames' => $params,
189 'userData' => $userData
190 ];
191 } elseif ( !isset( $tree[$key] ) ) {
192 $tree[$key] = [];
193 }
194 $tree =& $tree[$key];
195 }
196 }
197
211 public function match( $path ) {
212 $parts = explode( '/', $path );
213 $length = count( $parts );
214 if ( !isset( $this->treesByLength[$length] ) ) {
215 return false;
216 }
217 $node = $this->treesByLength[$length];
218
219 $paramValues = [];
220 foreach ( $parts as $part ) {
221 if ( isset( $node["=$part"] ) ) {
222 $node = $node["=$part"];
223 } elseif ( isset( $node['*'] ) ) {
224 $node = $node['*'];
225 $paramValues[] = $part;
226 } else {
227 return false;
228 }
229 }
230
231 return [
232 'params' => array_combine( $node['paramNames'], $paramValues ),
233 'userData' => $node['userData']
234 ];
235 }
236}
array $params
The job parameters.
A tree-based path routing algorithm.
add( $template, $userData)
Add a template to the matcher.
static newFromCache( $data)
Create a PathMatcher from cache data.
match( $path)
Match a path against the current match trees.
getCacheData()
Get a data array for later use by newFromCache().