htmlburger /
wpemerge
| 1 | <?php |
||||||
| 2 | /** |
||||||
| 3 | * @package WPEmerge |
||||||
| 4 | * @author Atanas Angelov <[email protected]> |
||||||
| 5 | * @copyright 2018 Atanas Angelov |
||||||
| 6 | * @license https://www.gnu.org/licenses/gpl-2.0.html GPL-2.0 |
||||||
| 7 | * @link https://wpemerge.com/ |
||||||
| 8 | */ |
||||||
| 9 | |||||||
| 10 | namespace WPEmerge\Routing; |
||||||
| 11 | |||||||
| 12 | use WPEmerge\Exceptions\ConfigurationException; |
||||||
| 13 | use WPEmerge\Helpers\Handler; |
||||||
| 14 | use WPEmerge\Requests\RequestInterface; |
||||||
| 15 | use WPEmerge\Routing\Conditions\ConditionFactory; |
||||||
| 16 | use WPEmerge\Routing\Conditions\ConditionInterface; |
||||||
| 17 | use WPEmerge\Support\Arr; |
||||||
| 18 | |||||||
| 19 | /** |
||||||
| 20 | * Provide routing for site requests (i.e. all non-api requests). |
||||||
| 21 | */ |
||||||
| 22 | class Router implements HasRoutesInterface { |
||||||
| 23 | use HasRoutesTrait; |
||||||
| 24 | |||||||
| 25 | /** |
||||||
| 26 | * Condition factory. |
||||||
| 27 | * |
||||||
| 28 | * @var ConditionFactory |
||||||
| 29 | */ |
||||||
| 30 | protected $condition_factory = null; |
||||||
| 31 | |||||||
| 32 | /** |
||||||
| 33 | * Group stack. |
||||||
| 34 | * |
||||||
| 35 | * @var array<array<string, mixed>> |
||||||
| 36 | */ |
||||||
| 37 | protected $group_stack = []; |
||||||
| 38 | |||||||
| 39 | /** |
||||||
| 40 | * Current active route. |
||||||
| 41 | * |
||||||
| 42 | * @var RouteInterface |
||||||
| 43 | */ |
||||||
| 44 | protected $current_route = null; |
||||||
| 45 | |||||||
| 46 | /** |
||||||
| 47 | * Constructor. |
||||||
| 48 | * |
||||||
| 49 | * @codeCoverageIgnore |
||||||
| 50 | * @param ConditionFactory $condition_factory |
||||||
| 51 | */ |
||||||
| 52 | public function __construct( ConditionFactory $condition_factory ) { |
||||||
| 53 | $this->condition_factory = $condition_factory; |
||||||
| 54 | } |
||||||
| 55 | |||||||
| 56 | /** |
||||||
| 57 | * Get the current route. |
||||||
| 58 | * |
||||||
| 59 | * @return RouteInterface |
||||||
| 60 | */ |
||||||
| 61 | public function getCurrentRoute() { |
||||||
| 62 | return $this->current_route; |
||||||
| 63 | 1 | } |
|||||
| 64 | 1 | ||||||
| 65 | /** |
||||||
| 66 | * Set the current route. |
||||||
| 67 | * |
||||||
| 68 | * @param RouteInterface |
||||||
| 69 | * @return void |
||||||
| 70 | */ |
||||||
| 71 | public function setCurrentRoute( RouteInterface $current_route ) { |
||||||
| 72 | $this->current_route = $current_route; |
||||||
| 73 | 1 | } |
|||||
| 74 | 1 | ||||||
| 75 | 1 | /** |
|||||
| 76 | * Merge the methods attribute combining values. |
||||||
| 77 | * |
||||||
| 78 | * @param array<string> $old |
||||||
| 79 | * @param array<string> $new |
||||||
| 80 | * @return array<string> |
||||||
| 81 | */ |
||||||
| 82 | public function mergeMethodsAttribute( $old, $new ) { |
||||||
| 83 | return array_merge( $old, $new ); |
||||||
| 84 | } |
||||||
| 85 | |||||||
| 86 | /** |
||||||
| 87 | * Merge the condition attribute. |
||||||
| 88 | * |
||||||
| 89 | * @param string|ConditionInterface $old |
||||||
| 90 | * @param string|ConditionInterface $new |
||||||
| 91 | * @return ConditionInterface|string |
||||||
| 92 | */ |
||||||
| 93 | public function mergeConditionAttribute( $old, $new ) { |
||||||
| 94 | try { |
||||||
| 95 | $condition = $this->condition_factory->merge( $old, $new ); |
||||||
| 96 | } catch ( ConfigurationException $e ) { |
||||||
| 97 | throw new ConfigurationException( 'Route condition is not a valid route string or condition.' ); |
||||||
| 98 | } |
||||||
| 99 | |||||||
| 100 | return $condition !== null ? $condition : ''; |
||||||
| 101 | } |
||||||
| 102 | |||||||
| 103 | /** |
||||||
| 104 | * Merge the middleware attribute combining values. |
||||||
| 105 | * |
||||||
| 106 | * @param array<string> $old |
||||||
| 107 | * @param array<string> $new |
||||||
| 108 | * @return array<string> |
||||||
| 109 | */ |
||||||
| 110 | public function mergeMiddlewareAttribute( $old, $new ) { |
||||||
| 111 | return array_merge( $old, $new ); |
||||||
| 112 | } |
||||||
| 113 | |||||||
| 114 | /** |
||||||
| 115 | * Merge the namespace attribute taking the latest value. |
||||||
| 116 | * |
||||||
| 117 | * @param string $old |
||||||
| 118 | * @param string $new |
||||||
| 119 | * @return string |
||||||
| 120 | */ |
||||||
| 121 | public function mergeNamespaceAttribute( $old, $new ) { |
||||||
| 122 | 1 | return ! empty( $new ) ? $new : $old; |
|||||
| 123 | 1 | } |
|||||
| 124 | |||||||
| 125 | 1 | /** |
|||||
| 126 | * Merge the handler attribute taking the latest value. |
||||||
| 127 | * |
||||||
| 128 | 1 | * @param string|\Closure $old |
|||||
| 129 | * @param string|\Closure $new |
||||||
| 130 | * @return string|\Closure |
||||||
| 131 | 1 | */ |
|||||
| 132 | 1 | public function mergeHandlerAttribute( $old, $new ) { |
|||||
| 133 | return ! empty( $new ) ? $new : $old; |
||||||
| 134 | } |
||||||
| 135 | |||||||
| 136 | /** |
||||||
| 137 | 3 | * Merge attributes into route. |
|||||
| 138 | 3 | * |
|||||
| 139 | * @param array<string, mixed> $old |
||||||
| 140 | 2 | * @param array<string, mixed> $new |
|||||
| 141 | 2 | * @return array<string, mixed> |
|||||
| 142 | 1 | */ |
|||||
| 143 | public function mergeAttributes( $old, $new ) { |
||||||
| 144 | 1 | $attributes = [ |
|||||
| 145 | 'methods' => $this->mergeMethodsAttribute( |
||||||
| 146 | 2 | (array) Arr::get( $old, 'methods', [] ), |
|||||
| 147 | (array) Arr::get( $new, 'methods', [] ) |
||||||
| 148 | ), |
||||||
| 149 | |||||||
| 150 | 'condition' => $this->mergeConditionAttribute( |
||||||
| 151 | Arr::get( $old, 'condition', '' ), |
||||||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||||
| 152 | 2 | Arr::get( $new, 'condition', '' ) |
|||||
|
0 ignored issues
–
show
It seems like
WPEmerge\Support\Arr::get($new, 'condition', '') can also be of type array<string,mixed>; however, parameter $new of WPEmerge\Routing\Router::mergeConditionAttribute() does only seem to accept WPEmerge\Routing\Conditi...nditionInterface|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
Loading history...
|
|||||||
| 153 | 2 | ), |
|||||
| 154 | 2 | ||||||
| 155 | 'middleware' => $this->mergeMiddlewareAttribute( |
||||||
| 156 | 2 | (array) Arr::get( $old, 'middleware', [] ), |
|||||
| 157 | 1 | (array) Arr::get( $new, 'middleware', [] ) |
|||||
| 158 | 1 | ), |
|||||
| 159 | 1 | ||||||
| 160 | 1 | 'namespace' => $this->mergeNamespaceAttribute( |
|||||
| 161 | 1 | Arr::get( $old, 'namespace', '' ), |
|||||
|
1 ignored issue
–
show
It seems like
WPEmerge\Support\Arr::get($old, 'namespace', '') can also be of type array<string,mixed>; however, parameter $old of WPEmerge\Routing\Router::mergeNamespaceAttribute() 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
Loading history...
|
|||||||
| 162 | Arr::get( $new, 'namespace', '' ) |
||||||
|
1 ignored issue
–
show
It seems like
WPEmerge\Support\Arr::get($new, 'namespace', '') can also be of type array<string,mixed>; however, parameter $new of WPEmerge\Routing\Router::mergeNamespaceAttribute() 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
Loading history...
|
|||||||
| 163 | 2 | ), |
|||||
| 164 | 2 | ||||||
| 165 | 'handler' => $this->mergeNamespaceAttribute( |
||||||
| 166 | 2 | Arr::get( $old, 'handler', '' ), |
|||||
| 167 | Arr::get( $new, 'handler', '' ) |
||||||
| 168 | 2 | ), |
|||||
| 169 | ]; |
||||||
| 170 | 2 | ||||||
| 171 | 2 | return $attributes; |
|||||
| 172 | 2 | } |
|||||
| 173 | 2 | ||||||
| 174 | /** |
||||||
| 175 | 2 | * Get the top group from the stack. |
|||||
| 176 | * |
||||||
| 177 | * @codeCoverageIgnore |
||||||
| 178 | * @return array<string, mixed> |
||||||
| 179 | */ |
||||||
| 180 | protected function getGroup() { |
||||||
| 181 | return Arr::last( $this->group_stack, null, [] ); |
||||||
| 182 | } |
||||||
| 183 | |||||||
| 184 | 1 | /** |
|||||
| 185 | * Add a group to the group stack, merging all previous attributes. |
||||||
| 186 | * |
||||||
| 187 | * @codeCoverageIgnore |
||||||
| 188 | 1 | * @param array<string, mixed> $group |
|||||
| 189 | * @return void |
||||||
| 190 | */ |
||||||
| 191 | protected function pushGroup( $group ) { |
||||||
| 192 | $this->group_stack[] = $this->mergeAttributes( $this->getGroup(), $group ); |
||||||
| 193 | } |
||||||
| 194 | |||||||
| 195 | /** |
||||||
| 196 | * Remove last group from the group stack. |
||||||
| 197 | 2 | * |
|||||
| 198 | 2 | * @codeCoverageIgnore |
|||||
| 199 | * @return void |
||||||
| 200 | 2 | */ |
|||||
| 201 | 2 | protected function popGroup() { |
|||||
| 202 | 1 | array_pop( $this->group_stack ); |
|||||
| 203 | 1 | } |
|||||
| 204 | |||||||
| 205 | 1 | /** |
|||||
| 206 | * Create a route group. |
||||||
| 207 | 1 | * |
|||||
| 208 | * @codeCoverageIgnore |
||||||
| 209 | * @param array<string, mixed> $attributes |
||||||
| 210 | * @param \Closure|string $routes Closure or path to file. |
||||||
| 211 | * @return void |
||||||
| 212 | */ |
||||||
| 213 | public function group( $attributes, $routes ) { |
||||||
| 214 | $this->pushGroup( $attributes ); |
||||||
| 215 | |||||||
| 216 | if ( is_string( $routes ) ) { |
||||||
| 217 | // @codeCoverageIgnore |
||||||
| 218 | require_once $routes; |
||||||
| 219 | } else { |
||||||
| 220 | $routes(); |
||||||
| 221 | } |
||||||
| 222 | |||||||
| 223 | $this->popGroup(); |
||||||
| 224 | } |
||||||
| 225 | |||||||
| 226 | /** |
||||||
| 227 | * Make a route condition. |
||||||
| 228 | * |
||||||
| 229 | * @param mixed $condition |
||||||
| 230 | * @return ConditionInterface |
||||||
| 231 | */ |
||||||
| 232 | protected function routeCondition( $condition ) { |
||||||
| 233 | if ( $condition === '' ) { |
||||||
| 234 | throw new ConfigurationException( 'No route condition specified. Did you miss to call url() or where()?' ); |
||||||
| 235 | } |
||||||
| 236 | |||||||
| 237 | if ( ! $condition instanceof ConditionInterface ) { |
||||||
| 238 | $condition = $this->condition_factory->make( $condition ); |
||||||
| 239 | } |
||||||
| 240 | |||||||
| 241 | return $condition; |
||||||
| 242 | } |
||||||
| 243 | |||||||
| 244 | /** |
||||||
| 245 | * Make a route handler. |
||||||
| 246 | * |
||||||
| 247 | * @codeCoverageIgnore |
||||||
| 248 | * @param string|\Closure|null $handler |
||||||
| 249 | * @param string $namespace |
||||||
| 250 | * @return Handler |
||||||
| 251 | */ |
||||||
| 252 | protected function routeHandler( $handler, $namespace ) { |
||||||
| 253 | return new Handler( $handler, '', $namespace ); |
||||||
| 254 | } |
||||||
| 255 | |||||||
| 256 | /** |
||||||
| 257 | * Make a route. |
||||||
| 258 | * |
||||||
| 259 | * @param array<string, mixed> $attributes |
||||||
| 260 | * @return RouteInterface |
||||||
| 261 | */ |
||||||
| 262 | public function route( $attributes ) { |
||||||
| 263 | $attributes = $this->mergeAttributes( $this->getGroup(), $attributes ); |
||||||
| 264 | |||||||
| 265 | $methods = Arr::get( $attributes, 'methods', [] ); |
||||||
| 266 | $condition = Arr::get( $attributes, 'condition', null ); |
||||||
| 267 | $handler = Arr::get( $attributes, 'handler', '' ); |
||||||
| 268 | $namespace = Arr::get( $attributes, 'namespace', '' ); |
||||||
| 269 | $middleware = Arr::get( $attributes, 'middleware', [] ); |
||||||
| 270 | |||||||
| 271 | $condition = $this->routeCondition( $condition ); |
||||||
| 272 | $handler = $this->routeHandler( $handler, $namespace ); |
||||||
|
2 ignored issues
–
show
It seems like
$namespace can also be of type array<string,mixed>; however, parameter $namespace of WPEmerge\Routing\Router::routeHandler() 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
Loading history...
It seems like
$handler can also be of type array<string,mixed>; however, parameter $handler of WPEmerge\Routing\Router::routeHandler() does only seem to accept Closure|null|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
Loading history...
|
|||||||
| 273 | |||||||
| 274 | $route = new Route( $methods, $condition, $handler ); |
||||||
| 275 | |||||||
| 276 | $route->middleware( $middleware ); |
||||||
| 277 | |||||||
| 278 | return $route; |
||||||
| 279 | } |
||||||
| 280 | |||||||
| 281 | /** |
||||||
| 282 | * Assign and return the first satisfied route (if any) as the current one for the given request. |
||||||
| 283 | * |
||||||
| 284 | * @param RequestInterface $request |
||||||
| 285 | * @return RouteInterface |
||||||
| 286 | */ |
||||||
| 287 | public function execute( $request ) { |
||||||
| 288 | $routes = $this->getRoutes(); |
||||||
| 289 | |||||||
| 290 | foreach ( $routes as $route ) { |
||||||
| 291 | if ( $route->isSatisfied( $request ) ) { |
||||||
| 292 | $this->setCurrentRoute( $route ); |
||||||
| 293 | return $route; |
||||||
| 294 | } |
||||||
| 295 | } |
||||||
| 296 | |||||||
| 297 | return null; |
||||||
| 298 | } |
||||||
| 299 | } |
||||||
| 300 |