Skip to content

Instantly share code, notes, and snippets.

@kmuenkel
Created July 31, 2020 20:08
Show Gist options
  • Save kmuenkel/d654ed37e52e8fd4189c9ef60302b8d1 to your computer and use it in GitHub Desktop.
Save kmuenkel/d654ed37e52e8fd4189c9ef60302b8d1 to your computer and use it in GitHub Desktop.
Define Laravel routes by query parameters
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Arr;
use Illuminate\Http\Request;
use InvalidArgumentException;
use Illuminate\Routing\Route;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Auth;
use App\Exceptions\MissingPathParamException;
/**
* Class RedirectByQuery
* @package App\Http\Middleware
*/
class RedirectByQuery
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
[$routeName, $routeMapping] = $this->getRouteMapping($request);
if ($routeMapping) {
if (!($route = app(Router::class)->getRoutes()->getByName($routeName))) {
throw new InvalidArgumentException("Unknown route name '$routeName'.");
}
$this->applyPathParams($request, $routeMapping);
$this->applyMethod($request, $routeMapping);
}
return $next($request);
}
/**
* @param Request $request
* @param array $routeMapping
* @return string
*/
protected function applyMethod(Request $request, array $routeMapping)
{
$requestMethod = $request->getMethod();
$routeMethods = $routeMapping['to']['methods'] ?? [];
$requestMethod = in_array($requestMethod, $routeMethods) ? $requestMethod : Arr::first($routeMethods);
$request->setMethod($requestMethod);
return $request;
}
/**
* @param Request $request
* @param array $routeMapping
* @return Request
* @throws MissingPathParamException
*/
protected function applyPathParams(Request $request, array $routeMapping)
{
$routePath = $routeMapping['to']['path'];
$routeParams = $routeMapping['to']['params'] ?? [];
foreach ($routeParams as $from => $to) {
$fromValue = $request->get($from);
if (!preg_match('/{'.preg_quote($to).'}/', $routePath)) {
$request->request->add([$to => $fromValue]);
continue;
}
$routePath = str_ireplace('{'.$to.'}', $fromValue, $routePath);
}
$request->setPathInfo("/$routePath");
if (preg_match_all('/{(.+?)}/', $routePath, $missing)) {
throw (new MissingPathParamException('Missing required path params'))->setMissingParams($missing[1]);
}
return $request;
}
/**
* @param Request $request
* @return array[string|null, array|null]
*/
protected function getRouteMapping(Request $request)
{
$routeMap = collect(Route::getMap());
$routeMapping = $routeMap->filter(function (array $routeMapping) use ($request) {
$routeMatches = $this->pathMatches($request, $routeMapping);
$routeMatches &= $this->methodMatches($request, $routeMapping);
$routeMatches &= $this->paramsMatch($request, $routeMapping);
return $routeMatches;
});
$routeName = $routeMapping->keys()->first();
$routeMapping = $routeMapping->first();
return [$routeName, $routeMapping];
}
/**
* @param Request $request
* @param array $routeMapping
* @return bool
*/
protected function pathMatches(Request $request, array $routeMapping)
{
$requestPath = ltrim($request->getPathInfo(), '/');
$mappedPath = ltrim($routeMapping['from']['path'] ?? '', '/');
return !$mappedPath || $requestPath === $mappedPath;
}
/**
* @param Request $request
* @param array $routeMapping
* @return bool
*/
protected function methodMatches(Request $request, array $routeMapping)
{
$requestMethod = $request->getMethod();
$mappedMethods = $routeMapping['from']['methods'] ?? [];
return !$mappedMethods || in_array($requestMethod, $mappedMethods);
}
/**
* @param Request $request
* @param array $routeMapping
* @return bool
*/
protected function paramsMatch(Request $request, array $routeMapping)
{
$requestParams = $request->toArray();
$mappedParams = $routeMapping['from']['params'] ?? [];
return !$mappedParams || array_intersect_assoc($requestParams, $mappedParams) === $mappedParams;
}
}
class AppServiceProvider
{
/**
* @void
*/
protected function routeMacros()
{
$routeMap = [];
Route::macro('mapFrom', function ($path, $methods = ['GET'], $params = [], $map = []) use (&$routeMap) {
$methods = array_map('strtoupper', (array)$methods);
/** @var Route $this */
if (!($routeName = $this->getName())) {
throw new UnexpectedValueException('Mapped routes must have a name.');
}
$routeMap[$routeName] = [
'from' => ['path' => $path, 'methods' => $methods, 'params' => $params],
'to' => ['path' => $this->uri(), 'methods' => $this->methods(), 'params' => $map]
];
});
Route::macro('getMap', function () use (&$routeMap) {
return $routeMap;
});
Request::macro('setPathInfo', function ($path) {
$this->pathInfo = $path;
return $this;
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment