前に Laravel のルート定義でコントローラーのクラス名とメソッド名を別々に書きたい みたいなのを書いた後、それっぽくルート定義を書けるようにするためのクラスを作りました。
マジックメソッドでミドルウェアがシュッと書けるので、ミドルウェアの記述がスッキリします。
<?php
namespace App\Routing;
use Illuminate\Routing\Router;
/**
* @method $this auth()
* @method $this can(string $arg)
*/
class AppRouteCollector
{
/**
* @var Router
*/
private $router;
/**
* @var array
*/
private $methods = [];
/**
* @var array
*/
private $paths = [];
/**
* @var array
*/
private $actions = [];
/**
* @var array
*/
private $names = [];
/**
* @var array
*/
private $middlewares = [];
/**
* @var array
*/
private $wheres = [];
/**
* @var bool
*/
private $last = true;
public function __construct(Router $router)
{
$this->router = $router;
}
public function __destruct()
{
if (!$this->last) {
return;
}
$this->router->match(
self::generateMethods($this->methods),
self::generatePath($this->paths),
[
'uses' => self::generateUses($this->actions),
'as' => self::generateAs($this->names),
'middleware' => self::generateMiddleware($this->middlewares),
'where' => self::generateWhere($this->wheres),
]
);
}
private static function generateMethods(array $methods): array
{
$methods = array_map(function (string $method) {
return strtoupper($method);
}, $methods);
return $methods;
}
private static function generatePath(array $paths): string
{
$paths = array_map(function (string $path) {
return trim($path, '/');
}, $paths);
$paths = array_filter($paths, function (string $path) {
return strlen($path) > 0;
});
return '/' . implode('/', $paths);
}
private static function generateUses(array $actions): string
{
$actions = array_map(function (string $action) {
return trim($action, '\\');
}, $actions);
if ($actions && end($actions)[0] === '@') {
$method = array_pop($actions);
$actions = implode('\\', $actions) . $method;
} else {
$actions = implode('\\', $actions);
}
return $actions;
}
private static function generateAs(array $names): string
{
return implode('', $names);
}
private static function generateMiddleware(array $middlewares): array
{
$arr = [];
foreach ($middlewares as $middleware => $arg) {
if (is_bool($arg)) {
$arg = var_export($arg, true);
}
if ($arg !== null) {
$middleware = "$middleware:$arg";
}
$arr[] = $middleware;
}
return $arr;
}
private static function generateWhere(array $wheres): array
{
return $wheres;
}
/**
* @return static
*/
private function chain()
{
$this->last = false;
$new = clone $this;
$new->last = true;
return $new;
}
/**
* @param string $method
* @param string $path
*
* @return static
*/
public function method(string $method, string $path)
{
$new = $this->path($path);
$new->methods[] = strtoupper($method);
return $new;
}
/**
* @param string $path
*
* @return static
*/
public function get(string $path)
{
return $this->method(__FUNCTION__, $path);
}
/**
* @param string $path
*
* @return static
*/
public function post(string $path)
{
return $this->method(__FUNCTION__, $path);
}
/**
* @param string $path
*
* @return static
*/
public function put(string $path)
{
return $this->method(__FUNCTION__, $path);
}
/**
* @param string $path
*
* @return static
*/
public function patch(string $path)
{
return $this->method(__FUNCTION__, $path);
}
/**
* @param string $path
*
* @return static
*/
public function delete(string $path)
{
return $this->method(__FUNCTION__, $path);
}
/**
* @param string $path
*
* @return static
*/
public function options(string $path)
{
return $this->method(__FUNCTION__, $path);
}
/**
* @param string $path
*
* @return static
*/
public function path($path)
{
$new = $this->chain();
$new->paths[] = trim($path, '/');
return $new;
}
/**
* @param string $action
*
* @return static
*/
public function action(string $action)
{
$new = $this->chain();
$new->actions[] = $action;
return $new;
}
/**
* @param string $name
*
* @return static
*/
public function name(string $name)
{
$new = $this->chain();
$new->names[] = $name;
return $new;
}
/**
* @param string $middleware
* @param mixed $arg
*
* @return static
*/
public function middleware(string $middleware, $arg = null)
{
$new = $this->chain();
$new->middlewares[$middleware] = $arg;
return $new;
}
/**
* @param string $where
* @param string $expr
*
* @return static
*/
public function where(string $where, string $expr)
{
$new = $this->chain();
$new->wheres[$where] = $expr;
return $new;
}
/**
* @param string $name
* @param array $arguments
*
* @return static
*/
public function __call($name, $arguments)
{
return $this->middleware($name, $arguments[0] ?? null);
}
/**
* @param string $name
*
* @return static
*/
public function __get($name)
{
return $this->middleware($name);
}
/**
* @param callable $callback
*/
public function group(callable $callback): void
{
$new = $this->chain();
$new->last = false;
$callback($new);
}
}
次のように使います。middleware('can:admin')
が can('admin')
のように書けます。静的解析にも優しいです。
use App\Routing\AppRouteCollector as R;
$r = resolve(R::class);
$r->auth()->group(function (R $r) {
$r->action(UserController::class)->can('admin')->group(function (R $r) {
$r->get('/users')->action('@index')->name('user.list');
$r->get('/users/create')->action('@create')->name('user.create');
$r->post('/users')->action('@store')->name('user.store');
$r->get('/users/{user}')->action('@show')->name('user.show');
$r->get('/users/{user}/edit')->action('@edit')->name('user.edit');
$r->put('/users/{user}')->action('@update')->name('user.update');
$r->delete('/users/{user}')->action('@destroy')->name('user.destroy');
});
});
さいごに
ついカッとなって作ったものの、我に返って冷静になると別に素のままの Laravel のルート定義でも良いか・・・と思ったのでお蔵入り。